Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .env
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ VITE_FEATURE_CHAINFLIP_SWAP_DCA=true
VITE_FEATURE_COWSWAP=true
VITE_FEATURE_THOR_SWAP=true
VITE_FEATURE_ZRX_SWAP=true
VITE_FEATURE_FYND_SWAP=false
VITE_FEATURE_MAYA_SWAP=true
VITE_FEATURE_AVNU_SWAP=true
VITE_FEATURE_DYNAMIC_LP_ASSETS=false
Expand Down Expand Up @@ -310,6 +311,7 @@ VITE_ZERION_BASE_URL=https://api.proxy.shapeshift.com/api/v1/zerion

# 0x
VITE_ZRX_BASE_URL=https://api.proxy.shapeshift.com/api/v1/zrx/
VITE_FYND_ETHEREUM_BASE_URL=http://localhost:3000/fynd-api

# Bebop
VITE_BEBOP_API_KEY=b4a7ffa9-2abb-45ae-8ddd-ec33bc377939
Expand Down
4 changes: 4 additions & 0 deletions .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,7 @@ VITE_PROXY_API_BASE_URL=https://dev-api.proxy.shapeshift.com

# Agentic Chat
# VITE_AGENTIC_SERVER_BASE_URL=http://localhost:4111
VITE_FEATURE_FYND_SWAP=true
VITE_FYND_ETHEREUM_BASE_URL=http://localhost:3000/fynd-api
FYND_ETHEREUM_BASE_URL=https://fynd-api.propellerheads.xyz/v1/ethereum
FYND_API_KEY=
2 changes: 2 additions & 0 deletions .env.production
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@ VITE_FEATURE_CELO=false

# mixpanel
VITE_MIXPANEL_TOKEN=9d304465fc72224aead9e027e7c24356
VITE_FEATURE_FYND_SWAP=false
VITE_FYND_ETHEREUM_BASE_URL=http://localhost:3001/v1
5 changes: 5 additions & 0 deletions headers/csps/defi/swappers/Fynd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { Csp } from '../../../types'

