Skip to content
Open
Show file tree
Hide file tree
Changes from all 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