export const csp: Csp = {
'connect-src': ['http://localhost:3001', 'https://fynd-api.propellerheads.xyz'],
}
2 changes: 2 additions & 0 deletions headers/csps/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import { csp as bebop } from './defi/swappers/Bebop'
import { csp as bobGateway } from './defi/swappers/BobGateway'
import { csp as butterSwap } from './defi/swappers/ButterSwap'
import { csp as cowSwap } from './defi/swappers/CowSwap'
import { csp as fynd } from './defi/swappers/Fynd'
import { csp as nearIntents } from './defi/swappers/NearIntents'
import { csp as oneInch } from './defi/swappers/OneInch'
import { csp as portals } from './defi/swappers/Portals'
Expand Down Expand Up @@ -188,6 +189,7 @@ export const csps = [
bobGateway,
bebop,
cowSwap,
fynd,
nearIntents,
oneInch,
portals,
Expand Down
1 change: 1 addition & 0 deletions packages/public-api/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export const getServerConfig = (): SwapperConfig => ({
VITE_COWSWAP_BASE_URL: env.COWSWAP_BASE_URL,
VITE_PORTALS_BASE_URL: env.PORTALS_BASE_URL,
VITE_ZRX_BASE_URL: env.ZRX_BASE_URL,
VITE_FYND_ETHEREUM_BASE_URL: '',
VITE_CHAINFLIP_API_KEY: env.CHAINFLIP_API_KEY,
VITE_CHAINFLIP_API_URL: env.CHAINFLIP_API_URL,
VITE_FEATURE_CHAINFLIP_SWAP_DCA: env.FEATURE_CHAINFLIP_SWAP_DCA,
Expand Down
6 changes: 6 additions & 0 deletions packages/swapper/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { cetusApi, cetusSwapper } from './swappers/CetusSwapper'
import { chainflipApi, chainflipSwapper } from './swappers/ChainflipSwapper'
import { cowApi, cowSwapper } from './swappers/CowSwapper'
import { debridgeApi, debridgeSwapper } from './swappers/DebridgeSwapper'
import { fyndApi, fyndSwapper } from './swappers/FyndSwapper'
import { mayachainApi, mayachainSwapper } from './swappers/MayachainSwapper'
import { nearIntentsApi, nearIntentsSwapper } from './swappers/NearIntentsSwapper'
import { portalsApi, portalsSwapper } from './swappers/PortalsSwapper'
Expand Down Expand Up @@ -104,6 +105,10 @@ export const swappers: Record<SwapperName, (SwapperApi & Swapper) | undefined> =
...bobGatewaySwapper,
...bobGatewayApi,
},
[SwapperName.Fynd]: {
...fyndSwapper,
...fyndApi,
},
Comment on lines +108 to +111

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 8 'SwapperName\.Fynd|VITE_FEATURE_FYND_SWAP|fyndApi|fyndSwapper' \
  src packages/public-api packages/swapper

Repository: shapeshift/web

Length of output: 22478


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== swapper registry consumers =="
rg -n "swapperRegistry\)|getSwapper\(|executeEvmTransaction\(|getUnsignedEvmTransaction\(|getTradeQuote\(|getTradeRate\(|checkTradeStatus\(|getEvmTransactionFees\(" packages src --glob '*.ts' --glob '*.tsx' | head -n 240

echo
echo "== constants outline/section =="
cat -n packages/swapper/src/constants.ts | sed -n '1,220p'

echo
echo "== public API files =="
git ls-files packages/public-api | sort

Repository: shapeshift/web

Length of output: 18303


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== selector helpers around Fynd =="
cat -n src/state/helpers.ts | sed -n '1,130p'
echo
echo "== preferences slice around Fynd =="
cat -n src/state/slices/preferencesSlice/preferencesSlice.ts | sed -n '210,255p'

echo
echo "== feature flag references concise =="
rg -n "FyndSwap|VITE_FEATURE_FYND_SWAP|SwapperName\.Fynd|getAvailable.*Swapper|is.*Swapper" src packages/swapper packages/public-api --glob '*.ts' --glob '*.tsx' | head -n 240

Repository: shapeshift/web

Length of output: 19607


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== swapper registry exported/use site =="
cat -n packages/swapper/src/swapper.ts | sed -n '1,110p'

echo
echo "== public-api swapper deps and index =="
cat -n packages/public-api/src/swapperDeps.ts | sed -n '1,220p'
echo
cat -n packages/public-api/src/index.ts | sed -n '1,220p'

echo
echo "== public-api route quote/rate/index =="
cat -n packages/public-api/src/routes/quote/getQuote.ts | sed -n '1,180p'
echo
cat -n packages/public-api/src/routes/rates/getRates.ts | sed -n '1,180p'
echo
cat -n packages/public-api/src/routes/quote/index.ts | sed -n '1,180p'
echo
cat -n packages/public-api/src/routes/rates/index.ts | sed -n '1,180p'

echo
echo "== public-api registry/status =="
cat -n packages/public-api/src/registry.ts | sed -n '1,220p'
echo
cat -n packages/public-api/src/routes/status/getSwapStatus.ts | sed -n '1,220p'
echo
cat -n packages/public-api/src/routes/status/index.ts | sed -n '1,220p'

Repository: shapeshift/web

Length of output: 39202


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== public-api env/config =="
cat -n packages/public-api/src/env.ts | sed -n '1,220p'
echo
echo "== public-api config feature constants =="
cat -n packages/public-api/src/config.ts | sed -n '1,260p'
echo
echo "== public-api constants enabled swapper env =="
cat -n packages/public-api/src/constants.ts | sed -n '1,220p'

echo
echo "== public-api docs mentioning Fynd/swapper names =="
rg -n "SwapperName|Fynd|enabled|ENABLED|quote/status/rates" packages/public-api/docs --glob '*.md'

Repository: shapeshift/web

Length of output: 9015


Security And Privacy (CWE-16)

Reachability: Internal · Exploitability: Moderate

Reachability path
● Entry
  packages/swapper/src/swappers/FyndSwapper/getTradeQuote/getTradeQuote.ts:19
  getTradeQuote
│
▼
● Sink
  packages/swapper/src/constants.ts

Gate the Fynd registry entry with VITE_FEATURE_FYND_SWAP.

packages/swapper/src/constants.ts exports fyndSwapper and fyndApi directly into swappers[SwapperName.Fynd], making quote/rate/unsigned transaction methods reachable through public API paths. Add public-API env/config support for this flag and skip exporting Fynd unless it is enabled, instead of relying only on app selectors/UI gating.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/swapper/src/constants.ts` around lines 108 - 111, Update the
swappers registry entry for SwapperName.Fynd to conditionally include the
combined fyndSwapper and fyndApi exports only when VITE_FEATURE_FYND_SWAP is
enabled. Add or reuse the public API environment/config flag used by
constants.ts, and ensure disabled configurations omit the Fynd entry entirely
rather than merely hiding it in UI selectors.

[SwapperName.Test]: undefined,
}

Expand Down Expand Up @@ -132,6 +137,7 @@ export const getDefaultSlippageDecimalPercentageForSwapper = (
switch (swapperName) {
case SwapperName.Zrx:
case SwapperName.Bebop:
case SwapperName.Fynd:
case SwapperName.Test:
return DEFAULT_SLIPPAGE_DECIMAL_PERCENTAGE
case SwapperName.CowSwap:
Expand Down
1 change: 1 addition & 0 deletions packages/swapper/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export * from './swappers/ChainflipSwapper'
export * from './swappers/SunioSwapper'
export * from './swappers/CowSwapper'
export * from './swappers/DebridgeSwapper'
export * from './swappers/FyndSwapper'
export * from './swappers/PortalsSwapper'
export * from './swappers/ThorchainSwapper'
export * from './swappers/MayachainSwapper'
Expand Down
4 changes: 4 additions & 0 deletions packages/swapper/src/swappers/FyndSwapper/FyndSwapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import type { Swapper } from '../../types'
import { executeEvmTransaction } from '../../utils'

export const fyndSwapper: Swapper = { executeEvmTransaction }
14 changes: 14 additions & 0 deletions packages/swapper/src/swappers/FyndSwapper/endpoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import type { SwapperApi } from '../../types'
import { checkEvmSwapStatus } from '../../utils'
import { getEvmTransactionFees, getUnsignedEvmTransaction } from '../../utils/evm'
import { getTradeQuote } from './getTradeQuote/getTradeQuote'
import { getTradeRate } from './getTradeRate/getTradeRate'
import type { FyndTradeQuoteInput, FyndTradeRateInput } from './types'

export const fyndApi: SwapperApi = {
getTradeQuote: (input, deps) => getTradeQuote(input as FyndTradeQuoteInput, deps),
getTradeRate: (input, deps) => getTradeRate(input as FyndTradeRateInput, deps),
getUnsignedEvmTransaction,
getEvmTransactionFees,
checkTradeStatus: checkEvmSwapStatus,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import type { Result } from '@sniptt/monads'
import { Err, Ok } from '@sniptt/monads'

import { getDefaultSlippageDecimalPercentageForSwapper } from '../../../constants'
import type {
SingleHopTradeQuoteSteps,
SwapErrorRight,
SwapperDeps,
TradeQuote,
} from '../../../types'
import { SwapperName, TradeQuoteError } from '../../../types'
import { assertQuoteAddresses, makeSwapErrorRight } from '../../../utils'
import type { FyndTradeQuoteInput } from '../types'
import { fetchFromFynd } from '../utils/fetchFromFynd'
import { getFyndStepData } from '../utils/getFyndStepData'
import { getFyndTradeContext } from '../utils/getFyndTradeContext'
import { assertValidTrade } from '../utils/helpers'

export const getTradeQuote = async (
input: FyndTradeQuoteInput,
deps: SwapperDeps,
): Promise<Result<TradeQuote[], SwapErrorRight>> => {
const maybeAddresses = assertQuoteAddresses(input)
if (maybeAddresses.isErr()) return Err(maybeAddresses.unwrapErr())
const { sendAddress, receiveAddress } = maybeAddresses.unwrap()

const validation = assertValidTrade(input)
if (validation.isErr()) return Err(validation.unwrapErr())

const slippageTolerancePercentageDecimal =
input.slippageTolerancePercentageDecimal ??
getDefaultSlippageDecimalPercentageForSwapper(SwapperName.Fynd)
const maybeFynd = await fetchFromFynd({
sellAsset: input.sellAsset,
buyAsset: input.buyAsset,
sellAmountCryptoBaseUnit: input.sellAmountIncludingProtocolFeesCryptoBaseUnit,
sender: sendAddress,
receiver: receiveAddress,
slippageTolerancePercentageDecimal,
baseUrl: deps.config.VITE_FYND_ETHEREUM_BASE_URL,
quoteOrRate: 'quote',
})
if (maybeFynd.isErr()) return Err(maybeFynd.unwrapErr())
const { quote, routerAddress } = maybeFynd.unwrap()
if (!quote.transaction) {
return Err(
makeSwapErrorRight({
message: 'Fynd returned an unencoded quote',
code: TradeQuoteError.InvalidResponse,
}),
)
}

const maybeContext = getFyndTradeContext({
input,
quote,
routerAddress,
slippageTolerancePercentageDecimal,
})
if (maybeContext.isErr()) return Err(maybeContext.unwrapErr())
const { tradeCommon, stepCommon, protocolFees } = maybeContext.unwrap()

const maybeStepData = await getFyndStepData({
type: 'quote',
input,
deps,
sellAsset: input.sellAsset,
transaction: quote.transaction,
from: sendAddress,
})
if (maybeStepData.isErr()) return Err(maybeStepData.unwrapErr())
const { transactionData, networkFeeCryptoBaseUnit } = maybeStepData.unwrap()

const tradeQuote: TradeQuote = {
...tradeCommon,
quoteOrRate: 'quote',
receiveAddress,
steps: [
{
...stepCommon,
accountNumber: input.accountNumber,
transactionData,
feeData: { networkFeeCryptoBaseUnit, protocolFees },
},
] as SingleHopTradeQuoteSteps,
}

return Ok([tradeQuote])
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import type { Result } from '@sniptt/monads'
import { Err, Ok } from '@sniptt/monads'

import { getDefaultSlippageDecimalPercentageForSwapper } from '../../../constants'
import type {
SingleHopTradeRateSteps,
SwapErrorRight,
SwapperDeps,
TradeRate,
} from '../../../types'
import { SwapperName } from '../../../types'
import type { FyndTradeRateInput } from '../types'
import { FYND_RATE_ADDRESS } from '../utils/constants'
import { fetchFromFynd } from '../utils/fetchFromFynd'
import { getFyndStepData } from '../utils/getFyndStepData'
import { getFyndTradeContext } from '../utils/getFyndTradeContext'
import { assertValidTrade } from '../utils/helpers'

export const getTradeRate = async (
input: FyndTradeRateInput,
deps: SwapperDeps,
): Promise<Result<TradeRate[], SwapErrorRight>> => {
const validation = assertValidTrade(input)
if (validation.isErr()) return Err(validation.unwrapErr())

const address = input.receiveAddress ?? FYND_RATE_ADDRESS
const slippageTolerancePercentageDecimal =
input.slippageTolerancePercentageDecimal ??
getDefaultSlippageDecimalPercentageForSwapper(SwapperName.Fynd)
const maybeFynd = await fetchFromFynd({
sellAsset: input.sellAsset,
buyAsset: input.buyAsset,
sellAmountCryptoBaseUnit: input.sellAmountIncludingProtocolFeesCryptoBaseUnit,
sender: address,
receiver: address,
slippageTolerancePercentageDecimal,
baseUrl: deps.config.VITE_FYND_ETHEREUM_BASE_URL,
quoteOrRate: 'rate',
})
if (maybeFynd.isErr()) return Err(maybeFynd.unwrapErr())
const { quote, routerAddress } = maybeFynd.unwrap()

const maybeContext = getFyndTradeContext({
input,
quote,
routerAddress,
slippageTolerancePercentageDecimal,
})
if (maybeContext.isErr()) return Err(maybeContext.unwrapErr())
const { tradeCommon, stepCommon, protocolFees } = maybeContext.unwrap()
const maybeStepData = await getFyndStepData({
type: 'rate',
input,
deps,
sellAsset: input.sellAsset,
gasEstimate: quote.gas_estimate,
gasPrice: quote.gas_price,
})
if (maybeStepData.isErr()) return Err(maybeStepData.unwrapErr())
const { networkFeeCryptoBaseUnit } = maybeStepData.unwrap()

const tradeRate: TradeRate = {
...tradeCommon,
quoteOrRate: 'rate',
receiveAddress: input.receiveAddress,
steps: [
{
...stepCommon,
accountNumber: input.accountNumber,
feeData: { networkFeeCryptoBaseUnit, protocolFees },
},
] as SingleHopTradeRateSteps,
}

return Ok([tradeRate])
}
3 changes: 3 additions & 0 deletions packages/swapper/src/swappers/FyndSwapper/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { fyndApi } from './endpoints'
export { fyndSwapper } from './FyndSwapper'
export * from './types'
60 changes: 60 additions & 0 deletions packages/swapper/src/swappers/FyndSwapper/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type { Address, Hex } from 'viem'

import type { GetEvmTradeQuoteInput, GetEvmTradeRateInput } from '../../types'

export type FyndTradeQuoteInput = GetEvmTradeQuoteInput
export type FyndTradeRateInput = GetEvmTradeRateInput

export type FyndQuoteStatus =
| 'success'
| 'no_route_found'
| 'insufficient_liquidity'
| 'timeout'
| 'not_ready'
| 'price_check_failed'

export type FyndTransaction = {
to: Address
value: string
data: Hex
client_fee_signature_offset: number | null
}

export type FyndFeeBreakdown = {
router_fee: string
client_fee: string
max_slippage: string
min_amount_received: string
swaps_hash: string | null
}

export type FyndOrderQuote = {
order_id: string
status: FyndQuoteStatus
amount_in: string
amount_out: string
amount_out_net_gas: string
gas_estimate: string
gas_price: string | null
price_impact_bps: number | null
route: {
swaps: {
protocol: string
}[]
} | null
transaction: FyndTransaction | null
fee_breakdown: FyndFeeBreakdown | null
}

export type FyndQuoteResponse = {
orders: FyndOrderQuote[]
total_gas_estimate: string
solve_time_ms: number
}

export type FyndInfoResponse = {
chain_id: number
router_address: Address | null
permit2_address: Address | null
version: string
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { KnownChainIds } from '@shapeshiftoss/types'
import type { Address } from 'viem'

export const FYND_SUPPORTED_CHAIN_IDS = [KnownChainIds.EthereumMainnet] as const
export type FyndSupportedChainId = (typeof FYND_SUPPORTED_CHAIN_IDS)[number]

export const FYND_NATIVE_ASSET_ADDRESS = '0x0000000000000000000000000000000000000000' as Address
export const FYND_RATE_ADDRESS = '0x0000000000000000000000000000000000000001' as Address
export const FYND_ROUTER_FEE_DIVISOR = '100000'
Loading