@anywalletkit/protocol-swap2-fynd 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/activity.cjs +1 -0
- package/dist/activity.cjs.map +1 -1
- package/dist/activity.js +2 -2
- package/dist/{chunk-MU4JJYO3.js → chunk-4BCEAXID.js} +3 -3
- package/dist/{chunk-EF7GO24L.js → chunk-J6XM7WEG.js} +2 -1
- package/dist/{chunk-EF7GO24L.js.map → chunk-J6XM7WEG.js.map} +1 -1
- package/dist/{chunk-Q23IYNLF.js → chunk-PF2NGOA5.js} +2 -2
- package/dist/{chunk-DKT3R77D.js → chunk-TMFP2W5C.js} +3 -3
- package/dist/index.cjs +1 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -4
- package/dist/protocol.cjs +1 -0
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.js +4 -4
- package/dist/provider.cjs +1 -0
- package/dist/provider.cjs.map +1 -1
- package/dist/provider.js +3 -3
- package/dist/router.cjs +1 -0
- package/dist/router.cjs.map +1 -1
- package/dist/router.d.cts +2 -2
- package/dist/router.d.ts +2 -2
- package/dist/router.js +1 -1
- package/package.json +5 -5
- /package/dist/{chunk-MU4JJYO3.js.map → chunk-4BCEAXID.js.map} +0 -0
- /package/dist/{chunk-Q23IYNLF.js.map → chunk-PF2NGOA5.js.map} +0 -0
- /package/dist/{chunk-DKT3R77D.js.map → chunk-TMFP2W5C.js.map} +0 -0
package/dist/activity.cjs
CHANGED
|
@@ -114,6 +114,7 @@ function checkFyndTransaction(tx, expected) {
|
|
|
114
114
|
return { method: call.functionName };
|
|
115
115
|
}
|
|
116
116
|
function fyndVerifyQuote(params, quote) {
|
|
117
|
+
if (params.sourceChain !== params.destChain) return "same-chain swaps only";
|
|
117
118
|
try {
|
|
118
119
|
const chainId = /^eip155:[0-9]+$/.test(params.sourceChain) ? Number(params.sourceChain.slice(7)) : NaN;
|
|
119
120
|
const src = (0, import_util.sourceAssetOf)(params);
|
package/dist/activity.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/activity.ts","../src/router.ts"],"sourcesContent":["/**\n * Activity recognition for Fynd: a swap entry point of the pinned\n * TychoRouterV3 on a chain the module serves → `swap`, \"Swapped A → B\" from\n * the owner's net transfers.\n */\nimport type {ActivityItem, ChainTx, DescribeActivityContext} from \"@anywalletkit/core\";\nimport {chainLabel, describeSwapLike, matchesCall} from \"@anywalletkit/protocol-swap2-core/activity\";\nimport {evmRef, isEvm} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {FYND_CHAINS, TYCHO_ROUTER_SELECTORS} from \"./router.js\";\n\nexport const FYND_MODULE = \"@anywalletkit/protocol-swap2-fynd\";\n\nexport function describeFyndActivity(tx: ChainTx, ctx: DescribeActivityContext): ActivityItem | null {\n if (!isEvm(tx.chain)) return null;\n const chain = FYND_CHAINS[evmRef(tx.chain)];\n if (!chain) return null;\n if (!matchesCall(tx, [chain.router], TYCHO_ROUTER_SELECTORS)) return null;\n return describeSwapLike(tx, ctx, {protocol: FYND_MODULE, subtitle: `Fynd · ${chainLabel(tx.chain)}`, kind: \"swap\"});\n}\n","/**\n * TychoRouterV3 — the only contract a Fynd quote may settle through (Fynd\n * License §3: every settlement of a Fynd output goes through the router in\n * Propeller Heads' registry). This file pins the router per chain and checks\n * Fynd's encoded transaction against the request before anything is approved\n * or sent: right contract, right method, the exact input, the requested\n * tokens, the user as receiver, the quoted output and minimum, and no client\n * fee. The route inside `swaps` is opaque and is not inspected here — the\n * router itself reverts below `minAmountOut`.\n */\nimport type {Quote, QuoteParams, RemotePins} from \"@anywalletkit/protocol-swap2-core/types\";\nimport {sourceAssetOf} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {decodeFunctionData, getAddress, parseAbi, toFunctionSelector, type Hex} from \"viem\";\n\n/** Router registry: github.com/propeller-heads/tycho crates/tycho-execution/config/deployment_registry.json (TychoRouterV3, effective 2026-09-01). */\nexport const FYND_CHAINS: Readonly<Record<number, {slug: string; name: string; router: `0x${string}`}>> = Object.freeze({\n 1: {slug: \"ethereum\", name: \"Ethereum\", router: \"0x1644D2477f809cc2C71bCCFd6Dc9497E3F83210d\"},\n 8453: {slug: \"base\", name: \"Base\", router: \"0xAbA5B53b03eAfaD1C5fc8BD5Fc765fC85Bb3de67\"},\n 42161: {slug: \"arbitrum\", name: \"Arbitrum\", router: \"0x924F147c50eA59f5180a26031A8b65B2aA1e81Cd\"},\n 56: {slug: \"bsc\", name: \"BNB Smart Chain\", router: \"0x7f3D12BBaFb8955E51B3ab9588b34c8ad95BDA4e\"},\n 137: {slug: \"polygon\", name: \"Polygon\", router: \"0xbd4e6011F03355C2A377Fd9Af939322A7d0A1bC1\"},\n 130: {slug: \"unichain\", name: \"Unichain\", router: \"0xCba5574597Ad00EA250Fd106DAB4Fc7461949635\"},\n 4663: {slug: \"robinhood\", name: \"Robinhood Chain\", router: \"0x09215a470bD585E59EB3F4B612fBD2678131fF9e\"},\n});\n\n/** The router's native-coin sentinel (lib/NativeETH.sol); Fynd's API uses the zero address instead. */\nexport const ROUTER_NATIVE = \"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE\";\nconst ZERO = \"0x0000000000000000000000000000000000000000\";\n\nconst PARAMS = \"uint256 amountIn, address tokenIn, address tokenOut, uint256 expectedAmountOut, uint256 minAmountOut\";\nconst FEE = \"(uint32 clientFeeBps, address clientFeeReceiver, uint256 maxClientContribution, uint256 deadline, bytes clientSignature) clientFeeParams\";\nconst PERMIT = \"((address token, uint160 amount, uint48 expiration, uint48 nonce) details, address spender, uint256 sigDeadline) permitSingle, bytes signature\";\n\n/** The three entry points a wallet swap uses (plain `transferFrom` from the user). */\nexport const TYCHO_ROUTER_ABI = parseAbi([\n `function singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData) payable returns (uint256)`,\n `function sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n `function splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n]);\n\n/** Every swap entry point of the router (Permit2 and vault variants included) — for recognizing history. */\nexport const TYCHO_ROUTER_SELECTORS: ReadonlySet<string> = new Set(\n [\n `singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwapUsingVault(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swapData)`,\n `sequentialSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n `splitSwapPermit2(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n ].map((sig) => toFunctionSelector(`function ${sig}`)),\n);\n\nexport class FyndTransactionError extends Error {\n constructor(message: string) {\n super(`fynd: ${message}`);\n this.name = \"FyndTransactionError\";\n }\n}\n\nexport interface ExpectedSwap {\n chainId: number;\n /** ERC-20 address, or the zero address for the native coin. */\n tokenIn: string;\n tokenOut: string;\n amountIn: bigint;\n receiver: string;\n /** Fynd's gross `amount_out` (before the router fee and slippage). */\n expectedAmountOut: bigint;\n /** Fynd's `min_amount_received` — what the router enforces on-chain. */\n minAmountOut: bigint;\n}\n\nexport interface EncodedTx {\n to: string;\n value: bigint;\n data: string;\n}\n\nconst sameAddress = (a: string, b: string) => {\n try {\n return getAddress(a) === getAddress(b);\n } catch {\n return false;\n }\n};\nconst routerToken = (t: string) => (sameAddress(t, ZERO) ? ROUTER_NATIVE : t);\n\n/** Refuses (throws {@link FyndTransactionError}) unless `tx` is exactly the swap the wallet asked for. */\nexport function checkFyndTransaction(tx: EncodedTx, expected: ExpectedSwap): {method: \"singleSwap\" | \"sequentialSwap\" | \"splitSwap\"} {\n const chain = FYND_CHAINS[expected.chainId];\n if (!chain) throw new FyndTransactionError(`chain ${expected.chainId} has no pinned Tycho router`);\n // `tx.to` is the API's: named in the refusal only when it is an address (scan 2026-10-27 modules, F34).\n if (!sameAddress(tx.to, chain.router)) throw new FyndTransactionError(`the transaction targets ${/^0x[0-9a-fA-F]{40}$/.test(tx.to) ? tx.to : \"something that is not an address\"}, not the Tycho router on ${chain.name}`);\n if (sameAddress(expected.receiver, ZERO) || sameAddress(expected.receiver, chain.router)) throw new FyndTransactionError(\"invalid receiver\");\n if (expected.amountIn <= 0n || expected.minAmountOut <= 0n || expected.minAmountOut > expected.expectedAmountOut) {\n throw new FyndTransactionError(\"invalid quote amounts\");\n }\n const nativeIn = sameAddress(expected.tokenIn, ZERO);\n if (tx.value !== (nativeIn ? expected.amountIn : 0n)) throw new FyndTransactionError(\"native value does not match the input\");\n if (!/^0x(?:[0-9a-fA-F]{2}){4,}$/.test(tx.data)) throw new FyndTransactionError(\"malformed calldata\");\n\n let call: ReturnType<typeof decodeFunctionData<typeof TYCHO_ROUTER_ABI>>;\n try {\n call = decodeFunctionData({abi: TYCHO_ROUTER_ABI, data: tx.data as Hex});\n } catch {\n throw new FyndTransactionError(\"not a plain singleSwap / sequentialSwap / splitSwap call (Permit2 and vault variants are not used)\");\n }\n const a = call.args;\n const [amountIn, tokenIn, tokenOut, expectedAmountOut, minAmountOut] = a;\n const isSplit = call.functionName === \"splitSwap\";\n const receiver = (isSplit ? a[6] : a[5]) as string;\n const fee = (isSplit ? a[7] : a[6]) as {clientFeeBps: number; clientFeeReceiver: string; maxClientContribution: bigint; deadline: bigint; clientSignature: string};\n const swaps = (isSplit ? a[8] : a[7]) as string;\n if (amountIn !== expected.amountIn) throw new FyndTransactionError(`calldata spends ${amountIn}, not ${expected.amountIn}`);\n if (!sameAddress(tokenIn, routerToken(expected.tokenIn)) || !sameAddress(tokenOut, routerToken(expected.tokenOut))) {\n throw new FyndTransactionError(\"calldata swaps other tokens than requested\");\n }\n if (!sameAddress(receiver, expected.receiver)) throw new FyndTransactionError(`calldata pays ${receiver}, not ${expected.receiver}`);\n if (expectedAmountOut !== expected.expectedAmountOut || minAmountOut !== expected.minAmountOut) {\n throw new FyndTransactionError(\"calldata amounts do not match the quote\");\n }\n if (fee.clientFeeBps !== 0 || fee.maxClientContribution !== 0n || !sameAddress(fee.clientFeeReceiver, ZERO)) {\n throw new FyndTransactionError(\"calldata carries client-fee parameters — this module charges none\");\n }\n if (swaps === \"0x\" || (isSplit && (a[5] as bigint) < 2n)) throw new FyndTransactionError(\"empty or invalid route\");\n return {method: call.functionName};\n}\n\n/**\n * {@link checkFyndTransaction} on a remote quote, against the request this app sent: the pinned\n * router, the exact input, the requested tokens, the app's recipient, the quoted minimum and no\n * client fee. The reason it fails, or null.\n */\nexport function fyndVerifyQuote(params: QuoteParams, quote: Quote): string | null {\n try {\n const chainId = /^eip155:[0-9]+$/.test(params.sourceChain) ? Number(params.sourceChain.slice(7)) : NaN;\n const src = sourceAssetOf(params);\n if (!src) return `unknown source token ${params.sourceToken}`;\n const tx = quote.txRequest;\n if (!tx?.to || !tx.data) return \"the quote carries no router transaction\";\n checkFyndTransaction({to: tx.to, data: tx.data, value: BigInt(tx.value ?? \"0\")}, {\n chainId,\n tokenIn: src.contract ? getAddress(src.contract) : ZERO,\n tokenOut: /^0x0+$/.test(params.destToken) ? ZERO : getAddress(params.destToken),\n amountIn: BigInt(params.amountIn),\n receiver: getAddress(params.destAddress),\n expectedAmountOut: BigInt(quote.providerData?.gross ?? \"0\"),\n minAmountOut: BigInt(quote.minDestAmount),\n });\n return null;\n } catch (e) {\n return e instanceof Error ? e.message.replace(/^fynd: /, \"\") : String(e);\n }\n}\n\n/**\n * The Tycho router a Fynd swap calls and approves on a chain, and {@link fyndVerifyQuote} — the\n * app's side of `remoteProvider({name: \"fynd\", baseUrl, ...fyndPins})`, so a backend's quote is\n * checked against these routers and the app's own request, never against itself (scan 2026-10-27\n * modules, F9).\n */\nexport const fyndPins: RemotePins = Object.freeze({\n callTargets: (chain: string): readonly string[] => fyndRouterFor(chain),\n spenders: (chain: string): readonly string[] => fyndRouterFor(chain),\n verifyQuote: fyndVerifyQuote,\n});\nfunction fyndRouterFor(chain: string): readonly string[] {\n const id = /^eip155:[0-9]+$/.test(chain) ? Number(chain.slice(7)) : NaN;\n const c = Object.prototype.hasOwnProperty.call(FYND_CHAINS, id) ? FYND_CHAINS[id] : undefined;\n return c ? [c.router] : [];\n}\n"],"mappings":"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","names":["import_util"]}
|
|
1
|
+
{"version":3,"sources":["../src/activity.ts","../src/router.ts"],"sourcesContent":["/**\n * Activity recognition for Fynd: a swap entry point of the pinned\n * TychoRouterV3 on a chain the module serves → `swap`, \"Swapped A → B\" from\n * the owner's net transfers.\n */\nimport type {ActivityItem, ChainTx, DescribeActivityContext} from \"@anywalletkit/core\";\nimport {chainLabel, describeSwapLike, matchesCall} from \"@anywalletkit/protocol-swap2-core/activity\";\nimport {evmRef, isEvm} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {FYND_CHAINS, TYCHO_ROUTER_SELECTORS} from \"./router.js\";\n\nexport const FYND_MODULE = \"@anywalletkit/protocol-swap2-fynd\";\n\nexport function describeFyndActivity(tx: ChainTx, ctx: DescribeActivityContext): ActivityItem | null {\n if (!isEvm(tx.chain)) return null;\n const chain = FYND_CHAINS[evmRef(tx.chain)];\n if (!chain) return null;\n if (!matchesCall(tx, [chain.router], TYCHO_ROUTER_SELECTORS)) return null;\n return describeSwapLike(tx, ctx, {protocol: FYND_MODULE, subtitle: `Fynd · ${chainLabel(tx.chain)}`, kind: \"swap\"});\n}\n","/**\n * TychoRouterV3 — the only contract a Fynd quote may settle through (Fynd\n * License §3: every settlement of a Fynd output goes through the router in\n * Propeller Heads' registry). This file pins the router per chain and checks\n * Fynd's encoded transaction against the request before anything is approved\n * or sent: right contract, right method, the exact input, the requested\n * tokens, the user as receiver, the quoted output and minimum, and no client\n * fee. The route inside `swaps` is opaque and is not inspected here — the\n * router itself reverts below `minAmountOut`.\n */\nimport type {Quote, QuoteParams, RemotePins} from \"@anywalletkit/protocol-swap2-core/types\";\nimport {sourceAssetOf} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {decodeFunctionData, getAddress, parseAbi, toFunctionSelector, type Hex} from \"viem\";\n\n/** Router registry: github.com/propeller-heads/tycho crates/tycho-execution/config/deployment_registry.json (TychoRouterV3, effective 2026-09-01). */\nexport const FYND_CHAINS: Readonly<Record<number, {slug: string; name: string; router: `0x${string}`}>> = Object.freeze({\n 1: {slug: \"ethereum\", name: \"Ethereum\", router: \"0x1644D2477f809cc2C71bCCFd6Dc9497E3F83210d\"},\n 8453: {slug: \"base\", name: \"Base\", router: \"0xAbA5B53b03eAfaD1C5fc8BD5Fc765fC85Bb3de67\"},\n 42161: {slug: \"arbitrum\", name: \"Arbitrum\", router: \"0x924F147c50eA59f5180a26031A8b65B2aA1e81Cd\"},\n 56: {slug: \"bsc\", name: \"BNB Smart Chain\", router: \"0x7f3D12BBaFb8955E51B3ab9588b34c8ad95BDA4e\"},\n 137: {slug: \"polygon\", name: \"Polygon\", router: \"0xbd4e6011F03355C2A377Fd9Af939322A7d0A1bC1\"},\n 130: {slug: \"unichain\", name: \"Unichain\", router: \"0xCba5574597Ad00EA250Fd106DAB4Fc7461949635\"},\n 4663: {slug: \"robinhood\", name: \"Robinhood Chain\", router: \"0x09215a470bD585E59EB3F4B612fBD2678131fF9e\"},\n});\n\n/** The router's native-coin sentinel (lib/NativeETH.sol); Fynd's API uses the zero address instead. */\nexport const ROUTER_NATIVE = \"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE\";\nconst ZERO = \"0x0000000000000000000000000000000000000000\";\n\nconst PARAMS = \"uint256 amountIn, address tokenIn, address tokenOut, uint256 expectedAmountOut, uint256 minAmountOut\";\nconst FEE = \"(uint32 clientFeeBps, address clientFeeReceiver, uint256 maxClientContribution, uint256 deadline, bytes clientSignature) clientFeeParams\";\nconst PERMIT = \"((address token, uint160 amount, uint48 expiration, uint48 nonce) details, address spender, uint256 sigDeadline) permitSingle, bytes signature\";\n\n/** The three entry points a wallet swap uses (plain `transferFrom` from the user). */\nexport const TYCHO_ROUTER_ABI = parseAbi([\n `function singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData) payable returns (uint256)`,\n `function sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n `function splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n]);\n\n/** Every swap entry point of the router (Permit2 and vault variants included) — for recognizing history. */\nexport const TYCHO_ROUTER_SELECTORS: ReadonlySet<string> = new Set(\n [\n `singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwapUsingVault(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swapData)`,\n `sequentialSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n `splitSwapPermit2(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n ].map((sig) => toFunctionSelector(`function ${sig}`)),\n);\n\nexport class FyndTransactionError extends Error {\n constructor(message: string) {\n super(`fynd: ${message}`);\n this.name = \"FyndTransactionError\";\n }\n}\n\nexport interface ExpectedSwap {\n chainId: number;\n /** ERC-20 address, or the zero address for the native coin. */\n tokenIn: string;\n tokenOut: string;\n amountIn: bigint;\n receiver: string;\n /** Fynd's gross `amount_out` (before the router fee and slippage). */\n expectedAmountOut: bigint;\n /** Fynd's `min_amount_received` — what the router enforces on-chain. */\n minAmountOut: bigint;\n}\n\nexport interface EncodedTx {\n to: string;\n value: bigint;\n data: string;\n}\n\nconst sameAddress = (a: string, b: string) => {\n try {\n return getAddress(a) === getAddress(b);\n } catch {\n return false;\n }\n};\nconst routerToken = (t: string) => (sameAddress(t, ZERO) ? ROUTER_NATIVE : t);\n\n/** Refuses (throws {@link FyndTransactionError}) unless `tx` is exactly the swap the wallet asked for. */\nexport function checkFyndTransaction(tx: EncodedTx, expected: ExpectedSwap): {method: \"singleSwap\" | \"sequentialSwap\" | \"splitSwap\"} {\n const chain = FYND_CHAINS[expected.chainId];\n if (!chain) throw new FyndTransactionError(`chain ${expected.chainId} has no pinned Tycho router`);\n // `tx.to` is the API's: named in the refusal only when it is an address (scan 2026-10-27 modules, F34).\n if (!sameAddress(tx.to, chain.router)) throw new FyndTransactionError(`the transaction targets ${/^0x[0-9a-fA-F]{40}$/.test(tx.to) ? tx.to : \"something that is not an address\"}, not the Tycho router on ${chain.name}`);\n if (sameAddress(expected.receiver, ZERO) || sameAddress(expected.receiver, chain.router)) throw new FyndTransactionError(\"invalid receiver\");\n if (expected.amountIn <= 0n || expected.minAmountOut <= 0n || expected.minAmountOut > expected.expectedAmountOut) {\n throw new FyndTransactionError(\"invalid quote amounts\");\n }\n const nativeIn = sameAddress(expected.tokenIn, ZERO);\n if (tx.value !== (nativeIn ? expected.amountIn : 0n)) throw new FyndTransactionError(\"native value does not match the input\");\n if (!/^0x(?:[0-9a-fA-F]{2}){4,}$/.test(tx.data)) throw new FyndTransactionError(\"malformed calldata\");\n\n let call: ReturnType<typeof decodeFunctionData<typeof TYCHO_ROUTER_ABI>>;\n try {\n call = decodeFunctionData({abi: TYCHO_ROUTER_ABI, data: tx.data as Hex});\n } catch {\n throw new FyndTransactionError(\"not a plain singleSwap / sequentialSwap / splitSwap call (Permit2 and vault variants are not used)\");\n }\n const a = call.args;\n const [amountIn, tokenIn, tokenOut, expectedAmountOut, minAmountOut] = a;\n const isSplit = call.functionName === \"splitSwap\";\n const receiver = (isSplit ? a[6] : a[5]) as string;\n const fee = (isSplit ? a[7] : a[6]) as {clientFeeBps: number; clientFeeReceiver: string; maxClientContribution: bigint; deadline: bigint; clientSignature: string};\n const swaps = (isSplit ? a[8] : a[7]) as string;\n if (amountIn !== expected.amountIn) throw new FyndTransactionError(`calldata spends ${amountIn}, not ${expected.amountIn}`);\n if (!sameAddress(tokenIn, routerToken(expected.tokenIn)) || !sameAddress(tokenOut, routerToken(expected.tokenOut))) {\n throw new FyndTransactionError(\"calldata swaps other tokens than requested\");\n }\n if (!sameAddress(receiver, expected.receiver)) throw new FyndTransactionError(`calldata pays ${receiver}, not ${expected.receiver}`);\n if (expectedAmountOut !== expected.expectedAmountOut || minAmountOut !== expected.minAmountOut) {\n throw new FyndTransactionError(\"calldata amounts do not match the quote\");\n }\n if (fee.clientFeeBps !== 0 || fee.maxClientContribution !== 0n || !sameAddress(fee.clientFeeReceiver, ZERO)) {\n throw new FyndTransactionError(\"calldata carries client-fee parameters — this module charges none\");\n }\n if (swaps === \"0x\" || (isSplit && (a[5] as bigint) < 2n)) throw new FyndTransactionError(\"empty or invalid route\");\n return {method: call.functionName};\n}\n\n/**\n * {@link checkFyndTransaction} on a remote quote, against the request this app sent: the pinned\n * router, one chain, the exact input, the requested tokens, the app's recipient, the quoted minimum and\n * no client fee. The reason it fails, or null.\n */\nexport function fyndVerifyQuote(params: QuoteParams, quote: Quote): string | null {\n // Fynd swaps on one chain; a source-chain swap must not answer a cross-chain request (reaudit 2026-10-30, #3).\n if (params.sourceChain !== params.destChain) return \"same-chain swaps only\";\n try {\n const chainId = /^eip155:[0-9]+$/.test(params.sourceChain) ? Number(params.sourceChain.slice(7)) : NaN;\n const src = sourceAssetOf(params);\n if (!src) return `unknown source token ${params.sourceToken}`;\n const tx = quote.txRequest;\n if (!tx?.to || !tx.data) return \"the quote carries no router transaction\";\n checkFyndTransaction({to: tx.to, data: tx.data, value: BigInt(tx.value ?? \"0\")}, {\n chainId,\n tokenIn: src.contract ? getAddress(src.contract) : ZERO,\n tokenOut: /^0x0+$/.test(params.destToken) ? ZERO : getAddress(params.destToken),\n amountIn: BigInt(params.amountIn),\n receiver: getAddress(params.destAddress),\n expectedAmountOut: BigInt(quote.providerData?.gross ?? \"0\"),\n minAmountOut: BigInt(quote.minDestAmount),\n });\n return null;\n } catch (e) {\n return e instanceof Error ? e.message.replace(/^fynd: /, \"\") : String(e);\n }\n}\n\n/**\n * The Tycho router a Fynd swap calls and approves on a chain, and {@link fyndVerifyQuote} — the\n * app's side of `remoteProvider({name: \"fynd\", baseUrl, ...fyndPins})`, so a backend's quote is\n * checked against these routers and the app's own request, never against itself (scan 2026-10-27\n * modules, F9).\n */\nexport const fyndPins: RemotePins = Object.freeze({\n callTargets: (chain: string): readonly string[] => fyndRouterFor(chain),\n spenders: (chain: string): readonly string[] => fyndRouterFor(chain),\n verifyQuote: fyndVerifyQuote,\n});\nfunction fyndRouterFor(chain: string): readonly string[] {\n const id = /^eip155:[0-9]+$/.test(chain) ? Number(chain.slice(7)) : NaN;\n const c = Object.prototype.hasOwnProperty.call(FYND_CHAINS, id) ? FYND_CHAINS[id] : undefined;\n return c ? [c.router] : [];\n}\n"],"mappings":"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","names":["import_util"]}
|
package/dist/activity.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
describeFyndActivity
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-PF2NGOA5.js";
|
|
4
4
|
import {
|
|
5
5
|
FYND_CHAINS,
|
|
6
6
|
checkFyndTransaction
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-J6XM7WEG.js";
|
|
8
8
|
|
|
9
9
|
// src/provider.ts
|
|
10
10
|
import { getAddress } from "viem";
|
|
@@ -235,4 +235,4 @@ export {
|
|
|
235
235
|
FYND_DEFAULT_EXCLUDED_PROTOCOLS,
|
|
236
236
|
fyndProvider
|
|
237
237
|
};
|
|
238
|
-
//# sourceMappingURL=chunk-
|
|
238
|
+
//# sourceMappingURL=chunk-4BCEAXID.js.map
|
|
@@ -85,6 +85,7 @@ function checkFyndTransaction(tx, expected) {
|
|
|
85
85
|
return { method: call.functionName };
|
|
86
86
|
}
|
|
87
87
|
function fyndVerifyQuote(params, quote) {
|
|
88
|
+
if (params.sourceChain !== params.destChain) return "same-chain swaps only";
|
|
88
89
|
try {
|
|
89
90
|
const chainId = /^eip155:[0-9]+$/.test(params.sourceChain) ? Number(params.sourceChain.slice(7)) : NaN;
|
|
90
91
|
const src = sourceAssetOf(params);
|
|
@@ -126,4 +127,4 @@ export {
|
|
|
126
127
|
fyndVerifyQuote,
|
|
127
128
|
fyndPins
|
|
128
129
|
};
|
|
129
|
-
//# sourceMappingURL=chunk-
|
|
130
|
+
//# sourceMappingURL=chunk-J6XM7WEG.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/router.ts"],"sourcesContent":["/**\n * TychoRouterV3 — the only contract a Fynd quote may settle through (Fynd\n * License §3: every settlement of a Fynd output goes through the router in\n * Propeller Heads' registry). This file pins the router per chain and checks\n * Fynd's encoded transaction against the request before anything is approved\n * or sent: right contract, right method, the exact input, the requested\n * tokens, the user as receiver, the quoted output and minimum, and no client\n * fee. The route inside `swaps` is opaque and is not inspected here — the\n * router itself reverts below `minAmountOut`.\n */\nimport type {Quote, QuoteParams, RemotePins} from \"@anywalletkit/protocol-swap2-core/types\";\nimport {sourceAssetOf} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {decodeFunctionData, getAddress, parseAbi, toFunctionSelector, type Hex} from \"viem\";\n\n/** Router registry: github.com/propeller-heads/tycho crates/tycho-execution/config/deployment_registry.json (TychoRouterV3, effective 2026-09-01). */\nexport const FYND_CHAINS: Readonly<Record<number, {slug: string; name: string; router: `0x${string}`}>> = Object.freeze({\n 1: {slug: \"ethereum\", name: \"Ethereum\", router: \"0x1644D2477f809cc2C71bCCFd6Dc9497E3F83210d\"},\n 8453: {slug: \"base\", name: \"Base\", router: \"0xAbA5B53b03eAfaD1C5fc8BD5Fc765fC85Bb3de67\"},\n 42161: {slug: \"arbitrum\", name: \"Arbitrum\", router: \"0x924F147c50eA59f5180a26031A8b65B2aA1e81Cd\"},\n 56: {slug: \"bsc\", name: \"BNB Smart Chain\", router: \"0x7f3D12BBaFb8955E51B3ab9588b34c8ad95BDA4e\"},\n 137: {slug: \"polygon\", name: \"Polygon\", router: \"0xbd4e6011F03355C2A377Fd9Af939322A7d0A1bC1\"},\n 130: {slug: \"unichain\", name: \"Unichain\", router: \"0xCba5574597Ad00EA250Fd106DAB4Fc7461949635\"},\n 4663: {slug: \"robinhood\", name: \"Robinhood Chain\", router: \"0x09215a470bD585E59EB3F4B612fBD2678131fF9e\"},\n});\n\n/** The router's native-coin sentinel (lib/NativeETH.sol); Fynd's API uses the zero address instead. */\nexport const ROUTER_NATIVE = \"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE\";\nconst ZERO = \"0x0000000000000000000000000000000000000000\";\n\nconst PARAMS = \"uint256 amountIn, address tokenIn, address tokenOut, uint256 expectedAmountOut, uint256 minAmountOut\";\nconst FEE = \"(uint32 clientFeeBps, address clientFeeReceiver, uint256 maxClientContribution, uint256 deadline, bytes clientSignature) clientFeeParams\";\nconst PERMIT = \"((address token, uint160 amount, uint48 expiration, uint48 nonce) details, address spender, uint256 sigDeadline) permitSingle, bytes signature\";\n\n/** The three entry points a wallet swap uses (plain `transferFrom` from the user). */\nexport const TYCHO_ROUTER_ABI = parseAbi([\n `function singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData) payable returns (uint256)`,\n `function sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n `function splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n]);\n\n/** Every swap entry point of the router (Permit2 and vault variants included) — for recognizing history. */\nexport const TYCHO_ROUTER_SELECTORS: ReadonlySet<string> = new Set(\n [\n `singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwapUsingVault(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swapData)`,\n `sequentialSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n `splitSwapPermit2(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n ].map((sig) => toFunctionSelector(`function ${sig}`)),\n);\n\nexport class FyndTransactionError extends Error {\n constructor(message: string) {\n super(`fynd: ${message}`);\n this.name = \"FyndTransactionError\";\n }\n}\n\nexport interface ExpectedSwap {\n chainId: number;\n /** ERC-20 address, or the zero address for the native coin. */\n tokenIn: string;\n tokenOut: string;\n amountIn: bigint;\n receiver: string;\n /** Fynd's gross `amount_out` (before the router fee and slippage). */\n expectedAmountOut: bigint;\n /** Fynd's `min_amount_received` — what the router enforces on-chain. */\n minAmountOut: bigint;\n}\n\nexport interface EncodedTx {\n to: string;\n value: bigint;\n data: string;\n}\n\nconst sameAddress = (a: string, b: string) => {\n try {\n return getAddress(a) === getAddress(b);\n } catch {\n return false;\n }\n};\nconst routerToken = (t: string) => (sameAddress(t, ZERO) ? ROUTER_NATIVE : t);\n\n/** Refuses (throws {@link FyndTransactionError}) unless `tx` is exactly the swap the wallet asked for. */\nexport function checkFyndTransaction(tx: EncodedTx, expected: ExpectedSwap): {method: \"singleSwap\" | \"sequentialSwap\" | \"splitSwap\"} {\n const chain = FYND_CHAINS[expected.chainId];\n if (!chain) throw new FyndTransactionError(`chain ${expected.chainId} has no pinned Tycho router`);\n // `tx.to` is the API's: named in the refusal only when it is an address (scan 2026-10-27 modules, F34).\n if (!sameAddress(tx.to, chain.router)) throw new FyndTransactionError(`the transaction targets ${/^0x[0-9a-fA-F]{40}$/.test(tx.to) ? tx.to : \"something that is not an address\"}, not the Tycho router on ${chain.name}`);\n if (sameAddress(expected.receiver, ZERO) || sameAddress(expected.receiver, chain.router)) throw new FyndTransactionError(\"invalid receiver\");\n if (expected.amountIn <= 0n || expected.minAmountOut <= 0n || expected.minAmountOut > expected.expectedAmountOut) {\n throw new FyndTransactionError(\"invalid quote amounts\");\n }\n const nativeIn = sameAddress(expected.tokenIn, ZERO);\n if (tx.value !== (nativeIn ? expected.amountIn : 0n)) throw new FyndTransactionError(\"native value does not match the input\");\n if (!/^0x(?:[0-9a-fA-F]{2}){4,}$/.test(tx.data)) throw new FyndTransactionError(\"malformed calldata\");\n\n let call: ReturnType<typeof decodeFunctionData<typeof TYCHO_ROUTER_ABI>>;\n try {\n call = decodeFunctionData({abi: TYCHO_ROUTER_ABI, data: tx.data as Hex});\n } catch {\n throw new FyndTransactionError(\"not a plain singleSwap / sequentialSwap / splitSwap call (Permit2 and vault variants are not used)\");\n }\n const a = call.args;\n const [amountIn, tokenIn, tokenOut, expectedAmountOut, minAmountOut] = a;\n const isSplit = call.functionName === \"splitSwap\";\n const receiver = (isSplit ? a[6] : a[5]) as string;\n const fee = (isSplit ? a[7] : a[6]) as {clientFeeBps: number; clientFeeReceiver: string; maxClientContribution: bigint; deadline: bigint; clientSignature: string};\n const swaps = (isSplit ? a[8] : a[7]) as string;\n if (amountIn !== expected.amountIn) throw new FyndTransactionError(`calldata spends ${amountIn}, not ${expected.amountIn}`);\n if (!sameAddress(tokenIn, routerToken(expected.tokenIn)) || !sameAddress(tokenOut, routerToken(expected.tokenOut))) {\n throw new FyndTransactionError(\"calldata swaps other tokens than requested\");\n }\n if (!sameAddress(receiver, expected.receiver)) throw new FyndTransactionError(`calldata pays ${receiver}, not ${expected.receiver}`);\n if (expectedAmountOut !== expected.expectedAmountOut || minAmountOut !== expected.minAmountOut) {\n throw new FyndTransactionError(\"calldata amounts do not match the quote\");\n }\n if (fee.clientFeeBps !== 0 || fee.maxClientContribution !== 0n || !sameAddress(fee.clientFeeReceiver, ZERO)) {\n throw new FyndTransactionError(\"calldata carries client-fee parameters — this module charges none\");\n }\n if (swaps === \"0x\" || (isSplit && (a[5] as bigint) < 2n)) throw new FyndTransactionError(\"empty or invalid route\");\n return {method: call.functionName};\n}\n\n/**\n * {@link checkFyndTransaction} on a remote quote, against the request this app sent: the pinned\n * router, the exact input, the requested tokens, the app's recipient, the quoted minimum and no\n * client fee. The reason it fails, or null.\n */\nexport function fyndVerifyQuote(params: QuoteParams, quote: Quote): string | null {\n try {\n const chainId = /^eip155:[0-9]+$/.test(params.sourceChain) ? Number(params.sourceChain.slice(7)) : NaN;\n const src = sourceAssetOf(params);\n if (!src) return `unknown source token ${params.sourceToken}`;\n const tx = quote.txRequest;\n if (!tx?.to || !tx.data) return \"the quote carries no router transaction\";\n checkFyndTransaction({to: tx.to, data: tx.data, value: BigInt(tx.value ?? \"0\")}, {\n chainId,\n tokenIn: src.contract ? getAddress(src.contract) : ZERO,\n tokenOut: /^0x0+$/.test(params.destToken) ? ZERO : getAddress(params.destToken),\n amountIn: BigInt(params.amountIn),\n receiver: getAddress(params.destAddress),\n expectedAmountOut: BigInt(quote.providerData?.gross ?? \"0\"),\n minAmountOut: BigInt(quote.minDestAmount),\n });\n return null;\n } catch (e) {\n return e instanceof Error ? e.message.replace(/^fynd: /, \"\") : String(e);\n }\n}\n\n/**\n * The Tycho router a Fynd swap calls and approves on a chain, and {@link fyndVerifyQuote} — the\n * app's side of `remoteProvider({name: \"fynd\", baseUrl, ...fyndPins})`, so a backend's quote is\n * checked against these routers and the app's own request, never against itself (scan 2026-10-27\n * modules, F9).\n */\nexport const fyndPins: RemotePins = Object.freeze({\n callTargets: (chain: string): readonly string[] => fyndRouterFor(chain),\n spenders: (chain: string): readonly string[] => fyndRouterFor(chain),\n verifyQuote: fyndVerifyQuote,\n});\nfunction fyndRouterFor(chain: string): readonly string[] {\n const id = /^eip155:[0-9]+$/.test(chain) ? Number(chain.slice(7)) : NaN;\n const c = Object.prototype.hasOwnProperty.call(FYND_CHAINS, id) ? FYND_CHAINS[id] : undefined;\n return c ? [c.router] : [];\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/router.ts"],"sourcesContent":["/**\n * TychoRouterV3 — the only contract a Fynd quote may settle through (Fynd\n * License §3: every settlement of a Fynd output goes through the router in\n * Propeller Heads' registry). This file pins the router per chain and checks\n * Fynd's encoded transaction against the request before anything is approved\n * or sent: right contract, right method, the exact input, the requested\n * tokens, the user as receiver, the quoted output and minimum, and no client\n * fee. The route inside `swaps` is opaque and is not inspected here — the\n * router itself reverts below `minAmountOut`.\n */\nimport type {Quote, QuoteParams, RemotePins} from \"@anywalletkit/protocol-swap2-core/types\";\nimport {sourceAssetOf} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {decodeFunctionData, getAddress, parseAbi, toFunctionSelector, type Hex} from \"viem\";\n\n/** Router registry: github.com/propeller-heads/tycho crates/tycho-execution/config/deployment_registry.json (TychoRouterV3, effective 2026-09-01). */\nexport const FYND_CHAINS: Readonly<Record<number, {slug: string; name: string; router: `0x${string}`}>> = Object.freeze({\n 1: {slug: \"ethereum\", name: \"Ethereum\", router: \"0x1644D2477f809cc2C71bCCFd6Dc9497E3F83210d\"},\n 8453: {slug: \"base\", name: \"Base\", router: \"0xAbA5B53b03eAfaD1C5fc8BD5Fc765fC85Bb3de67\"},\n 42161: {slug: \"arbitrum\", name: \"Arbitrum\", router: \"0x924F147c50eA59f5180a26031A8b65B2aA1e81Cd\"},\n 56: {slug: \"bsc\", name: \"BNB Smart Chain\", router: \"0x7f3D12BBaFb8955E51B3ab9588b34c8ad95BDA4e\"},\n 137: {slug: \"polygon\", name: \"Polygon\", router: \"0xbd4e6011F03355C2A377Fd9Af939322A7d0A1bC1\"},\n 130: {slug: \"unichain\", name: \"Unichain\", router: \"0xCba5574597Ad00EA250Fd106DAB4Fc7461949635\"},\n 4663: {slug: \"robinhood\", name: \"Robinhood Chain\", router: \"0x09215a470bD585E59EB3F4B612fBD2678131fF9e\"},\n});\n\n/** The router's native-coin sentinel (lib/NativeETH.sol); Fynd's API uses the zero address instead. */\nexport const ROUTER_NATIVE = \"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE\";\nconst ZERO = \"0x0000000000000000000000000000000000000000\";\n\nconst PARAMS = \"uint256 amountIn, address tokenIn, address tokenOut, uint256 expectedAmountOut, uint256 minAmountOut\";\nconst FEE = \"(uint32 clientFeeBps, address clientFeeReceiver, uint256 maxClientContribution, uint256 deadline, bytes clientSignature) clientFeeParams\";\nconst PERMIT = \"((address token, uint160 amount, uint48 expiration, uint48 nonce) details, address spender, uint256 sigDeadline) permitSingle, bytes signature\";\n\n/** The three entry points a wallet swap uses (plain `transferFrom` from the user). */\nexport const TYCHO_ROUTER_ABI = parseAbi([\n `function singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData) payable returns (uint256)`,\n `function sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n `function splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n]);\n\n/** Every swap entry point of the router (Permit2 and vault variants included) — for recognizing history. */\nexport const TYCHO_ROUTER_SELECTORS: ReadonlySet<string> = new Set(\n [\n `singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwapUsingVault(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swapData)`,\n `sequentialSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n `splitSwapPermit2(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n ].map((sig) => toFunctionSelector(`function ${sig}`)),\n);\n\nexport class FyndTransactionError extends Error {\n constructor(message: string) {\n super(`fynd: ${message}`);\n this.name = \"FyndTransactionError\";\n }\n}\n\nexport interface ExpectedSwap {\n chainId: number;\n /** ERC-20 address, or the zero address for the native coin. */\n tokenIn: string;\n tokenOut: string;\n amountIn: bigint;\n receiver: string;\n /** Fynd's gross `amount_out` (before the router fee and slippage). */\n expectedAmountOut: bigint;\n /** Fynd's `min_amount_received` — what the router enforces on-chain. */\n minAmountOut: bigint;\n}\n\nexport interface EncodedTx {\n to: string;\n value: bigint;\n data: string;\n}\n\nconst sameAddress = (a: string, b: string) => {\n try {\n return getAddress(a) === getAddress(b);\n } catch {\n return false;\n }\n};\nconst routerToken = (t: string) => (sameAddress(t, ZERO) ? ROUTER_NATIVE : t);\n\n/** Refuses (throws {@link FyndTransactionError}) unless `tx` is exactly the swap the wallet asked for. */\nexport function checkFyndTransaction(tx: EncodedTx, expected: ExpectedSwap): {method: \"singleSwap\" | \"sequentialSwap\" | \"splitSwap\"} {\n const chain = FYND_CHAINS[expected.chainId];\n if (!chain) throw new FyndTransactionError(`chain ${expected.chainId} has no pinned Tycho router`);\n // `tx.to` is the API's: named in the refusal only when it is an address (scan 2026-10-27 modules, F34).\n if (!sameAddress(tx.to, chain.router)) throw new FyndTransactionError(`the transaction targets ${/^0x[0-9a-fA-F]{40}$/.test(tx.to) ? tx.to : \"something that is not an address\"}, not the Tycho router on ${chain.name}`);\n if (sameAddress(expected.receiver, ZERO) || sameAddress(expected.receiver, chain.router)) throw new FyndTransactionError(\"invalid receiver\");\n if (expected.amountIn <= 0n || expected.minAmountOut <= 0n || expected.minAmountOut > expected.expectedAmountOut) {\n throw new FyndTransactionError(\"invalid quote amounts\");\n }\n const nativeIn = sameAddress(expected.tokenIn, ZERO);\n if (tx.value !== (nativeIn ? expected.amountIn : 0n)) throw new FyndTransactionError(\"native value does not match the input\");\n if (!/^0x(?:[0-9a-fA-F]{2}){4,}$/.test(tx.data)) throw new FyndTransactionError(\"malformed calldata\");\n\n let call: ReturnType<typeof decodeFunctionData<typeof TYCHO_ROUTER_ABI>>;\n try {\n call = decodeFunctionData({abi: TYCHO_ROUTER_ABI, data: tx.data as Hex});\n } catch {\n throw new FyndTransactionError(\"not a plain singleSwap / sequentialSwap / splitSwap call (Permit2 and vault variants are not used)\");\n }\n const a = call.args;\n const [amountIn, tokenIn, tokenOut, expectedAmountOut, minAmountOut] = a;\n const isSplit = call.functionName === \"splitSwap\";\n const receiver = (isSplit ? a[6] : a[5]) as string;\n const fee = (isSplit ? a[7] : a[6]) as {clientFeeBps: number; clientFeeReceiver: string; maxClientContribution: bigint; deadline: bigint; clientSignature: string};\n const swaps = (isSplit ? a[8] : a[7]) as string;\n if (amountIn !== expected.amountIn) throw new FyndTransactionError(`calldata spends ${amountIn}, not ${expected.amountIn}`);\n if (!sameAddress(tokenIn, routerToken(expected.tokenIn)) || !sameAddress(tokenOut, routerToken(expected.tokenOut))) {\n throw new FyndTransactionError(\"calldata swaps other tokens than requested\");\n }\n if (!sameAddress(receiver, expected.receiver)) throw new FyndTransactionError(`calldata pays ${receiver}, not ${expected.receiver}`);\n if (expectedAmountOut !== expected.expectedAmountOut || minAmountOut !== expected.minAmountOut) {\n throw new FyndTransactionError(\"calldata amounts do not match the quote\");\n }\n if (fee.clientFeeBps !== 0 || fee.maxClientContribution !== 0n || !sameAddress(fee.clientFeeReceiver, ZERO)) {\n throw new FyndTransactionError(\"calldata carries client-fee parameters — this module charges none\");\n }\n if (swaps === \"0x\" || (isSplit && (a[5] as bigint) < 2n)) throw new FyndTransactionError(\"empty or invalid route\");\n return {method: call.functionName};\n}\n\n/**\n * {@link checkFyndTransaction} on a remote quote, against the request this app sent: the pinned\n * router, one chain, the exact input, the requested tokens, the app's recipient, the quoted minimum and\n * no client fee. The reason it fails, or null.\n */\nexport function fyndVerifyQuote(params: QuoteParams, quote: Quote): string | null {\n // Fynd swaps on one chain; a source-chain swap must not answer a cross-chain request (reaudit 2026-10-30, #3).\n if (params.sourceChain !== params.destChain) return \"same-chain swaps only\";\n try {\n const chainId = /^eip155:[0-9]+$/.test(params.sourceChain) ? Number(params.sourceChain.slice(7)) : NaN;\n const src = sourceAssetOf(params);\n if (!src) return `unknown source token ${params.sourceToken}`;\n const tx = quote.txRequest;\n if (!tx?.to || !tx.data) return \"the quote carries no router transaction\";\n checkFyndTransaction({to: tx.to, data: tx.data, value: BigInt(tx.value ?? \"0\")}, {\n chainId,\n tokenIn: src.contract ? getAddress(src.contract) : ZERO,\n tokenOut: /^0x0+$/.test(params.destToken) ? ZERO : getAddress(params.destToken),\n amountIn: BigInt(params.amountIn),\n receiver: getAddress(params.destAddress),\n expectedAmountOut: BigInt(quote.providerData?.gross ?? \"0\"),\n minAmountOut: BigInt(quote.minDestAmount),\n });\n return null;\n } catch (e) {\n return e instanceof Error ? e.message.replace(/^fynd: /, \"\") : String(e);\n }\n}\n\n/**\n * The Tycho router a Fynd swap calls and approves on a chain, and {@link fyndVerifyQuote} — the\n * app's side of `remoteProvider({name: \"fynd\", baseUrl, ...fyndPins})`, so a backend's quote is\n * checked against these routers and the app's own request, never against itself (scan 2026-10-27\n * modules, F9).\n */\nexport const fyndPins: RemotePins = Object.freeze({\n callTargets: (chain: string): readonly string[] => fyndRouterFor(chain),\n spenders: (chain: string): readonly string[] => fyndRouterFor(chain),\n verifyQuote: fyndVerifyQuote,\n});\nfunction fyndRouterFor(chain: string): readonly string[] {\n const id = /^eip155:[0-9]+$/.test(chain) ? Number(chain.slice(7)) : NaN;\n const c = Object.prototype.hasOwnProperty.call(FYND_CHAINS, id) ? FYND_CHAINS[id] : undefined;\n return c ? [c.router] : [];\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FYND_CHAINS,
|
|
3
3
|
TYCHO_ROUTER_SELECTORS
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-J6XM7WEG.js";
|
|
5
5
|
|
|
6
6
|
// src/activity.ts
|
|
7
7
|
import { chainLabel, describeSwapLike, matchesCall } from "@anywalletkit/protocol-swap2-core/activity";
|
|
@@ -19,4 +19,4 @@ export {
|
|
|
19
19
|
FYND_MODULE,
|
|
20
20
|
describeFyndActivity
|
|
21
21
|
};
|
|
22
|
-
//# sourceMappingURL=chunk-
|
|
22
|
+
//# sourceMappingURL=chunk-PF2NGOA5.js.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
fyndProvider
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-4BCEAXID.js";
|
|
4
4
|
import {
|
|
5
5
|
FYND_CHAINS
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-J6XM7WEG.js";
|
|
7
7
|
|
|
8
8
|
// src/protocol.ts
|
|
9
9
|
import { SingleProviderSwap2 } from "@anywalletkit/protocol-swap2-core/swap2/single";
|
|
@@ -29,4 +29,4 @@ var FyndSwap2Protocol = class extends SingleProviderSwap2 {
|
|
|
29
29
|
export {
|
|
30
30
|
FyndSwap2Protocol
|
|
31
31
|
};
|
|
32
|
-
//# sourceMappingURL=chunk-
|
|
32
|
+
//# sourceMappingURL=chunk-TMFP2W5C.js.map
|
package/dist/index.cjs
CHANGED
|
@@ -132,6 +132,7 @@ function checkFyndTransaction(tx, expected) {
|
|
|
132
132
|
return { method: call.functionName };
|
|
133
133
|
}
|
|
134
134
|
function fyndVerifyQuote(params, quote) {
|
|
135
|
+
if (params.sourceChain !== params.destChain) return "same-chain swaps only";
|
|
135
136
|
try {
|
|
136
137
|
const chainId = /^eip155:[0-9]+$/.test(params.sourceChain) ? Number(params.sourceChain.slice(7)) : NaN;
|
|
137
138
|
const src = (0, import_util.sourceAssetOf)(params);
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/provider.ts","../src/activity.ts","../src/router.ts","../src/protocol.ts"],"sourcesContent":["export * from \"./provider.js\";\nexport * from \"./router.js\";\nexport {FyndSwap2Protocol} from \"./protocol.js\";\nexport {FyndSwap2Protocol as default} from \"./protocol.js\";\nexport * from \"./activity.js\";\nexport type { FyndSwap2Config } from \"./protocol.js\"\n","import {getAddress} from \"viem\";\nimport {evmRef, isEvm, timeoutSignal, sourceAssetOf} from \"@anywalletkit/protocol-swap2-core/util\";\nimport type {Asset, CrossChainProvider, IntentRef, IntentStatus, Quote, QuoteParams, UnsignedTx} from \"@anywalletkit/protocol-swap2-core/types\";\nimport {describeFyndActivity} from \"./activity.js\";\nimport {FYND_CHAINS, checkFyndTransaction} from \"./router.js\";\n\n/**\n * Fynd — Propeller Heads' DEX aggregator (docs.fynd.xyz) as a same-chain\n * swap2 provider: exact-input swaps on Ethereum, Base, Arbitrum, BNB Chain,\n * Polygon, Unichain and Robinhood Chain, priced over the pools Tycho indexes\n * and settled through TychoRouterV3.\n *\n * Fynd outputs are licensed under the Fynd License: a swap built from a Fynd\n * quote must settle through the Tycho router with its fees intact — which is\n * the only transaction this provider builds (see README).\n *\n * Two backends: the hosted API (`apiKey` — keep it on a server, behind\n * `remoteProvider`) or your own `fynd serve` per chain (`selfHosted`).\n */\n\nconst ZERO = \"0x0000000000000000000000000000000000000000\";\nconst DEFAULT_BASE_URL = \"https://fynd-api.propellerheads.xyz\";\nconst DEFAULT_SLIPPAGE_BPS = 50;\n/** A Fynd quote is priced at one block; the router enforces the minimum, the engine re-checks this TTL before broadcast. */\nconst DEFAULT_TTL_MS = 30_000;\n/**\n * Liquidity families left out of routing by default. 2026-10-24 on the hosted\n * API: every Ethereum route through `uniswap_v4_hooks` was quoted ~0.6–1 %\n * above the market and reverted when simulated (4 of 4 native-in pairs,\n * also USDT → USDC), while the same pairs without it executed (8 of 8 across\n * Ethereum, Base and Arbitrum). Override with `excludeProtocols`.\n */\nexport const FYND_DEFAULT_EXCLUDED_PROTOCOLS: readonly string[] = Object.freeze([\"uniswap_v4_hooks\"]);\nconst QUOTE_FAILURES: Readonly<Record<string, string>> = {\n no_route_found: \"no route for this pair\",\n insufficient_liquidity: \"not enough liquidity for this amount\",\n timeout: \"the solver timed out\",\n not_ready: \"the solver is still syncing\",\n price_check_failed: \"the price guard rejected the route\",\n encoding_failed: \"could not encode the swap (fees + slippage leave nothing)\",\n};\n\nexport interface FyndProviderConfig {\n enabled?: boolean;\n /** Hosted API key (raw `Authorization` value). Server-side only. */\n apiKey?: string;\n /** Hosted API base URL; requests go to `{baseUrl}/v1/{chain}/quote`. */\n baseUrl?: string;\n /** Your own Fynd servers per EVM chain id; requests go to `{url}/v1/quote`, no key. Takes precedence over the hosted API for that chain. */\n selfHosted?: Readonly<Record<number, string>>;\n /** JSON-RPC endpoints per chain id: pre-broadcast simulation and receipts. Optional. */\n rpcUrls?: Readonly<Record<number, readonly string[]>>;\n defaultSlippageBps?: number;\n quoteTtlMs?: number;\n timeoutMs?: number;\n /** Protocol names or family prefixes ending in \":\" Fynd must not route through. Default {@link FYND_DEFAULT_EXCLUDED_PROTOCOLS}. */\n excludeProtocols?: readonly string[];\n}\n\ninterface FyndOrderQuote {\n status?: string;\n amount_in?: string;\n amount_out?: string;\n gas_estimate?: string;\n price_impact_bps?: number | null;\n route?: {swaps?: {protocol?: string}[]};\n fee_breakdown?: {router_fee?: string; client_fee?: string; max_slippage?: string; min_amount_received?: string};\n transaction?: {to?: string; value?: string; data?: string};\n}\n\nconst uint = (v: unknown): bigint | null => (typeof v === \"string\" && /^\\d+$/.test(v) ? BigInt(v) : null);\n/** A protocol name from the API as it may appear in a refusal or the quote: at most 40 characters (scan 2026-10-27 modules, F34). */\nconst protocolName = (p: string): string => (p.length > 40 ? `${p.slice(0, 40)}…` : p);\n/** Hex or an address, compared without case (length first: the client's side may be a megabyte). */\nconst sameText = (a: string, b: string): boolean => a.length === b.length && a.toLowerCase() === b.toLowerCase();\nconst sameAmount = (a: string, b: string): boolean => /^\\d{1,80}$/.test(a) && /^\\d{1,80}$/.test(b) && BigInt(a) === BigInt(b);\n\nexport function fyndProvider(config: FyndProviderConfig): CrossChainProvider {\n const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const slippageDefault = config.defaultSlippageBps ?? DEFAULT_SLIPPAGE_BPS;\n const ttl = config.quoteTtlMs ?? DEFAULT_TTL_MS;\n const timeoutMs = config.timeoutMs ?? 10_000;\n const excluded = [...(config.excludeProtocols ?? FYND_DEFAULT_EXCLUDED_PROTOCOLS)];\n const isExcluded = (protocol: string) => excluded.some((x) => (x.endsWith(\":\") ? protocol.startsWith(x) : protocol === x));\n\n const endpoint = (chainId: number): {url: string; headers: Record<string, string>} | null => {\n const chain = FYND_CHAINS[chainId];\n if (!chain) return null;\n const own = config.selfHosted?.[chainId];\n if (own) return {url: `${own.replace(/\\/+$/, \"\")}/v1/quote`, headers: {}};\n if (!config.apiKey) return null;\n return {url: `${baseUrl}/v1/${chain.slug}/quote`, headers: {authorization: config.apiKey}};\n };\n const chainIdOf = (caip2: string): number | null => {\n if (!isEvm(caip2)) return null;\n const id = evmRef(caip2);\n return endpoint(id) ? id : null;\n };\n\n async function rpc(chainId: number, method: string, params: unknown[]): Promise<unknown> {\n let lastErr: unknown;\n for (const url of config.rpcUrls?.[chainId] ?? []) {\n try {\n const res = await fetch(url, {method: \"POST\", headers: {\"content-type\": \"application/json\"}, body: JSON.stringify({jsonrpc: \"2.0\", id: 1, method, params}), signal: timeoutSignal(12_000)});\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n const body = (await res.json()) as {result?: unknown; error?: {message?: string}};\n if (body.error) throw Object.assign(new Error(body.error.message ?? \"rpc error\"), {rpc: true});\n return body.result ?? null;\n } catch (e) {\n if ((e as {rpc?: boolean}).rpc) throw e;\n lastErr = e;\n }\n }\n throw new Error(`fynd: no RPC answered on chain ${chainId} (${lastErr instanceof Error ? lastErr.message : String(lastErr)})`);\n }\n\n return {\n name: \"fynd\",\n describeActivity: describeFyndActivity,\n supportsSameChain: true,\n enabled: () => config.enabled ?? true,\n supportsChain: (caip2: string) => chainIdOf(caip2) !== null,\n supportsChainPair: (from: string, to: string) => from === to && chainIdOf(from) !== null,\n supportsRoute: (from: Asset, to: Asset) => from.chain === to.chain && chainIdOf(from.chain) !== null,\n\n async getQuote(params: QuoteParams, signal?: AbortSignal): Promise<Quote | null> {\n const refuse = (why: string): null => {\n params.onRefusal?.(`fynd: ${why}`);\n return null;\n };\n if (params.flex) return refuse(\"amount-flexible deposits are not supported\");\n if (params.sourceChain !== params.destChain) return refuse(\"same-chain swaps only\");\n const chainId = chainIdOf(params.sourceChain);\n if (chainId === null) return refuse(`Fynd is not configured for ${params.sourceChain}`);\n const ep = endpoint(chainId)!;\n const src = sourceAssetOf(params);\n if (!src) return refuse(`unknown source token ${params.sourceToken}`);\n const tokenIn = src.contract ? getAddress(src.contract) : ZERO;\n const nativeOut = /^0x0+$/.test(params.destToken);\n if (!nativeOut && !/^0x[0-9a-fA-F]{40}$/.test(params.destToken)) return refuse(\"destination token must be an EVM address\");\n const tokenOut = nativeOut ? ZERO : getAddress(params.destToken);\n if (tokenIn === tokenOut) return refuse(\"same token in and out\");\n if (!/^0x[0-9a-fA-F]{40}$/.test(params.destAddress)) return refuse(\"recipient must be an EVM address\");\n const receiver = getAddress(params.destAddress);\n const sender = getAddress(params.refundAddress && /^0x[0-9a-fA-F]{40}$/.test(params.refundAddress) ? params.refundAddress : params.destAddress);\n let amountIn: bigint;\n try {\n amountIn = BigInt(params.amountIn);\n } catch {\n return refuse(\"amount is not an integer\");\n }\n if (amountIn <= 0n) return refuse(\"amount must be positive\");\n const bps = params.slippageBps ?? slippageDefault;\n if (!Number.isInteger(bps) || bps <= 0 || bps >= 10_000) return refuse(\"invalid slippage\");\n\n const body = {\n orders: [{token_in: tokenIn, token_out: tokenOut, amount: amountIn.toString(), side: \"sell\", sender, receiver}],\n options: {\n timeout_ms: Math.min(timeoutMs, 8_000),\n encoding_options: {slippage: (bps / 10_000).toString(), transfer_type: \"transfer_from\"},\n ...(excluded.length ? {route_filter: {exclude_protocols: excluded}} : {}),\n },\n };\n let res: Response;\n try {\n res = await fetch(ep.url, {method: \"POST\", headers: {\"content-type\": \"application/json\", ...ep.headers}, body: JSON.stringify(body), signal: signal ?? timeoutSignal(timeoutMs)});\n } catch (e) {\n return refuse(`API unreachable (${e instanceof Error ? e.name : \"error\"})`);\n }\n if (res.status === 401 || res.status === 403) return refuse(`API key rejected (HTTP ${res.status})`);\n if (res.status === 429) return refuse(\"rate limited (HTTP 429)\");\n if (res.status === 422) return refuse(\"no route for this pair\");\n if (!res.ok) return refuse(`API answered HTTP ${res.status}`);\n let parsed: {orders?: FyndOrderQuote[]};\n try {\n parsed = (await res.json()) as {orders?: FyndOrderQuote[]};\n } catch {\n return refuse(\"malformed API response\");\n }\n if (!Array.isArray(parsed.orders) || parsed.orders.length !== 1) return refuse(\"malformed API response (order count)\");\n const q = parsed.orders[0]!;\n if (q.status !== \"success\") return refuse(QUOTE_FAILURES[q.status ?? \"\"] ?? `quote status ${String(q.status).slice(0, 40)}`);\n\n const quotedIn = uint(q.amount_in);\n const gross = uint(q.amount_out);\n const routerFee = uint(q.fee_breakdown?.router_fee);\n const clientFee = uint(q.fee_breakdown?.client_fee);\n const maxSlippage = uint(q.fee_breakdown?.max_slippage);\n const minOut = uint(q.fee_breakdown?.min_amount_received);\n const value = uint(q.transaction?.value ?? \"\");\n if (quotedIn === null || gross === null || routerFee === null || clientFee === null || maxSlippage === null || minOut === null || value === null) {\n return refuse(\"malformed quote amounts\");\n }\n if (quotedIn !== amountIn) return refuse(`quoted ${quotedIn}, not the requested ${amountIn}`);\n if (clientFee !== 0n) return refuse(\"the quote carries a client fee\");\n if (routerFee + clientFee + maxSlippage + minOut !== gross) return refuse(\"fee breakdown does not add up to the output\");\n if (params.minAmountOut !== undefined && minOut < BigInt(params.minAmountOut)) return refuse(`floor breach (min=${minOut} < floor=${params.minAmountOut})`);\n const tx = {to: q.transaction?.to ?? \"\", value, data: q.transaction?.data ?? \"\"};\n try {\n checkFyndTransaction(tx, {chainId, tokenIn, tokenOut, amountIn, receiver, expectedAmountOut: gross, minAmountOut: minOut});\n } catch (e) {\n return refuse(e instanceof Error ? e.message.replace(/^fynd: /, \"\") : String(e));\n }\n const allProtocols = [...new Set((q.route?.swaps ?? []).map((s) => s.protocol).filter((p): p is string => typeof p === \"string\"))];\n const banned = allProtocols.find(isExcluded);\n if (banned) return refuse(`the route goes through ${protocolName(banned)}, which this wallet excludes`);\n // Native input needs no allowance: the exact transaction can be executed\n // against the latest block right now, so a route that would revert never\n // enters the auction. (ERC-20 input is simulated in preflightDeposit,\n // once the approval exists.)\n if (tokenIn === ZERO && config.rpcUrls?.[chainId]?.length) {\n try {\n await rpc(chainId, \"eth_call\", [{from: sender, to: tx.to, data: tx.data, value: `0x${value.toString(16)}`}, \"latest\", {[sender]: {balance: `0x${(value + 10n ** 18n).toString(16)}`}}]);\n } catch (e) {\n if ((e as {rpc?: boolean}).rpc) return refuse(`the route does not execute on-chain right now (simulation reverted via ${allProtocols.slice(0, 6).map(protocolName).join(\", \") || \"?\"})`);\n // no RPC answered: the router still enforces the minimum\n }\n }\n const gas = uint(q.gas_estimate) ?? 0n;\n const protocols = allProtocols.slice(0, 6).map(protocolName);\n return {\n provider: \"fynd\",\n intentId: `fynd:${chainId}`,\n depositAddress: \"\",\n depositMemo: null,\n txRequest: {\n to: getAddress(tx.to),\n data: tx.data,\n value: value.toString(),\n chainId,\n // the router's own overhead on top of Fynd's route estimate\n gasLimit: ((gas * 13n) / 10n + 80_000n).toString(),\n ...(tokenIn === ZERO ? {} : {approvalAddress: getAddress(tx.to)}),\n },\n sourceAmount: amountIn.toString(),\n // what the receiver gets at the quoted price: gross minus the router fee\n destAmount: (gross - routerFee).toString(),\n minDestAmount: minOut.toString(),\n feeUsd: 0,\n etaSeconds: 15,\n reliability: 0.96,\n validUntilMs: Date.now() + ttl,\n providerData: {\n minOut: minOut.toString(),\n sender,\n routerFee: routerFee.toString(),\n gross: gross.toString(),\n protocols: protocols.join(\",\"),\n ...(typeof q.price_impact_bps === \"number\" ? {priceImpactBps: String(q.price_impact_bps)} : {}),\n },\n };\n },\n\n /**\n * Simulate the exact transaction at the latest block (allowance already in place); refuse when it would revert.\n * Only the quote's own transaction is simulated: behind a backend `tx` is the client's and only the quote is\n * sealed, so a client's call (any target, a megabyte of calldata) never reaches this RPC (scan 2026-10-27 modules, F24).\n */\n async preflightDeposit(quote: Quote, tx: UnsignedTx): Promise<void> {\n const chainId = Number(quote.intentId.split(\":\")[1]);\n const sender = quote.providerData?.sender;\n if (!FYND_CHAINS[chainId] || !config.rpcUrls?.[chainId]?.length || !tx.data || !sender) return;\n const quoted = quote.txRequest;\n if (!quoted?.to || !quoted.data || !sameText(tx.to, quoted.to) || !sameText(tx.data, quoted.data) || !sameAmount(tx.value || \"0\", quoted.value ?? \"0\")) {\n throw new Error(\"fynd: the transaction is not the quoted swap. Nothing was sent; refresh the quote.\");\n }\n try {\n await rpc(chainId, \"eth_call\", [{from: sender, to: quoted.to, data: quoted.data, value: `0x${BigInt(quoted.value ?? \"0\").toString(16)}`}, \"latest\"]);\n } catch (e) {\n if ((e as {rpc?: boolean}).rpc) {\n throw new Error(`fynd: the swap would revert now (${e instanceof Error ? e.message.slice(0, 120) : \"revert\"}) — the price moved past your minimum. Nothing was sent; refresh the quote.`);\n }\n // no RPC answered: leave it to the router's own minimum\n }\n },\n\n async buildDeposit(quote: Quote): Promise<UnsignedTx> {\n const tx = quote.txRequest;\n if (!tx?.to || !tx.data) throw new Error(\"swap2: Fynd quote has no transaction request\");\n return {chain: \"\", to: tx.to, value: tx.value ?? \"0\", data: tx.data, ...(tx.chainId !== undefined ? {chainId: tx.chainId} : {}), ...(tx.gasLimit ? {gasLimit: tx.gasLimit} : {}), ...(tx.approvalAddress ? {approvalAddress: tx.approvalAddress} : {})};\n },\n\n async trackIntent(ref: IntentRef): Promise<IntentStatus> {\n if (!ref.sourceTxHash) return {state: \"pending\"};\n const chainId = Number(ref.intentId.split(\":\")[1]);\n if (!FYND_CHAINS[chainId] || !config.rpcUrls?.[chainId]?.length) return {state: \"pending\"};\n try {\n const receipt = (await rpc(chainId, \"eth_getTransactionReceipt\", [ref.sourceTxHash])) as {status?: string} | null;\n if (!receipt) return {state: \"pending\"};\n return receipt.status === \"0x1\" ? {state: \"completed\", destTxHash: ref.sourceTxHash} : {state: \"failed\"};\n } catch {\n return {state: \"pending\"};\n }\n },\n };\n}\n","/**\n * Activity recognition for Fynd: a swap entry point of the pinned\n * TychoRouterV3 on a chain the module serves → `swap`, \"Swapped A → B\" from\n * the owner's net transfers.\n */\nimport type {ActivityItem, ChainTx, DescribeActivityContext} from \"@anywalletkit/core\";\nimport {chainLabel, describeSwapLike, matchesCall} from \"@anywalletkit/protocol-swap2-core/activity\";\nimport {evmRef, isEvm} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {FYND_CHAINS, TYCHO_ROUTER_SELECTORS} from \"./router.js\";\n\nexport const FYND_MODULE = \"@anywalletkit/protocol-swap2-fynd\";\n\nexport function describeFyndActivity(tx: ChainTx, ctx: DescribeActivityContext): ActivityItem | null {\n if (!isEvm(tx.chain)) return null;\n const chain = FYND_CHAINS[evmRef(tx.chain)];\n if (!chain) return null;\n if (!matchesCall(tx, [chain.router], TYCHO_ROUTER_SELECTORS)) return null;\n return describeSwapLike(tx, ctx, {protocol: FYND_MODULE, subtitle: `Fynd · ${chainLabel(tx.chain)}`, kind: \"swap\"});\n}\n","/**\n * TychoRouterV3 — the only contract a Fynd quote may settle through (Fynd\n * License §3: every settlement of a Fynd output goes through the router in\n * Propeller Heads' registry). This file pins the router per chain and checks\n * Fynd's encoded transaction against the request before anything is approved\n * or sent: right contract, right method, the exact input, the requested\n * tokens, the user as receiver, the quoted output and minimum, and no client\n * fee. The route inside `swaps` is opaque and is not inspected here — the\n * router itself reverts below `minAmountOut`.\n */\nimport type {Quote, QuoteParams, RemotePins} from \"@anywalletkit/protocol-swap2-core/types\";\nimport {sourceAssetOf} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {decodeFunctionData, getAddress, parseAbi, toFunctionSelector, type Hex} from \"viem\";\n\n/** Router registry: github.com/propeller-heads/tycho crates/tycho-execution/config/deployment_registry.json (TychoRouterV3, effective 2026-09-01). */\nexport const FYND_CHAINS: Readonly<Record<number, {slug: string; name: string; router: `0x${string}`}>> = Object.freeze({\n 1: {slug: \"ethereum\", name: \"Ethereum\", router: \"0x1644D2477f809cc2C71bCCFd6Dc9497E3F83210d\"},\n 8453: {slug: \"base\", name: \"Base\", router: \"0xAbA5B53b03eAfaD1C5fc8BD5Fc765fC85Bb3de67\"},\n 42161: {slug: \"arbitrum\", name: \"Arbitrum\", router: \"0x924F147c50eA59f5180a26031A8b65B2aA1e81Cd\"},\n 56: {slug: \"bsc\", name: \"BNB Smart Chain\", router: \"0x7f3D12BBaFb8955E51B3ab9588b34c8ad95BDA4e\"},\n 137: {slug: \"polygon\", name: \"Polygon\", router: \"0xbd4e6011F03355C2A377Fd9Af939322A7d0A1bC1\"},\n 130: {slug: \"unichain\", name: \"Unichain\", router: \"0xCba5574597Ad00EA250Fd106DAB4Fc7461949635\"},\n 4663: {slug: \"robinhood\", name: \"Robinhood Chain\", router: \"0x09215a470bD585E59EB3F4B612fBD2678131fF9e\"},\n});\n\n/** The router's native-coin sentinel (lib/NativeETH.sol); Fynd's API uses the zero address instead. */\nexport const ROUTER_NATIVE = \"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE\";\nconst ZERO = \"0x0000000000000000000000000000000000000000\";\n\nconst PARAMS = \"uint256 amountIn, address tokenIn, address tokenOut, uint256 expectedAmountOut, uint256 minAmountOut\";\nconst FEE = \"(uint32 clientFeeBps, address clientFeeReceiver, uint256 maxClientContribution, uint256 deadline, bytes clientSignature) clientFeeParams\";\nconst PERMIT = \"((address token, uint160 amount, uint48 expiration, uint48 nonce) details, address spender, uint256 sigDeadline) permitSingle, bytes signature\";\n\n/** The three entry points a wallet swap uses (plain `transferFrom` from the user). */\nexport const TYCHO_ROUTER_ABI = parseAbi([\n `function singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData) payable returns (uint256)`,\n `function sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n `function splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n]);\n\n/** Every swap entry point of the router (Permit2 and vault variants included) — for recognizing history. */\nexport const TYCHO_ROUTER_SELECTORS: ReadonlySet<string> = new Set(\n [\n `singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwapUsingVault(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swapData)`,\n `sequentialSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n `splitSwapPermit2(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n ].map((sig) => toFunctionSelector(`function ${sig}`)),\n);\n\nexport class FyndTransactionError extends Error {\n constructor(message: string) {\n super(`fynd: ${message}`);\n this.name = \"FyndTransactionError\";\n }\n}\n\nexport interface ExpectedSwap {\n chainId: number;\n /** ERC-20 address, or the zero address for the native coin. */\n tokenIn: string;\n tokenOut: string;\n amountIn: bigint;\n receiver: string;\n /** Fynd's gross `amount_out` (before the router fee and slippage). */\n expectedAmountOut: bigint;\n /** Fynd's `min_amount_received` — what the router enforces on-chain. */\n minAmountOut: bigint;\n}\n\nexport interface EncodedTx {\n to: string;\n value: bigint;\n data: string;\n}\n\nconst sameAddress = (a: string, b: string) => {\n try {\n return getAddress(a) === getAddress(b);\n } catch {\n return false;\n }\n};\nconst routerToken = (t: string) => (sameAddress(t, ZERO) ? ROUTER_NATIVE : t);\n\n/** Refuses (throws {@link FyndTransactionError}) unless `tx` is exactly the swap the wallet asked for. */\nexport function checkFyndTransaction(tx: EncodedTx, expected: ExpectedSwap): {method: \"singleSwap\" | \"sequentialSwap\" | \"splitSwap\"} {\n const chain = FYND_CHAINS[expected.chainId];\n if (!chain) throw new FyndTransactionError(`chain ${expected.chainId} has no pinned Tycho router`);\n // `tx.to` is the API's: named in the refusal only when it is an address (scan 2026-10-27 modules, F34).\n if (!sameAddress(tx.to, chain.router)) throw new FyndTransactionError(`the transaction targets ${/^0x[0-9a-fA-F]{40}$/.test(tx.to) ? tx.to : \"something that is not an address\"}, not the Tycho router on ${chain.name}`);\n if (sameAddress(expected.receiver, ZERO) || sameAddress(expected.receiver, chain.router)) throw new FyndTransactionError(\"invalid receiver\");\n if (expected.amountIn <= 0n || expected.minAmountOut <= 0n || expected.minAmountOut > expected.expectedAmountOut) {\n throw new FyndTransactionError(\"invalid quote amounts\");\n }\n const nativeIn = sameAddress(expected.tokenIn, ZERO);\n if (tx.value !== (nativeIn ? expected.amountIn : 0n)) throw new FyndTransactionError(\"native value does not match the input\");\n if (!/^0x(?:[0-9a-fA-F]{2}){4,}$/.test(tx.data)) throw new FyndTransactionError(\"malformed calldata\");\n\n let call: ReturnType<typeof decodeFunctionData<typeof TYCHO_ROUTER_ABI>>;\n try {\n call = decodeFunctionData({abi: TYCHO_ROUTER_ABI, data: tx.data as Hex});\n } catch {\n throw new FyndTransactionError(\"not a plain singleSwap / sequentialSwap / splitSwap call (Permit2 and vault variants are not used)\");\n }\n const a = call.args;\n const [amountIn, tokenIn, tokenOut, expectedAmountOut, minAmountOut] = a;\n const isSplit = call.functionName === \"splitSwap\";\n const receiver = (isSplit ? a[6] : a[5]) as string;\n const fee = (isSplit ? a[7] : a[6]) as {clientFeeBps: number; clientFeeReceiver: string; maxClientContribution: bigint; deadline: bigint; clientSignature: string};\n const swaps = (isSplit ? a[8] : a[7]) as string;\n if (amountIn !== expected.amountIn) throw new FyndTransactionError(`calldata spends ${amountIn}, not ${expected.amountIn}`);\n if (!sameAddress(tokenIn, routerToken(expected.tokenIn)) || !sameAddress(tokenOut, routerToken(expected.tokenOut))) {\n throw new FyndTransactionError(\"calldata swaps other tokens than requested\");\n }\n if (!sameAddress(receiver, expected.receiver)) throw new FyndTransactionError(`calldata pays ${receiver}, not ${expected.receiver}`);\n if (expectedAmountOut !== expected.expectedAmountOut || minAmountOut !== expected.minAmountOut) {\n throw new FyndTransactionError(\"calldata amounts do not match the quote\");\n }\n if (fee.clientFeeBps !== 0 || fee.maxClientContribution !== 0n || !sameAddress(fee.clientFeeReceiver, ZERO)) {\n throw new FyndTransactionError(\"calldata carries client-fee parameters — this module charges none\");\n }\n if (swaps === \"0x\" || (isSplit && (a[5] as bigint) < 2n)) throw new FyndTransactionError(\"empty or invalid route\");\n return {method: call.functionName};\n}\n\n/**\n * {@link checkFyndTransaction} on a remote quote, against the request this app sent: the pinned\n * router, the exact input, the requested tokens, the app's recipient, the quoted minimum and no\n * client fee. The reason it fails, or null.\n */\nexport function fyndVerifyQuote(params: QuoteParams, quote: Quote): string | null {\n try {\n const chainId = /^eip155:[0-9]+$/.test(params.sourceChain) ? Number(params.sourceChain.slice(7)) : NaN;\n const src = sourceAssetOf(params);\n if (!src) return `unknown source token ${params.sourceToken}`;\n const tx = quote.txRequest;\n if (!tx?.to || !tx.data) return \"the quote carries no router transaction\";\n checkFyndTransaction({to: tx.to, data: tx.data, value: BigInt(tx.value ?? \"0\")}, {\n chainId,\n tokenIn: src.contract ? getAddress(src.contract) : ZERO,\n tokenOut: /^0x0+$/.test(params.destToken) ? ZERO : getAddress(params.destToken),\n amountIn: BigInt(params.amountIn),\n receiver: getAddress(params.destAddress),\n expectedAmountOut: BigInt(quote.providerData?.gross ?? \"0\"),\n minAmountOut: BigInt(quote.minDestAmount),\n });\n return null;\n } catch (e) {\n return e instanceof Error ? e.message.replace(/^fynd: /, \"\") : String(e);\n }\n}\n\n/**\n * The Tycho router a Fynd swap calls and approves on a chain, and {@link fyndVerifyQuote} — the\n * app's side of `remoteProvider({name: \"fynd\", baseUrl, ...fyndPins})`, so a backend's quote is\n * checked against these routers and the app's own request, never against itself (scan 2026-10-27\n * modules, F9).\n */\nexport const fyndPins: RemotePins = Object.freeze({\n callTargets: (chain: string): readonly string[] => fyndRouterFor(chain),\n spenders: (chain: string): readonly string[] => fyndRouterFor(chain),\n verifyQuote: fyndVerifyQuote,\n});\nfunction fyndRouterFor(chain: string): readonly string[] {\n const id = /^eip155:[0-9]+$/.test(chain) ? Number(chain.slice(7)) : NaN;\n const c = Object.prototype.hasOwnProperty.call(FYND_CHAINS, id) ? FYND_CHAINS[id] : undefined;\n return c ? [c.router] : [];\n}\n","import {SingleProviderSwap2} from \"@anywalletkit/protocol-swap2-core/swap2/single\";\nimport type {Swap2Account, Swap2EngineConfig} from \"@anywalletkit/protocol-swap2-core/swap2/base\";\nimport {evmRef, isEvm} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {fyndProvider, type FyndProviderConfig} from \"./provider.js\";\nimport {FYND_CHAINS} from \"./router.js\";\n\nexport interface FyndSwap2Config extends Swap2EngineConfig, FyndProviderConfig {}\n\n/** Fynd as a standalone swap2 protocol (same-chain swaps on one account). */\nexport class FyndSwap2Protocol extends SingleProviderSwap2 {\n protected readonly provider;\n private readonly _fynd: FyndProviderConfig;\n constructor(account: Swap2Account | undefined, config: FyndSwap2Config) {\n super(account, config);\n this.provider = fyndProvider(config);\n this._fynd = config;\n }\n protected override chainSupported(caip2: string): boolean {\n if (!isEvm(caip2)) return false;\n const id = evmRef(caip2);\n return !!FYND_CHAINS[id] && (!!this._fynd.selfHosted?.[id] || !!this._fynd.apiKey);\n }\n protected override pairSupported(fromChain: string, toChain: string): boolean {\n return fromChain === toChain && this.chainSupported(fromChain);\n }\n}\n"],"mappings":"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","names":["import_viem","import_util","import_util","ZERO","import_util"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/provider.ts","../src/activity.ts","../src/router.ts","../src/protocol.ts"],"sourcesContent":["export * from \"./provider.js\";\nexport * from \"./router.js\";\nexport {FyndSwap2Protocol} from \"./protocol.js\";\nexport {FyndSwap2Protocol as default} from \"./protocol.js\";\nexport * from \"./activity.js\";\nexport type { FyndSwap2Config } from \"./protocol.js\"\n","import {getAddress} from \"viem\";\nimport {evmRef, isEvm, timeoutSignal, sourceAssetOf} from \"@anywalletkit/protocol-swap2-core/util\";\nimport type {Asset, CrossChainProvider, IntentRef, IntentStatus, Quote, QuoteParams, UnsignedTx} from \"@anywalletkit/protocol-swap2-core/types\";\nimport {describeFyndActivity} from \"./activity.js\";\nimport {FYND_CHAINS, checkFyndTransaction} from \"./router.js\";\n\n/**\n * Fynd — Propeller Heads' DEX aggregator (docs.fynd.xyz) as a same-chain\n * swap2 provider: exact-input swaps on Ethereum, Base, Arbitrum, BNB Chain,\n * Polygon, Unichain and Robinhood Chain, priced over the pools Tycho indexes\n * and settled through TychoRouterV3.\n *\n * Fynd outputs are licensed under the Fynd License: a swap built from a Fynd\n * quote must settle through the Tycho router with its fees intact — which is\n * the only transaction this provider builds (see README).\n *\n * Two backends: the hosted API (`apiKey` — keep it on a server, behind\n * `remoteProvider`) or your own `fynd serve` per chain (`selfHosted`).\n */\n\nconst ZERO = \"0x0000000000000000000000000000000000000000\";\nconst DEFAULT_BASE_URL = \"https://fynd-api.propellerheads.xyz\";\nconst DEFAULT_SLIPPAGE_BPS = 50;\n/** A Fynd quote is priced at one block; the router enforces the minimum, the engine re-checks this TTL before broadcast. */\nconst DEFAULT_TTL_MS = 30_000;\n/**\n * Liquidity families left out of routing by default. 2026-10-24 on the hosted\n * API: every Ethereum route through `uniswap_v4_hooks` was quoted ~0.6–1 %\n * above the market and reverted when simulated (4 of 4 native-in pairs,\n * also USDT → USDC), while the same pairs without it executed (8 of 8 across\n * Ethereum, Base and Arbitrum). Override with `excludeProtocols`.\n */\nexport const FYND_DEFAULT_EXCLUDED_PROTOCOLS: readonly string[] = Object.freeze([\"uniswap_v4_hooks\"]);\nconst QUOTE_FAILURES: Readonly<Record<string, string>> = {\n no_route_found: \"no route for this pair\",\n insufficient_liquidity: \"not enough liquidity for this amount\",\n timeout: \"the solver timed out\",\n not_ready: \"the solver is still syncing\",\n price_check_failed: \"the price guard rejected the route\",\n encoding_failed: \"could not encode the swap (fees + slippage leave nothing)\",\n};\n\nexport interface FyndProviderConfig {\n enabled?: boolean;\n /** Hosted API key (raw `Authorization` value). Server-side only. */\n apiKey?: string;\n /** Hosted API base URL; requests go to `{baseUrl}/v1/{chain}/quote`. */\n baseUrl?: string;\n /** Your own Fynd servers per EVM chain id; requests go to `{url}/v1/quote`, no key. Takes precedence over the hosted API for that chain. */\n selfHosted?: Readonly<Record<number, string>>;\n /** JSON-RPC endpoints per chain id: pre-broadcast simulation and receipts. Optional. */\n rpcUrls?: Readonly<Record<number, readonly string[]>>;\n defaultSlippageBps?: number;\n quoteTtlMs?: number;\n timeoutMs?: number;\n /** Protocol names or family prefixes ending in \":\" Fynd must not route through. Default {@link FYND_DEFAULT_EXCLUDED_PROTOCOLS}. */\n excludeProtocols?: readonly string[];\n}\n\ninterface FyndOrderQuote {\n status?: string;\n amount_in?: string;\n amount_out?: string;\n gas_estimate?: string;\n price_impact_bps?: number | null;\n route?: {swaps?: {protocol?: string}[]};\n fee_breakdown?: {router_fee?: string; client_fee?: string; max_slippage?: string; min_amount_received?: string};\n transaction?: {to?: string; value?: string; data?: string};\n}\n\nconst uint = (v: unknown): bigint | null => (typeof v === \"string\" && /^\\d+$/.test(v) ? BigInt(v) : null);\n/** A protocol name from the API as it may appear in a refusal or the quote: at most 40 characters (scan 2026-10-27 modules, F34). */\nconst protocolName = (p: string): string => (p.length > 40 ? `${p.slice(0, 40)}…` : p);\n/** Hex or an address, compared without case (length first: the client's side may be a megabyte). */\nconst sameText = (a: string, b: string): boolean => a.length === b.length && a.toLowerCase() === b.toLowerCase();\nconst sameAmount = (a: string, b: string): boolean => /^\\d{1,80}$/.test(a) && /^\\d{1,80}$/.test(b) && BigInt(a) === BigInt(b);\n\nexport function fyndProvider(config: FyndProviderConfig): CrossChainProvider {\n const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const slippageDefault = config.defaultSlippageBps ?? DEFAULT_SLIPPAGE_BPS;\n const ttl = config.quoteTtlMs ?? DEFAULT_TTL_MS;\n const timeoutMs = config.timeoutMs ?? 10_000;\n const excluded = [...(config.excludeProtocols ?? FYND_DEFAULT_EXCLUDED_PROTOCOLS)];\n const isExcluded = (protocol: string) => excluded.some((x) => (x.endsWith(\":\") ? protocol.startsWith(x) : protocol === x));\n\n const endpoint = (chainId: number): {url: string; headers: Record<string, string>} | null => {\n const chain = FYND_CHAINS[chainId];\n if (!chain) return null;\n const own = config.selfHosted?.[chainId];\n if (own) return {url: `${own.replace(/\\/+$/, \"\")}/v1/quote`, headers: {}};\n if (!config.apiKey) return null;\n return {url: `${baseUrl}/v1/${chain.slug}/quote`, headers: {authorization: config.apiKey}};\n };\n const chainIdOf = (caip2: string): number | null => {\n if (!isEvm(caip2)) return null;\n const id = evmRef(caip2);\n return endpoint(id) ? id : null;\n };\n\n async function rpc(chainId: number, method: string, params: unknown[]): Promise<unknown> {\n let lastErr: unknown;\n for (const url of config.rpcUrls?.[chainId] ?? []) {\n try {\n const res = await fetch(url, {method: \"POST\", headers: {\"content-type\": \"application/json\"}, body: JSON.stringify({jsonrpc: \"2.0\", id: 1, method, params}), signal: timeoutSignal(12_000)});\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n const body = (await res.json()) as {result?: unknown; error?: {message?: string}};\n if (body.error) throw Object.assign(new Error(body.error.message ?? \"rpc error\"), {rpc: true});\n return body.result ?? null;\n } catch (e) {\n if ((e as {rpc?: boolean}).rpc) throw e;\n lastErr = e;\n }\n }\n throw new Error(`fynd: no RPC answered on chain ${chainId} (${lastErr instanceof Error ? lastErr.message : String(lastErr)})`);\n }\n\n return {\n name: \"fynd\",\n describeActivity: describeFyndActivity,\n supportsSameChain: true,\n enabled: () => config.enabled ?? true,\n supportsChain: (caip2: string) => chainIdOf(caip2) !== null,\n supportsChainPair: (from: string, to: string) => from === to && chainIdOf(from) !== null,\n supportsRoute: (from: Asset, to: Asset) => from.chain === to.chain && chainIdOf(from.chain) !== null,\n\n async getQuote(params: QuoteParams, signal?: AbortSignal): Promise<Quote | null> {\n const refuse = (why: string): null => {\n params.onRefusal?.(`fynd: ${why}`);\n return null;\n };\n if (params.flex) return refuse(\"amount-flexible deposits are not supported\");\n if (params.sourceChain !== params.destChain) return refuse(\"same-chain swaps only\");\n const chainId = chainIdOf(params.sourceChain);\n if (chainId === null) return refuse(`Fynd is not configured for ${params.sourceChain}`);\n const ep = endpoint(chainId)!;\n const src = sourceAssetOf(params);\n if (!src) return refuse(`unknown source token ${params.sourceToken}`);\n const tokenIn = src.contract ? getAddress(src.contract) : ZERO;\n const nativeOut = /^0x0+$/.test(params.destToken);\n if (!nativeOut && !/^0x[0-9a-fA-F]{40}$/.test(params.destToken)) return refuse(\"destination token must be an EVM address\");\n const tokenOut = nativeOut ? ZERO : getAddress(params.destToken);\n if (tokenIn === tokenOut) return refuse(\"same token in and out\");\n if (!/^0x[0-9a-fA-F]{40}$/.test(params.destAddress)) return refuse(\"recipient must be an EVM address\");\n const receiver = getAddress(params.destAddress);\n const sender = getAddress(params.refundAddress && /^0x[0-9a-fA-F]{40}$/.test(params.refundAddress) ? params.refundAddress : params.destAddress);\n let amountIn: bigint;\n try {\n amountIn = BigInt(params.amountIn);\n } catch {\n return refuse(\"amount is not an integer\");\n }\n if (amountIn <= 0n) return refuse(\"amount must be positive\");\n const bps = params.slippageBps ?? slippageDefault;\n if (!Number.isInteger(bps) || bps <= 0 || bps >= 10_000) return refuse(\"invalid slippage\");\n\n const body = {\n orders: [{token_in: tokenIn, token_out: tokenOut, amount: amountIn.toString(), side: \"sell\", sender, receiver}],\n options: {\n timeout_ms: Math.min(timeoutMs, 8_000),\n encoding_options: {slippage: (bps / 10_000).toString(), transfer_type: \"transfer_from\"},\n ...(excluded.length ? {route_filter: {exclude_protocols: excluded}} : {}),\n },\n };\n let res: Response;\n try {\n res = await fetch(ep.url, {method: \"POST\", headers: {\"content-type\": \"application/json\", ...ep.headers}, body: JSON.stringify(body), signal: signal ?? timeoutSignal(timeoutMs)});\n } catch (e) {\n return refuse(`API unreachable (${e instanceof Error ? e.name : \"error\"})`);\n }\n if (res.status === 401 || res.status === 403) return refuse(`API key rejected (HTTP ${res.status})`);\n if (res.status === 429) return refuse(\"rate limited (HTTP 429)\");\n if (res.status === 422) return refuse(\"no route for this pair\");\n if (!res.ok) return refuse(`API answered HTTP ${res.status}`);\n let parsed: {orders?: FyndOrderQuote[]};\n try {\n parsed = (await res.json()) as {orders?: FyndOrderQuote[]};\n } catch {\n return refuse(\"malformed API response\");\n }\n if (!Array.isArray(parsed.orders) || parsed.orders.length !== 1) return refuse(\"malformed API response (order count)\");\n const q = parsed.orders[0]!;\n if (q.status !== \"success\") return refuse(QUOTE_FAILURES[q.status ?? \"\"] ?? `quote status ${String(q.status).slice(0, 40)}`);\n\n const quotedIn = uint(q.amount_in);\n const gross = uint(q.amount_out);\n const routerFee = uint(q.fee_breakdown?.router_fee);\n const clientFee = uint(q.fee_breakdown?.client_fee);\n const maxSlippage = uint(q.fee_breakdown?.max_slippage);\n const minOut = uint(q.fee_breakdown?.min_amount_received);\n const value = uint(q.transaction?.value ?? \"\");\n if (quotedIn === null || gross === null || routerFee === null || clientFee === null || maxSlippage === null || minOut === null || value === null) {\n return refuse(\"malformed quote amounts\");\n }\n if (quotedIn !== amountIn) return refuse(`quoted ${quotedIn}, not the requested ${amountIn}`);\n if (clientFee !== 0n) return refuse(\"the quote carries a client fee\");\n if (routerFee + clientFee + maxSlippage + minOut !== gross) return refuse(\"fee breakdown does not add up to the output\");\n if (params.minAmountOut !== undefined && minOut < BigInt(params.minAmountOut)) return refuse(`floor breach (min=${minOut} < floor=${params.minAmountOut})`);\n const tx = {to: q.transaction?.to ?? \"\", value, data: q.transaction?.data ?? \"\"};\n try {\n checkFyndTransaction(tx, {chainId, tokenIn, tokenOut, amountIn, receiver, expectedAmountOut: gross, minAmountOut: minOut});\n } catch (e) {\n return refuse(e instanceof Error ? e.message.replace(/^fynd: /, \"\") : String(e));\n }\n const allProtocols = [...new Set((q.route?.swaps ?? []).map((s) => s.protocol).filter((p): p is string => typeof p === \"string\"))];\n const banned = allProtocols.find(isExcluded);\n if (banned) return refuse(`the route goes through ${protocolName(banned)}, which this wallet excludes`);\n // Native input needs no allowance: the exact transaction can be executed\n // against the latest block right now, so a route that would revert never\n // enters the auction. (ERC-20 input is simulated in preflightDeposit,\n // once the approval exists.)\n if (tokenIn === ZERO && config.rpcUrls?.[chainId]?.length) {\n try {\n await rpc(chainId, \"eth_call\", [{from: sender, to: tx.to, data: tx.data, value: `0x${value.toString(16)}`}, \"latest\", {[sender]: {balance: `0x${(value + 10n ** 18n).toString(16)}`}}]);\n } catch (e) {\n if ((e as {rpc?: boolean}).rpc) return refuse(`the route does not execute on-chain right now (simulation reverted via ${allProtocols.slice(0, 6).map(protocolName).join(\", \") || \"?\"})`);\n // no RPC answered: the router still enforces the minimum\n }\n }\n const gas = uint(q.gas_estimate) ?? 0n;\n const protocols = allProtocols.slice(0, 6).map(protocolName);\n return {\n provider: \"fynd\",\n intentId: `fynd:${chainId}`,\n depositAddress: \"\",\n depositMemo: null,\n txRequest: {\n to: getAddress(tx.to),\n data: tx.data,\n value: value.toString(),\n chainId,\n // the router's own overhead on top of Fynd's route estimate\n gasLimit: ((gas * 13n) / 10n + 80_000n).toString(),\n ...(tokenIn === ZERO ? {} : {approvalAddress: getAddress(tx.to)}),\n },\n sourceAmount: amountIn.toString(),\n // what the receiver gets at the quoted price: gross minus the router fee\n destAmount: (gross - routerFee).toString(),\n minDestAmount: minOut.toString(),\n feeUsd: 0,\n etaSeconds: 15,\n reliability: 0.96,\n validUntilMs: Date.now() + ttl,\n providerData: {\n minOut: minOut.toString(),\n sender,\n routerFee: routerFee.toString(),\n gross: gross.toString(),\n protocols: protocols.join(\",\"),\n ...(typeof q.price_impact_bps === \"number\" ? {priceImpactBps: String(q.price_impact_bps)} : {}),\n },\n };\n },\n\n /**\n * Simulate the exact transaction at the latest block (allowance already in place); refuse when it would revert.\n * Only the quote's own transaction is simulated: behind a backend `tx` is the client's and only the quote is\n * sealed, so a client's call (any target, a megabyte of calldata) never reaches this RPC (scan 2026-10-27 modules, F24).\n */\n async preflightDeposit(quote: Quote, tx: UnsignedTx): Promise<void> {\n const chainId = Number(quote.intentId.split(\":\")[1]);\n const sender = quote.providerData?.sender;\n if (!FYND_CHAINS[chainId] || !config.rpcUrls?.[chainId]?.length || !tx.data || !sender) return;\n const quoted = quote.txRequest;\n if (!quoted?.to || !quoted.data || !sameText(tx.to, quoted.to) || !sameText(tx.data, quoted.data) || !sameAmount(tx.value || \"0\", quoted.value ?? \"0\")) {\n throw new Error(\"fynd: the transaction is not the quoted swap. Nothing was sent; refresh the quote.\");\n }\n try {\n await rpc(chainId, \"eth_call\", [{from: sender, to: quoted.to, data: quoted.data, value: `0x${BigInt(quoted.value ?? \"0\").toString(16)}`}, \"latest\"]);\n } catch (e) {\n if ((e as {rpc?: boolean}).rpc) {\n throw new Error(`fynd: the swap would revert now (${e instanceof Error ? e.message.slice(0, 120) : \"revert\"}) — the price moved past your minimum. Nothing was sent; refresh the quote.`);\n }\n // no RPC answered: leave it to the router's own minimum\n }\n },\n\n async buildDeposit(quote: Quote): Promise<UnsignedTx> {\n const tx = quote.txRequest;\n if (!tx?.to || !tx.data) throw new Error(\"swap2: Fynd quote has no transaction request\");\n return {chain: \"\", to: tx.to, value: tx.value ?? \"0\", data: tx.data, ...(tx.chainId !== undefined ? {chainId: tx.chainId} : {}), ...(tx.gasLimit ? {gasLimit: tx.gasLimit} : {}), ...(tx.approvalAddress ? {approvalAddress: tx.approvalAddress} : {})};\n },\n\n async trackIntent(ref: IntentRef): Promise<IntentStatus> {\n if (!ref.sourceTxHash) return {state: \"pending\"};\n const chainId = Number(ref.intentId.split(\":\")[1]);\n if (!FYND_CHAINS[chainId] || !config.rpcUrls?.[chainId]?.length) return {state: \"pending\"};\n try {\n const receipt = (await rpc(chainId, \"eth_getTransactionReceipt\", [ref.sourceTxHash])) as {status?: string} | null;\n if (!receipt) return {state: \"pending\"};\n return receipt.status === \"0x1\" ? {state: \"completed\", destTxHash: ref.sourceTxHash} : {state: \"failed\"};\n } catch {\n return {state: \"pending\"};\n }\n },\n };\n}\n","/**\n * Activity recognition for Fynd: a swap entry point of the pinned\n * TychoRouterV3 on a chain the module serves → `swap`, \"Swapped A → B\" from\n * the owner's net transfers.\n */\nimport type {ActivityItem, ChainTx, DescribeActivityContext} from \"@anywalletkit/core\";\nimport {chainLabel, describeSwapLike, matchesCall} from \"@anywalletkit/protocol-swap2-core/activity\";\nimport {evmRef, isEvm} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {FYND_CHAINS, TYCHO_ROUTER_SELECTORS} from \"./router.js\";\n\nexport const FYND_MODULE = \"@anywalletkit/protocol-swap2-fynd\";\n\nexport function describeFyndActivity(tx: ChainTx, ctx: DescribeActivityContext): ActivityItem | null {\n if (!isEvm(tx.chain)) return null;\n const chain = FYND_CHAINS[evmRef(tx.chain)];\n if (!chain) return null;\n if (!matchesCall(tx, [chain.router], TYCHO_ROUTER_SELECTORS)) return null;\n return describeSwapLike(tx, ctx, {protocol: FYND_MODULE, subtitle: `Fynd · ${chainLabel(tx.chain)}`, kind: \"swap\"});\n}\n","/**\n * TychoRouterV3 — the only contract a Fynd quote may settle through (Fynd\n * License §3: every settlement of a Fynd output goes through the router in\n * Propeller Heads' registry). This file pins the router per chain and checks\n * Fynd's encoded transaction against the request before anything is approved\n * or sent: right contract, right method, the exact input, the requested\n * tokens, the user as receiver, the quoted output and minimum, and no client\n * fee. The route inside `swaps` is opaque and is not inspected here — the\n * router itself reverts below `minAmountOut`.\n */\nimport type {Quote, QuoteParams, RemotePins} from \"@anywalletkit/protocol-swap2-core/types\";\nimport {sourceAssetOf} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {decodeFunctionData, getAddress, parseAbi, toFunctionSelector, type Hex} from \"viem\";\n\n/** Router registry: github.com/propeller-heads/tycho crates/tycho-execution/config/deployment_registry.json (TychoRouterV3, effective 2026-09-01). */\nexport const FYND_CHAINS: Readonly<Record<number, {slug: string; name: string; router: `0x${string}`}>> = Object.freeze({\n 1: {slug: \"ethereum\", name: \"Ethereum\", router: \"0x1644D2477f809cc2C71bCCFd6Dc9497E3F83210d\"},\n 8453: {slug: \"base\", name: \"Base\", router: \"0xAbA5B53b03eAfaD1C5fc8BD5Fc765fC85Bb3de67\"},\n 42161: {slug: \"arbitrum\", name: \"Arbitrum\", router: \"0x924F147c50eA59f5180a26031A8b65B2aA1e81Cd\"},\n 56: {slug: \"bsc\", name: \"BNB Smart Chain\", router: \"0x7f3D12BBaFb8955E51B3ab9588b34c8ad95BDA4e\"},\n 137: {slug: \"polygon\", name: \"Polygon\", router: \"0xbd4e6011F03355C2A377Fd9Af939322A7d0A1bC1\"},\n 130: {slug: \"unichain\", name: \"Unichain\", router: \"0xCba5574597Ad00EA250Fd106DAB4Fc7461949635\"},\n 4663: {slug: \"robinhood\", name: \"Robinhood Chain\", router: \"0x09215a470bD585E59EB3F4B612fBD2678131fF9e\"},\n});\n\n/** The router's native-coin sentinel (lib/NativeETH.sol); Fynd's API uses the zero address instead. */\nexport const ROUTER_NATIVE = \"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE\";\nconst ZERO = \"0x0000000000000000000000000000000000000000\";\n\nconst PARAMS = \"uint256 amountIn, address tokenIn, address tokenOut, uint256 expectedAmountOut, uint256 minAmountOut\";\nconst FEE = \"(uint32 clientFeeBps, address clientFeeReceiver, uint256 maxClientContribution, uint256 deadline, bytes clientSignature) clientFeeParams\";\nconst PERMIT = \"((address token, uint160 amount, uint48 expiration, uint48 nonce) details, address spender, uint256 sigDeadline) permitSingle, bytes signature\";\n\n/** The three entry points a wallet swap uses (plain `transferFrom` from the user). */\nexport const TYCHO_ROUTER_ABI = parseAbi([\n `function singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData) payable returns (uint256)`,\n `function sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n `function splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n]);\n\n/** Every swap entry point of the router (Permit2 and vault variants included) — for recognizing history. */\nexport const TYCHO_ROUTER_SELECTORS: ReadonlySet<string> = new Set(\n [\n `singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwapUsingVault(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swapData)`,\n `sequentialSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n `splitSwapPermit2(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n ].map((sig) => toFunctionSelector(`function ${sig}`)),\n);\n\nexport class FyndTransactionError extends Error {\n constructor(message: string) {\n super(`fynd: ${message}`);\n this.name = \"FyndTransactionError\";\n }\n}\n\nexport interface ExpectedSwap {\n chainId: number;\n /** ERC-20 address, or the zero address for the native coin. */\n tokenIn: string;\n tokenOut: string;\n amountIn: bigint;\n receiver: string;\n /** Fynd's gross `amount_out` (before the router fee and slippage). */\n expectedAmountOut: bigint;\n /** Fynd's `min_amount_received` — what the router enforces on-chain. */\n minAmountOut: bigint;\n}\n\nexport interface EncodedTx {\n to: string;\n value: bigint;\n data: string;\n}\n\nconst sameAddress = (a: string, b: string) => {\n try {\n return getAddress(a) === getAddress(b);\n } catch {\n return false;\n }\n};\nconst routerToken = (t: string) => (sameAddress(t, ZERO) ? ROUTER_NATIVE : t);\n\n/** Refuses (throws {@link FyndTransactionError}) unless `tx` is exactly the swap the wallet asked for. */\nexport function checkFyndTransaction(tx: EncodedTx, expected: ExpectedSwap): {method: \"singleSwap\" | \"sequentialSwap\" | \"splitSwap\"} {\n const chain = FYND_CHAINS[expected.chainId];\n if (!chain) throw new FyndTransactionError(`chain ${expected.chainId} has no pinned Tycho router`);\n // `tx.to` is the API's: named in the refusal only when it is an address (scan 2026-10-27 modules, F34).\n if (!sameAddress(tx.to, chain.router)) throw new FyndTransactionError(`the transaction targets ${/^0x[0-9a-fA-F]{40}$/.test(tx.to) ? tx.to : \"something that is not an address\"}, not the Tycho router on ${chain.name}`);\n if (sameAddress(expected.receiver, ZERO) || sameAddress(expected.receiver, chain.router)) throw new FyndTransactionError(\"invalid receiver\");\n if (expected.amountIn <= 0n || expected.minAmountOut <= 0n || expected.minAmountOut > expected.expectedAmountOut) {\n throw new FyndTransactionError(\"invalid quote amounts\");\n }\n const nativeIn = sameAddress(expected.tokenIn, ZERO);\n if (tx.value !== (nativeIn ? expected.amountIn : 0n)) throw new FyndTransactionError(\"native value does not match the input\");\n if (!/^0x(?:[0-9a-fA-F]{2}){4,}$/.test(tx.data)) throw new FyndTransactionError(\"malformed calldata\");\n\n let call: ReturnType<typeof decodeFunctionData<typeof TYCHO_ROUTER_ABI>>;\n try {\n call = decodeFunctionData({abi: TYCHO_ROUTER_ABI, data: tx.data as Hex});\n } catch {\n throw new FyndTransactionError(\"not a plain singleSwap / sequentialSwap / splitSwap call (Permit2 and vault variants are not used)\");\n }\n const a = call.args;\n const [amountIn, tokenIn, tokenOut, expectedAmountOut, minAmountOut] = a;\n const isSplit = call.functionName === \"splitSwap\";\n const receiver = (isSplit ? a[6] : a[5]) as string;\n const fee = (isSplit ? a[7] : a[6]) as {clientFeeBps: number; clientFeeReceiver: string; maxClientContribution: bigint; deadline: bigint; clientSignature: string};\n const swaps = (isSplit ? a[8] : a[7]) as string;\n if (amountIn !== expected.amountIn) throw new FyndTransactionError(`calldata spends ${amountIn}, not ${expected.amountIn}`);\n if (!sameAddress(tokenIn, routerToken(expected.tokenIn)) || !sameAddress(tokenOut, routerToken(expected.tokenOut))) {\n throw new FyndTransactionError(\"calldata swaps other tokens than requested\");\n }\n if (!sameAddress(receiver, expected.receiver)) throw new FyndTransactionError(`calldata pays ${receiver}, not ${expected.receiver}`);\n if (expectedAmountOut !== expected.expectedAmountOut || minAmountOut !== expected.minAmountOut) {\n throw new FyndTransactionError(\"calldata amounts do not match the quote\");\n }\n if (fee.clientFeeBps !== 0 || fee.maxClientContribution !== 0n || !sameAddress(fee.clientFeeReceiver, ZERO)) {\n throw new FyndTransactionError(\"calldata carries client-fee parameters — this module charges none\");\n }\n if (swaps === \"0x\" || (isSplit && (a[5] as bigint) < 2n)) throw new FyndTransactionError(\"empty or invalid route\");\n return {method: call.functionName};\n}\n\n/**\n * {@link checkFyndTransaction} on a remote quote, against the request this app sent: the pinned\n * router, one chain, the exact input, the requested tokens, the app's recipient, the quoted minimum and\n * no client fee. The reason it fails, or null.\n */\nexport function fyndVerifyQuote(params: QuoteParams, quote: Quote): string | null {\n // Fynd swaps on one chain; a source-chain swap must not answer a cross-chain request (reaudit 2026-10-30, #3).\n if (params.sourceChain !== params.destChain) return \"same-chain swaps only\";\n try {\n const chainId = /^eip155:[0-9]+$/.test(params.sourceChain) ? Number(params.sourceChain.slice(7)) : NaN;\n const src = sourceAssetOf(params);\n if (!src) return `unknown source token ${params.sourceToken}`;\n const tx = quote.txRequest;\n if (!tx?.to || !tx.data) return \"the quote carries no router transaction\";\n checkFyndTransaction({to: tx.to, data: tx.data, value: BigInt(tx.value ?? \"0\")}, {\n chainId,\n tokenIn: src.contract ? getAddress(src.contract) : ZERO,\n tokenOut: /^0x0+$/.test(params.destToken) ? ZERO : getAddress(params.destToken),\n amountIn: BigInt(params.amountIn),\n receiver: getAddress(params.destAddress),\n expectedAmountOut: BigInt(quote.providerData?.gross ?? \"0\"),\n minAmountOut: BigInt(quote.minDestAmount),\n });\n return null;\n } catch (e) {\n return e instanceof Error ? e.message.replace(/^fynd: /, \"\") : String(e);\n }\n}\n\n/**\n * The Tycho router a Fynd swap calls and approves on a chain, and {@link fyndVerifyQuote} — the\n * app's side of `remoteProvider({name: \"fynd\", baseUrl, ...fyndPins})`, so a backend's quote is\n * checked against these routers and the app's own request, never against itself (scan 2026-10-27\n * modules, F9).\n */\nexport const fyndPins: RemotePins = Object.freeze({\n callTargets: (chain: string): readonly string[] => fyndRouterFor(chain),\n spenders: (chain: string): readonly string[] => fyndRouterFor(chain),\n verifyQuote: fyndVerifyQuote,\n});\nfunction fyndRouterFor(chain: string): readonly string[] {\n const id = /^eip155:[0-9]+$/.test(chain) ? Number(chain.slice(7)) : NaN;\n const c = Object.prototype.hasOwnProperty.call(FYND_CHAINS, id) ? FYND_CHAINS[id] : undefined;\n return c ? [c.router] : [];\n}\n","import {SingleProviderSwap2} from \"@anywalletkit/protocol-swap2-core/swap2/single\";\nimport type {Swap2Account, Swap2EngineConfig} from \"@anywalletkit/protocol-swap2-core/swap2/base\";\nimport {evmRef, isEvm} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {fyndProvider, type FyndProviderConfig} from \"./provider.js\";\nimport {FYND_CHAINS} from \"./router.js\";\n\nexport interface FyndSwap2Config extends Swap2EngineConfig, FyndProviderConfig {}\n\n/** Fynd as a standalone swap2 protocol (same-chain swaps on one account). */\nexport class FyndSwap2Protocol extends SingleProviderSwap2 {\n protected readonly provider;\n private readonly _fynd: FyndProviderConfig;\n constructor(account: Swap2Account | undefined, config: FyndSwap2Config) {\n super(account, config);\n this.provider = fyndProvider(config);\n this._fynd = config;\n }\n protected override chainSupported(caip2: string): boolean {\n if (!isEvm(caip2)) return false;\n const id = evmRef(caip2);\n return !!FYND_CHAINS[id] && (!!this._fynd.selfHosted?.[id] || !!this._fynd.apiKey);\n }\n protected override pairSupported(fromChain: string, toChain: string): boolean {\n return fromChain === toChain && this.chainSupported(fromChain);\n }\n}\n"],"mappings":"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","names":["import_viem","import_util","import_util","ZERO","import_util"]}
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FyndSwap2Protocol
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-TMFP2W5C.js";
|
|
4
4
|
import {
|
|
5
5
|
FYND_DEFAULT_EXCLUDED_PROTOCOLS,
|
|
6
6
|
fyndProvider
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-4BCEAXID.js";
|
|
8
8
|
import {
|
|
9
9
|
FYND_MODULE,
|
|
10
10
|
describeFyndActivity
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-PF2NGOA5.js";
|
|
12
12
|
import {
|
|
13
13
|
FYND_CHAINS,
|
|
14
14
|
FyndTransactionError,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
checkFyndTransaction,
|
|
19
19
|
fyndPins,
|
|
20
20
|
fyndVerifyQuote
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-J6XM7WEG.js";
|
|
22
22
|
export {
|
|
23
23
|
FYND_CHAINS,
|
|
24
24
|
FYND_DEFAULT_EXCLUDED_PROTOCOLS,
|
package/dist/protocol.cjs
CHANGED
|
@@ -121,6 +121,7 @@ function checkFyndTransaction(tx, expected) {
|
|
|
121
121
|
return { method: call.functionName };
|
|
122
122
|
}
|
|
123
123
|
function fyndVerifyQuote(params, quote) {
|
|
124
|
+
if (params.sourceChain !== params.destChain) return "same-chain swaps only";
|
|
124
125
|
try {
|
|
125
126
|
const chainId = /^eip155:[0-9]+$/.test(params.sourceChain) ? Number(params.sourceChain.slice(7)) : NaN;
|
|
126
127
|
const src = (0, import_util.sourceAssetOf)(params);
|
package/dist/protocol.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/protocol.ts","../src/provider.ts","../src/activity.ts","../src/router.ts"],"sourcesContent":["import {SingleProviderSwap2} from \"@anywalletkit/protocol-swap2-core/swap2/single\";\nimport type {Swap2Account, Swap2EngineConfig} from \"@anywalletkit/protocol-swap2-core/swap2/base\";\nimport {evmRef, isEvm} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {fyndProvider, type FyndProviderConfig} from \"./provider.js\";\nimport {FYND_CHAINS} from \"./router.js\";\n\nexport interface FyndSwap2Config extends Swap2EngineConfig, FyndProviderConfig {}\n\n/** Fynd as a standalone swap2 protocol (same-chain swaps on one account). */\nexport class FyndSwap2Protocol extends SingleProviderSwap2 {\n protected readonly provider;\n private readonly _fynd: FyndProviderConfig;\n constructor(account: Swap2Account | undefined, config: FyndSwap2Config) {\n super(account, config);\n this.provider = fyndProvider(config);\n this._fynd = config;\n }\n protected override chainSupported(caip2: string): boolean {\n if (!isEvm(caip2)) return false;\n const id = evmRef(caip2);\n return !!FYND_CHAINS[id] && (!!this._fynd.selfHosted?.[id] || !!this._fynd.apiKey);\n }\n protected override pairSupported(fromChain: string, toChain: string): boolean {\n return fromChain === toChain && this.chainSupported(fromChain);\n }\n}\n","import {getAddress} from \"viem\";\nimport {evmRef, isEvm, timeoutSignal, sourceAssetOf} from \"@anywalletkit/protocol-swap2-core/util\";\nimport type {Asset, CrossChainProvider, IntentRef, IntentStatus, Quote, QuoteParams, UnsignedTx} from \"@anywalletkit/protocol-swap2-core/types\";\nimport {describeFyndActivity} from \"./activity.js\";\nimport {FYND_CHAINS, checkFyndTransaction} from \"./router.js\";\n\n/**\n * Fynd — Propeller Heads' DEX aggregator (docs.fynd.xyz) as a same-chain\n * swap2 provider: exact-input swaps on Ethereum, Base, Arbitrum, BNB Chain,\n * Polygon, Unichain and Robinhood Chain, priced over the pools Tycho indexes\n * and settled through TychoRouterV3.\n *\n * Fynd outputs are licensed under the Fynd License: a swap built from a Fynd\n * quote must settle through the Tycho router with its fees intact — which is\n * the only transaction this provider builds (see README).\n *\n * Two backends: the hosted API (`apiKey` — keep it on a server, behind\n * `remoteProvider`) or your own `fynd serve` per chain (`selfHosted`).\n */\n\nconst ZERO = \"0x0000000000000000000000000000000000000000\";\nconst DEFAULT_BASE_URL = \"https://fynd-api.propellerheads.xyz\";\nconst DEFAULT_SLIPPAGE_BPS = 50;\n/** A Fynd quote is priced at one block; the router enforces the minimum, the engine re-checks this TTL before broadcast. */\nconst DEFAULT_TTL_MS = 30_000;\n/**\n * Liquidity families left out of routing by default. 2026-10-24 on the hosted\n * API: every Ethereum route through `uniswap_v4_hooks` was quoted ~0.6–1 %\n * above the market and reverted when simulated (4 of 4 native-in pairs,\n * also USDT → USDC), while the same pairs without it executed (8 of 8 across\n * Ethereum, Base and Arbitrum). Override with `excludeProtocols`.\n */\nexport const FYND_DEFAULT_EXCLUDED_PROTOCOLS: readonly string[] = Object.freeze([\"uniswap_v4_hooks\"]);\nconst QUOTE_FAILURES: Readonly<Record<string, string>> = {\n no_route_found: \"no route for this pair\",\n insufficient_liquidity: \"not enough liquidity for this amount\",\n timeout: \"the solver timed out\",\n not_ready: \"the solver is still syncing\",\n price_check_failed: \"the price guard rejected the route\",\n encoding_failed: \"could not encode the swap (fees + slippage leave nothing)\",\n};\n\nexport interface FyndProviderConfig {\n enabled?: boolean;\n /** Hosted API key (raw `Authorization` value). Server-side only. */\n apiKey?: string;\n /** Hosted API base URL; requests go to `{baseUrl}/v1/{chain}/quote`. */\n baseUrl?: string;\n /** Your own Fynd servers per EVM chain id; requests go to `{url}/v1/quote`, no key. Takes precedence over the hosted API for that chain. */\n selfHosted?: Readonly<Record<number, string>>;\n /** JSON-RPC endpoints per chain id: pre-broadcast simulation and receipts. Optional. */\n rpcUrls?: Readonly<Record<number, readonly string[]>>;\n defaultSlippageBps?: number;\n quoteTtlMs?: number;\n timeoutMs?: number;\n /** Protocol names or family prefixes ending in \":\" Fynd must not route through. Default {@link FYND_DEFAULT_EXCLUDED_PROTOCOLS}. */\n excludeProtocols?: readonly string[];\n}\n\ninterface FyndOrderQuote {\n status?: string;\n amount_in?: string;\n amount_out?: string;\n gas_estimate?: string;\n price_impact_bps?: number | null;\n route?: {swaps?: {protocol?: string}[]};\n fee_breakdown?: {router_fee?: string; client_fee?: string; max_slippage?: string; min_amount_received?: string};\n transaction?: {to?: string; value?: string; data?: string};\n}\n\nconst uint = (v: unknown): bigint | null => (typeof v === \"string\" && /^\\d+$/.test(v) ? BigInt(v) : null);\n/** A protocol name from the API as it may appear in a refusal or the quote: at most 40 characters (scan 2026-10-27 modules, F34). */\nconst protocolName = (p: string): string => (p.length > 40 ? `${p.slice(0, 40)}…` : p);\n/** Hex or an address, compared without case (length first: the client's side may be a megabyte). */\nconst sameText = (a: string, b: string): boolean => a.length === b.length && a.toLowerCase() === b.toLowerCase();\nconst sameAmount = (a: string, b: string): boolean => /^\\d{1,80}$/.test(a) && /^\\d{1,80}$/.test(b) && BigInt(a) === BigInt(b);\n\nexport function fyndProvider(config: FyndProviderConfig): CrossChainProvider {\n const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const slippageDefault = config.defaultSlippageBps ?? DEFAULT_SLIPPAGE_BPS;\n const ttl = config.quoteTtlMs ?? DEFAULT_TTL_MS;\n const timeoutMs = config.timeoutMs ?? 10_000;\n const excluded = [...(config.excludeProtocols ?? FYND_DEFAULT_EXCLUDED_PROTOCOLS)];\n const isExcluded = (protocol: string) => excluded.some((x) => (x.endsWith(\":\") ? protocol.startsWith(x) : protocol === x));\n\n const endpoint = (chainId: number): {url: string; headers: Record<string, string>} | null => {\n const chain = FYND_CHAINS[chainId];\n if (!chain) return null;\n const own = config.selfHosted?.[chainId];\n if (own) return {url: `${own.replace(/\\/+$/, \"\")}/v1/quote`, headers: {}};\n if (!config.apiKey) return null;\n return {url: `${baseUrl}/v1/${chain.slug}/quote`, headers: {authorization: config.apiKey}};\n };\n const chainIdOf = (caip2: string): number | null => {\n if (!isEvm(caip2)) return null;\n const id = evmRef(caip2);\n return endpoint(id) ? id : null;\n };\n\n async function rpc(chainId: number, method: string, params: unknown[]): Promise<unknown> {\n let lastErr: unknown;\n for (const url of config.rpcUrls?.[chainId] ?? []) {\n try {\n const res = await fetch(url, {method: \"POST\", headers: {\"content-type\": \"application/json\"}, body: JSON.stringify({jsonrpc: \"2.0\", id: 1, method, params}), signal: timeoutSignal(12_000)});\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n const body = (await res.json()) as {result?: unknown; error?: {message?: string}};\n if (body.error) throw Object.assign(new Error(body.error.message ?? \"rpc error\"), {rpc: true});\n return body.result ?? null;\n } catch (e) {\n if ((e as {rpc?: boolean}).rpc) throw e;\n lastErr = e;\n }\n }\n throw new Error(`fynd: no RPC answered on chain ${chainId} (${lastErr instanceof Error ? lastErr.message : String(lastErr)})`);\n }\n\n return {\n name: \"fynd\",\n describeActivity: describeFyndActivity,\n supportsSameChain: true,\n enabled: () => config.enabled ?? true,\n supportsChain: (caip2: string) => chainIdOf(caip2) !== null,\n supportsChainPair: (from: string, to: string) => from === to && chainIdOf(from) !== null,\n supportsRoute: (from: Asset, to: Asset) => from.chain === to.chain && chainIdOf(from.chain) !== null,\n\n async getQuote(params: QuoteParams, signal?: AbortSignal): Promise<Quote | null> {\n const refuse = (why: string): null => {\n params.onRefusal?.(`fynd: ${why}`);\n return null;\n };\n if (params.flex) return refuse(\"amount-flexible deposits are not supported\");\n if (params.sourceChain !== params.destChain) return refuse(\"same-chain swaps only\");\n const chainId = chainIdOf(params.sourceChain);\n if (chainId === null) return refuse(`Fynd is not configured for ${params.sourceChain}`);\n const ep = endpoint(chainId)!;\n const src = sourceAssetOf(params);\n if (!src) return refuse(`unknown source token ${params.sourceToken}`);\n const tokenIn = src.contract ? getAddress(src.contract) : ZERO;\n const nativeOut = /^0x0+$/.test(params.destToken);\n if (!nativeOut && !/^0x[0-9a-fA-F]{40}$/.test(params.destToken)) return refuse(\"destination token must be an EVM address\");\n const tokenOut = nativeOut ? ZERO : getAddress(params.destToken);\n if (tokenIn === tokenOut) return refuse(\"same token in and out\");\n if (!/^0x[0-9a-fA-F]{40}$/.test(params.destAddress)) return refuse(\"recipient must be an EVM address\");\n const receiver = getAddress(params.destAddress);\n const sender = getAddress(params.refundAddress && /^0x[0-9a-fA-F]{40}$/.test(params.refundAddress) ? params.refundAddress : params.destAddress);\n let amountIn: bigint;\n try {\n amountIn = BigInt(params.amountIn);\n } catch {\n return refuse(\"amount is not an integer\");\n }\n if (amountIn <= 0n) return refuse(\"amount must be positive\");\n const bps = params.slippageBps ?? slippageDefault;\n if (!Number.isInteger(bps) || bps <= 0 || bps >= 10_000) return refuse(\"invalid slippage\");\n\n const body = {\n orders: [{token_in: tokenIn, token_out: tokenOut, amount: amountIn.toString(), side: \"sell\", sender, receiver}],\n options: {\n timeout_ms: Math.min(timeoutMs, 8_000),\n encoding_options: {slippage: (bps / 10_000).toString(), transfer_type: \"transfer_from\"},\n ...(excluded.length ? {route_filter: {exclude_protocols: excluded}} : {}),\n },\n };\n let res: Response;\n try {\n res = await fetch(ep.url, {method: \"POST\", headers: {\"content-type\": \"application/json\", ...ep.headers}, body: JSON.stringify(body), signal: signal ?? timeoutSignal(timeoutMs)});\n } catch (e) {\n return refuse(`API unreachable (${e instanceof Error ? e.name : \"error\"})`);\n }\n if (res.status === 401 || res.status === 403) return refuse(`API key rejected (HTTP ${res.status})`);\n if (res.status === 429) return refuse(\"rate limited (HTTP 429)\");\n if (res.status === 422) return refuse(\"no route for this pair\");\n if (!res.ok) return refuse(`API answered HTTP ${res.status}`);\n let parsed: {orders?: FyndOrderQuote[]};\n try {\n parsed = (await res.json()) as {orders?: FyndOrderQuote[]};\n } catch {\n return refuse(\"malformed API response\");\n }\n if (!Array.isArray(parsed.orders) || parsed.orders.length !== 1) return refuse(\"malformed API response (order count)\");\n const q = parsed.orders[0]!;\n if (q.status !== \"success\") return refuse(QUOTE_FAILURES[q.status ?? \"\"] ?? `quote status ${String(q.status).slice(0, 40)}`);\n\n const quotedIn = uint(q.amount_in);\n const gross = uint(q.amount_out);\n const routerFee = uint(q.fee_breakdown?.router_fee);\n const clientFee = uint(q.fee_breakdown?.client_fee);\n const maxSlippage = uint(q.fee_breakdown?.max_slippage);\n const minOut = uint(q.fee_breakdown?.min_amount_received);\n const value = uint(q.transaction?.value ?? \"\");\n if (quotedIn === null || gross === null || routerFee === null || clientFee === null || maxSlippage === null || minOut === null || value === null) {\n return refuse(\"malformed quote amounts\");\n }\n if (quotedIn !== amountIn) return refuse(`quoted ${quotedIn}, not the requested ${amountIn}`);\n if (clientFee !== 0n) return refuse(\"the quote carries a client fee\");\n if (routerFee + clientFee + maxSlippage + minOut !== gross) return refuse(\"fee breakdown does not add up to the output\");\n if (params.minAmountOut !== undefined && minOut < BigInt(params.minAmountOut)) return refuse(`floor breach (min=${minOut} < floor=${params.minAmountOut})`);\n const tx = {to: q.transaction?.to ?? \"\", value, data: q.transaction?.data ?? \"\"};\n try {\n checkFyndTransaction(tx, {chainId, tokenIn, tokenOut, amountIn, receiver, expectedAmountOut: gross, minAmountOut: minOut});\n } catch (e) {\n return refuse(e instanceof Error ? e.message.replace(/^fynd: /, \"\") : String(e));\n }\n const allProtocols = [...new Set((q.route?.swaps ?? []).map((s) => s.protocol).filter((p): p is string => typeof p === \"string\"))];\n const banned = allProtocols.find(isExcluded);\n if (banned) return refuse(`the route goes through ${protocolName(banned)}, which this wallet excludes`);\n // Native input needs no allowance: the exact transaction can be executed\n // against the latest block right now, so a route that would revert never\n // enters the auction. (ERC-20 input is simulated in preflightDeposit,\n // once the approval exists.)\n if (tokenIn === ZERO && config.rpcUrls?.[chainId]?.length) {\n try {\n await rpc(chainId, \"eth_call\", [{from: sender, to: tx.to, data: tx.data, value: `0x${value.toString(16)}`}, \"latest\", {[sender]: {balance: `0x${(value + 10n ** 18n).toString(16)}`}}]);\n } catch (e) {\n if ((e as {rpc?: boolean}).rpc) return refuse(`the route does not execute on-chain right now (simulation reverted via ${allProtocols.slice(0, 6).map(protocolName).join(\", \") || \"?\"})`);\n // no RPC answered: the router still enforces the minimum\n }\n }\n const gas = uint(q.gas_estimate) ?? 0n;\n const protocols = allProtocols.slice(0, 6).map(protocolName);\n return {\n provider: \"fynd\",\n intentId: `fynd:${chainId}`,\n depositAddress: \"\",\n depositMemo: null,\n txRequest: {\n to: getAddress(tx.to),\n data: tx.data,\n value: value.toString(),\n chainId,\n // the router's own overhead on top of Fynd's route estimate\n gasLimit: ((gas * 13n) / 10n + 80_000n).toString(),\n ...(tokenIn === ZERO ? {} : {approvalAddress: getAddress(tx.to)}),\n },\n sourceAmount: amountIn.toString(),\n // what the receiver gets at the quoted price: gross minus the router fee\n destAmount: (gross - routerFee).toString(),\n minDestAmount: minOut.toString(),\n feeUsd: 0,\n etaSeconds: 15,\n reliability: 0.96,\n validUntilMs: Date.now() + ttl,\n providerData: {\n minOut: minOut.toString(),\n sender,\n routerFee: routerFee.toString(),\n gross: gross.toString(),\n protocols: protocols.join(\",\"),\n ...(typeof q.price_impact_bps === \"number\" ? {priceImpactBps: String(q.price_impact_bps)} : {}),\n },\n };\n },\n\n /**\n * Simulate the exact transaction at the latest block (allowance already in place); refuse when it would revert.\n * Only the quote's own transaction is simulated: behind a backend `tx` is the client's and only the quote is\n * sealed, so a client's call (any target, a megabyte of calldata) never reaches this RPC (scan 2026-10-27 modules, F24).\n */\n async preflightDeposit(quote: Quote, tx: UnsignedTx): Promise<void> {\n const chainId = Number(quote.intentId.split(\":\")[1]);\n const sender = quote.providerData?.sender;\n if (!FYND_CHAINS[chainId] || !config.rpcUrls?.[chainId]?.length || !tx.data || !sender) return;\n const quoted = quote.txRequest;\n if (!quoted?.to || !quoted.data || !sameText(tx.to, quoted.to) || !sameText(tx.data, quoted.data) || !sameAmount(tx.value || \"0\", quoted.value ?? \"0\")) {\n throw new Error(\"fynd: the transaction is not the quoted swap. Nothing was sent; refresh the quote.\");\n }\n try {\n await rpc(chainId, \"eth_call\", [{from: sender, to: quoted.to, data: quoted.data, value: `0x${BigInt(quoted.value ?? \"0\").toString(16)}`}, \"latest\"]);\n } catch (e) {\n if ((e as {rpc?: boolean}).rpc) {\n throw new Error(`fynd: the swap would revert now (${e instanceof Error ? e.message.slice(0, 120) : \"revert\"}) — the price moved past your minimum. Nothing was sent; refresh the quote.`);\n }\n // no RPC answered: leave it to the router's own minimum\n }\n },\n\n async buildDeposit(quote: Quote): Promise<UnsignedTx> {\n const tx = quote.txRequest;\n if (!tx?.to || !tx.data) throw new Error(\"swap2: Fynd quote has no transaction request\");\n return {chain: \"\", to: tx.to, value: tx.value ?? \"0\", data: tx.data, ...(tx.chainId !== undefined ? {chainId: tx.chainId} : {}), ...(tx.gasLimit ? {gasLimit: tx.gasLimit} : {}), ...(tx.approvalAddress ? {approvalAddress: tx.approvalAddress} : {})};\n },\n\n async trackIntent(ref: IntentRef): Promise<IntentStatus> {\n if (!ref.sourceTxHash) return {state: \"pending\"};\n const chainId = Number(ref.intentId.split(\":\")[1]);\n if (!FYND_CHAINS[chainId] || !config.rpcUrls?.[chainId]?.length) return {state: \"pending\"};\n try {\n const receipt = (await rpc(chainId, \"eth_getTransactionReceipt\", [ref.sourceTxHash])) as {status?: string} | null;\n if (!receipt) return {state: \"pending\"};\n return receipt.status === \"0x1\" ? {state: \"completed\", destTxHash: ref.sourceTxHash} : {state: \"failed\"};\n } catch {\n return {state: \"pending\"};\n }\n },\n };\n}\n","/**\n * Activity recognition for Fynd: a swap entry point of the pinned\n * TychoRouterV3 on a chain the module serves → `swap`, \"Swapped A → B\" from\n * the owner's net transfers.\n */\nimport type {ActivityItem, ChainTx, DescribeActivityContext} from \"@anywalletkit/core\";\nimport {chainLabel, describeSwapLike, matchesCall} from \"@anywalletkit/protocol-swap2-core/activity\";\nimport {evmRef, isEvm} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {FYND_CHAINS, TYCHO_ROUTER_SELECTORS} from \"./router.js\";\n\nexport const FYND_MODULE = \"@anywalletkit/protocol-swap2-fynd\";\n\nexport function describeFyndActivity(tx: ChainTx, ctx: DescribeActivityContext): ActivityItem | null {\n if (!isEvm(tx.chain)) return null;\n const chain = FYND_CHAINS[evmRef(tx.chain)];\n if (!chain) return null;\n if (!matchesCall(tx, [chain.router], TYCHO_ROUTER_SELECTORS)) return null;\n return describeSwapLike(tx, ctx, {protocol: FYND_MODULE, subtitle: `Fynd · ${chainLabel(tx.chain)}`, kind: \"swap\"});\n}\n","/**\n * TychoRouterV3 — the only contract a Fynd quote may settle through (Fynd\n * License §3: every settlement of a Fynd output goes through the router in\n * Propeller Heads' registry). This file pins the router per chain and checks\n * Fynd's encoded transaction against the request before anything is approved\n * or sent: right contract, right method, the exact input, the requested\n * tokens, the user as receiver, the quoted output and minimum, and no client\n * fee. The route inside `swaps` is opaque and is not inspected here — the\n * router itself reverts below `minAmountOut`.\n */\nimport type {Quote, QuoteParams, RemotePins} from \"@anywalletkit/protocol-swap2-core/types\";\nimport {sourceAssetOf} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {decodeFunctionData, getAddress, parseAbi, toFunctionSelector, type Hex} from \"viem\";\n\n/** Router registry: github.com/propeller-heads/tycho crates/tycho-execution/config/deployment_registry.json (TychoRouterV3, effective 2026-09-01). */\nexport const FYND_CHAINS: Readonly<Record<number, {slug: string; name: string; router: `0x${string}`}>> = Object.freeze({\n 1: {slug: \"ethereum\", name: \"Ethereum\", router: \"0x1644D2477f809cc2C71bCCFd6Dc9497E3F83210d\"},\n 8453: {slug: \"base\", name: \"Base\", router: \"0xAbA5B53b03eAfaD1C5fc8BD5Fc765fC85Bb3de67\"},\n 42161: {slug: \"arbitrum\", name: \"Arbitrum\", router: \"0x924F147c50eA59f5180a26031A8b65B2aA1e81Cd\"},\n 56: {slug: \"bsc\", name: \"BNB Smart Chain\", router: \"0x7f3D12BBaFb8955E51B3ab9588b34c8ad95BDA4e\"},\n 137: {slug: \"polygon\", name: \"Polygon\", router: \"0xbd4e6011F03355C2A377Fd9Af939322A7d0A1bC1\"},\n 130: {slug: \"unichain\", name: \"Unichain\", router: \"0xCba5574597Ad00EA250Fd106DAB4Fc7461949635\"},\n 4663: {slug: \"robinhood\", name: \"Robinhood Chain\", router: \"0x09215a470bD585E59EB3F4B612fBD2678131fF9e\"},\n});\n\n/** The router's native-coin sentinel (lib/NativeETH.sol); Fynd's API uses the zero address instead. */\nexport const ROUTER_NATIVE = \"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE\";\nconst ZERO = \"0x0000000000000000000000000000000000000000\";\n\nconst PARAMS = \"uint256 amountIn, address tokenIn, address tokenOut, uint256 expectedAmountOut, uint256 minAmountOut\";\nconst FEE = \"(uint32 clientFeeBps, address clientFeeReceiver, uint256 maxClientContribution, uint256 deadline, bytes clientSignature) clientFeeParams\";\nconst PERMIT = \"((address token, uint160 amount, uint48 expiration, uint48 nonce) details, address spender, uint256 sigDeadline) permitSingle, bytes signature\";\n\n/** The three entry points a wallet swap uses (plain `transferFrom` from the user). */\nexport const TYCHO_ROUTER_ABI = parseAbi([\n `function singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData) payable returns (uint256)`,\n `function sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n `function splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n]);\n\n/** Every swap entry point of the router (Permit2 and vault variants included) — for recognizing history. */\nexport const TYCHO_ROUTER_SELECTORS: ReadonlySet<string> = new Set(\n [\n `singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwapUsingVault(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swapData)`,\n `sequentialSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n `splitSwapPermit2(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n ].map((sig) => toFunctionSelector(`function ${sig}`)),\n);\n\nexport class FyndTransactionError extends Error {\n constructor(message: string) {\n super(`fynd: ${message}`);\n this.name = \"FyndTransactionError\";\n }\n}\n\nexport interface ExpectedSwap {\n chainId: number;\n /** ERC-20 address, or the zero address for the native coin. */\n tokenIn: string;\n tokenOut: string;\n amountIn: bigint;\n receiver: string;\n /** Fynd's gross `amount_out` (before the router fee and slippage). */\n expectedAmountOut: bigint;\n /** Fynd's `min_amount_received` — what the router enforces on-chain. */\n minAmountOut: bigint;\n}\n\nexport interface EncodedTx {\n to: string;\n value: bigint;\n data: string;\n}\n\nconst sameAddress = (a: string, b: string) => {\n try {\n return getAddress(a) === getAddress(b);\n } catch {\n return false;\n }\n};\nconst routerToken = (t: string) => (sameAddress(t, ZERO) ? ROUTER_NATIVE : t);\n\n/** Refuses (throws {@link FyndTransactionError}) unless `tx` is exactly the swap the wallet asked for. */\nexport function checkFyndTransaction(tx: EncodedTx, expected: ExpectedSwap): {method: \"singleSwap\" | \"sequentialSwap\" | \"splitSwap\"} {\n const chain = FYND_CHAINS[expected.chainId];\n if (!chain) throw new FyndTransactionError(`chain ${expected.chainId} has no pinned Tycho router`);\n // `tx.to` is the API's: named in the refusal only when it is an address (scan 2026-10-27 modules, F34).\n if (!sameAddress(tx.to, chain.router)) throw new FyndTransactionError(`the transaction targets ${/^0x[0-9a-fA-F]{40}$/.test(tx.to) ? tx.to : \"something that is not an address\"}, not the Tycho router on ${chain.name}`);\n if (sameAddress(expected.receiver, ZERO) || sameAddress(expected.receiver, chain.router)) throw new FyndTransactionError(\"invalid receiver\");\n if (expected.amountIn <= 0n || expected.minAmountOut <= 0n || expected.minAmountOut > expected.expectedAmountOut) {\n throw new FyndTransactionError(\"invalid quote amounts\");\n }\n const nativeIn = sameAddress(expected.tokenIn, ZERO);\n if (tx.value !== (nativeIn ? expected.amountIn : 0n)) throw new FyndTransactionError(\"native value does not match the input\");\n if (!/^0x(?:[0-9a-fA-F]{2}){4,}$/.test(tx.data)) throw new FyndTransactionError(\"malformed calldata\");\n\n let call: ReturnType<typeof decodeFunctionData<typeof TYCHO_ROUTER_ABI>>;\n try {\n call = decodeFunctionData({abi: TYCHO_ROUTER_ABI, data: tx.data as Hex});\n } catch {\n throw new FyndTransactionError(\"not a plain singleSwap / sequentialSwap / splitSwap call (Permit2 and vault variants are not used)\");\n }\n const a = call.args;\n const [amountIn, tokenIn, tokenOut, expectedAmountOut, minAmountOut] = a;\n const isSplit = call.functionName === \"splitSwap\";\n const receiver = (isSplit ? a[6] : a[5]) as string;\n const fee = (isSplit ? a[7] : a[6]) as {clientFeeBps: number; clientFeeReceiver: string; maxClientContribution: bigint; deadline: bigint; clientSignature: string};\n const swaps = (isSplit ? a[8] : a[7]) as string;\n if (amountIn !== expected.amountIn) throw new FyndTransactionError(`calldata spends ${amountIn}, not ${expected.amountIn}`);\n if (!sameAddress(tokenIn, routerToken(expected.tokenIn)) || !sameAddress(tokenOut, routerToken(expected.tokenOut))) {\n throw new FyndTransactionError(\"calldata swaps other tokens than requested\");\n }\n if (!sameAddress(receiver, expected.receiver)) throw new FyndTransactionError(`calldata pays ${receiver}, not ${expected.receiver}`);\n if (expectedAmountOut !== expected.expectedAmountOut || minAmountOut !== expected.minAmountOut) {\n throw new FyndTransactionError(\"calldata amounts do not match the quote\");\n }\n if (fee.clientFeeBps !== 0 || fee.maxClientContribution !== 0n || !sameAddress(fee.clientFeeReceiver, ZERO)) {\n throw new FyndTransactionError(\"calldata carries client-fee parameters — this module charges none\");\n }\n if (swaps === \"0x\" || (isSplit && (a[5] as bigint) < 2n)) throw new FyndTransactionError(\"empty or invalid route\");\n return {method: call.functionName};\n}\n\n/**\n * {@link checkFyndTransaction} on a remote quote, against the request this app sent: the pinned\n * router, the exact input, the requested tokens, the app's recipient, the quoted minimum and no\n * client fee. The reason it fails, or null.\n */\nexport function fyndVerifyQuote(params: QuoteParams, quote: Quote): string | null {\n try {\n const chainId = /^eip155:[0-9]+$/.test(params.sourceChain) ? Number(params.sourceChain.slice(7)) : NaN;\n const src = sourceAssetOf(params);\n if (!src) return `unknown source token ${params.sourceToken}`;\n const tx = quote.txRequest;\n if (!tx?.to || !tx.data) return \"the quote carries no router transaction\";\n checkFyndTransaction({to: tx.to, data: tx.data, value: BigInt(tx.value ?? \"0\")}, {\n chainId,\n tokenIn: src.contract ? getAddress(src.contract) : ZERO,\n tokenOut: /^0x0+$/.test(params.destToken) ? ZERO : getAddress(params.destToken),\n amountIn: BigInt(params.amountIn),\n receiver: getAddress(params.destAddress),\n expectedAmountOut: BigInt(quote.providerData?.gross ?? \"0\"),\n minAmountOut: BigInt(quote.minDestAmount),\n });\n return null;\n } catch (e) {\n return e instanceof Error ? e.message.replace(/^fynd: /, \"\") : String(e);\n }\n}\n\n/**\n * The Tycho router a Fynd swap calls and approves on a chain, and {@link fyndVerifyQuote} — the\n * app's side of `remoteProvider({name: \"fynd\", baseUrl, ...fyndPins})`, so a backend's quote is\n * checked against these routers and the app's own request, never against itself (scan 2026-10-27\n * modules, F9).\n */\nexport const fyndPins: RemotePins = Object.freeze({\n callTargets: (chain: string): readonly string[] => fyndRouterFor(chain),\n spenders: (chain: string): readonly string[] => fyndRouterFor(chain),\n verifyQuote: fyndVerifyQuote,\n});\nfunction fyndRouterFor(chain: string): readonly string[] {\n const id = /^eip155:[0-9]+$/.test(chain) ? Number(chain.slice(7)) : NaN;\n const c = Object.prototype.hasOwnProperty.call(FYND_CHAINS, id) ? FYND_CHAINS[id] : undefined;\n return c ? [c.router] : [];\n}\n"],"mappings":"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","names":["import_util","import_viem","import_util","import_util","ZERO"]}
|
|
1
|
+
{"version":3,"sources":["../src/protocol.ts","../src/provider.ts","../src/activity.ts","../src/router.ts"],"sourcesContent":["import {SingleProviderSwap2} from \"@anywalletkit/protocol-swap2-core/swap2/single\";\nimport type {Swap2Account, Swap2EngineConfig} from \"@anywalletkit/protocol-swap2-core/swap2/base\";\nimport {evmRef, isEvm} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {fyndProvider, type FyndProviderConfig} from \"./provider.js\";\nimport {FYND_CHAINS} from \"./router.js\";\n\nexport interface FyndSwap2Config extends Swap2EngineConfig, FyndProviderConfig {}\n\n/** Fynd as a standalone swap2 protocol (same-chain swaps on one account). */\nexport class FyndSwap2Protocol extends SingleProviderSwap2 {\n protected readonly provider;\n private readonly _fynd: FyndProviderConfig;\n constructor(account: Swap2Account | undefined, config: FyndSwap2Config) {\n super(account, config);\n this.provider = fyndProvider(config);\n this._fynd = config;\n }\n protected override chainSupported(caip2: string): boolean {\n if (!isEvm(caip2)) return false;\n const id = evmRef(caip2);\n return !!FYND_CHAINS[id] && (!!this._fynd.selfHosted?.[id] || !!this._fynd.apiKey);\n }\n protected override pairSupported(fromChain: string, toChain: string): boolean {\n return fromChain === toChain && this.chainSupported(fromChain);\n }\n}\n","import {getAddress} from \"viem\";\nimport {evmRef, isEvm, timeoutSignal, sourceAssetOf} from \"@anywalletkit/protocol-swap2-core/util\";\nimport type {Asset, CrossChainProvider, IntentRef, IntentStatus, Quote, QuoteParams, UnsignedTx} from \"@anywalletkit/protocol-swap2-core/types\";\nimport {describeFyndActivity} from \"./activity.js\";\nimport {FYND_CHAINS, checkFyndTransaction} from \"./router.js\";\n\n/**\n * Fynd — Propeller Heads' DEX aggregator (docs.fynd.xyz) as a same-chain\n * swap2 provider: exact-input swaps on Ethereum, Base, Arbitrum, BNB Chain,\n * Polygon, Unichain and Robinhood Chain, priced over the pools Tycho indexes\n * and settled through TychoRouterV3.\n *\n * Fynd outputs are licensed under the Fynd License: a swap built from a Fynd\n * quote must settle through the Tycho router with its fees intact — which is\n * the only transaction this provider builds (see README).\n *\n * Two backends: the hosted API (`apiKey` — keep it on a server, behind\n * `remoteProvider`) or your own `fynd serve` per chain (`selfHosted`).\n */\n\nconst ZERO = \"0x0000000000000000000000000000000000000000\";\nconst DEFAULT_BASE_URL = \"https://fynd-api.propellerheads.xyz\";\nconst DEFAULT_SLIPPAGE_BPS = 50;\n/** A Fynd quote is priced at one block; the router enforces the minimum, the engine re-checks this TTL before broadcast. */\nconst DEFAULT_TTL_MS = 30_000;\n/**\n * Liquidity families left out of routing by default. 2026-10-24 on the hosted\n * API: every Ethereum route through `uniswap_v4_hooks` was quoted ~0.6–1 %\n * above the market and reverted when simulated (4 of 4 native-in pairs,\n * also USDT → USDC), while the same pairs without it executed (8 of 8 across\n * Ethereum, Base and Arbitrum). Override with `excludeProtocols`.\n */\nexport const FYND_DEFAULT_EXCLUDED_PROTOCOLS: readonly string[] = Object.freeze([\"uniswap_v4_hooks\"]);\nconst QUOTE_FAILURES: Readonly<Record<string, string>> = {\n no_route_found: \"no route for this pair\",\n insufficient_liquidity: \"not enough liquidity for this amount\",\n timeout: \"the solver timed out\",\n not_ready: \"the solver is still syncing\",\n price_check_failed: \"the price guard rejected the route\",\n encoding_failed: \"could not encode the swap (fees + slippage leave nothing)\",\n};\n\nexport interface FyndProviderConfig {\n enabled?: boolean;\n /** Hosted API key (raw `Authorization` value). Server-side only. */\n apiKey?: string;\n /** Hosted API base URL; requests go to `{baseUrl}/v1/{chain}/quote`. */\n baseUrl?: string;\n /** Your own Fynd servers per EVM chain id; requests go to `{url}/v1/quote`, no key. Takes precedence over the hosted API for that chain. */\n selfHosted?: Readonly<Record<number, string>>;\n /** JSON-RPC endpoints per chain id: pre-broadcast simulation and receipts. Optional. */\n rpcUrls?: Readonly<Record<number, readonly string[]>>;\n defaultSlippageBps?: number;\n quoteTtlMs?: number;\n timeoutMs?: number;\n /** Protocol names or family prefixes ending in \":\" Fynd must not route through. Default {@link FYND_DEFAULT_EXCLUDED_PROTOCOLS}. */\n excludeProtocols?: readonly string[];\n}\n\ninterface FyndOrderQuote {\n status?: string;\n amount_in?: string;\n amount_out?: string;\n gas_estimate?: string;\n price_impact_bps?: number | null;\n route?: {swaps?: {protocol?: string}[]};\n fee_breakdown?: {router_fee?: string; client_fee?: string; max_slippage?: string; min_amount_received?: string};\n transaction?: {to?: string; value?: string; data?: string};\n}\n\nconst uint = (v: unknown): bigint | null => (typeof v === \"string\" && /^\\d+$/.test(v) ? BigInt(v) : null);\n/** A protocol name from the API as it may appear in a refusal or the quote: at most 40 characters (scan 2026-10-27 modules, F34). */\nconst protocolName = (p: string): string => (p.length > 40 ? `${p.slice(0, 40)}…` : p);\n/** Hex or an address, compared without case (length first: the client's side may be a megabyte). */\nconst sameText = (a: string, b: string): boolean => a.length === b.length && a.toLowerCase() === b.toLowerCase();\nconst sameAmount = (a: string, b: string): boolean => /^\\d{1,80}$/.test(a) && /^\\d{1,80}$/.test(b) && BigInt(a) === BigInt(b);\n\nexport function fyndProvider(config: FyndProviderConfig): CrossChainProvider {\n const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const slippageDefault = config.defaultSlippageBps ?? DEFAULT_SLIPPAGE_BPS;\n const ttl = config.quoteTtlMs ?? DEFAULT_TTL_MS;\n const timeoutMs = config.timeoutMs ?? 10_000;\n const excluded = [...(config.excludeProtocols ?? FYND_DEFAULT_EXCLUDED_PROTOCOLS)];\n const isExcluded = (protocol: string) => excluded.some((x) => (x.endsWith(\":\") ? protocol.startsWith(x) : protocol === x));\n\n const endpoint = (chainId: number): {url: string; headers: Record<string, string>} | null => {\n const chain = FYND_CHAINS[chainId];\n if (!chain) return null;\n const own = config.selfHosted?.[chainId];\n if (own) return {url: `${own.replace(/\\/+$/, \"\")}/v1/quote`, headers: {}};\n if (!config.apiKey) return null;\n return {url: `${baseUrl}/v1/${chain.slug}/quote`, headers: {authorization: config.apiKey}};\n };\n const chainIdOf = (caip2: string): number | null => {\n if (!isEvm(caip2)) return null;\n const id = evmRef(caip2);\n return endpoint(id) ? id : null;\n };\n\n async function rpc(chainId: number, method: string, params: unknown[]): Promise<unknown> {\n let lastErr: unknown;\n for (const url of config.rpcUrls?.[chainId] ?? []) {\n try {\n const res = await fetch(url, {method: \"POST\", headers: {\"content-type\": \"application/json\"}, body: JSON.stringify({jsonrpc: \"2.0\", id: 1, method, params}), signal: timeoutSignal(12_000)});\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n const body = (await res.json()) as {result?: unknown; error?: {message?: string}};\n if (body.error) throw Object.assign(new Error(body.error.message ?? \"rpc error\"), {rpc: true});\n return body.result ?? null;\n } catch (e) {\n if ((e as {rpc?: boolean}).rpc) throw e;\n lastErr = e;\n }\n }\n throw new Error(`fynd: no RPC answered on chain ${chainId} (${lastErr instanceof Error ? lastErr.message : String(lastErr)})`);\n }\n\n return {\n name: \"fynd\",\n describeActivity: describeFyndActivity,\n supportsSameChain: true,\n enabled: () => config.enabled ?? true,\n supportsChain: (caip2: string) => chainIdOf(caip2) !== null,\n supportsChainPair: (from: string, to: string) => from === to && chainIdOf(from) !== null,\n supportsRoute: (from: Asset, to: Asset) => from.chain === to.chain && chainIdOf(from.chain) !== null,\n\n async getQuote(params: QuoteParams, signal?: AbortSignal): Promise<Quote | null> {\n const refuse = (why: string): null => {\n params.onRefusal?.(`fynd: ${why}`);\n return null;\n };\n if (params.flex) return refuse(\"amount-flexible deposits are not supported\");\n if (params.sourceChain !== params.destChain) return refuse(\"same-chain swaps only\");\n const chainId = chainIdOf(params.sourceChain);\n if (chainId === null) return refuse(`Fynd is not configured for ${params.sourceChain}`);\n const ep = endpoint(chainId)!;\n const src = sourceAssetOf(params);\n if (!src) return refuse(`unknown source token ${params.sourceToken}`);\n const tokenIn = src.contract ? getAddress(src.contract) : ZERO;\n const nativeOut = /^0x0+$/.test(params.destToken);\n if (!nativeOut && !/^0x[0-9a-fA-F]{40}$/.test(params.destToken)) return refuse(\"destination token must be an EVM address\");\n const tokenOut = nativeOut ? ZERO : getAddress(params.destToken);\n if (tokenIn === tokenOut) return refuse(\"same token in and out\");\n if (!/^0x[0-9a-fA-F]{40}$/.test(params.destAddress)) return refuse(\"recipient must be an EVM address\");\n const receiver = getAddress(params.destAddress);\n const sender = getAddress(params.refundAddress && /^0x[0-9a-fA-F]{40}$/.test(params.refundAddress) ? params.refundAddress : params.destAddress);\n let amountIn: bigint;\n try {\n amountIn = BigInt(params.amountIn);\n } catch {\n return refuse(\"amount is not an integer\");\n }\n if (amountIn <= 0n) return refuse(\"amount must be positive\");\n const bps = params.slippageBps ?? slippageDefault;\n if (!Number.isInteger(bps) || bps <= 0 || bps >= 10_000) return refuse(\"invalid slippage\");\n\n const body = {\n orders: [{token_in: tokenIn, token_out: tokenOut, amount: amountIn.toString(), side: \"sell\", sender, receiver}],\n options: {\n timeout_ms: Math.min(timeoutMs, 8_000),\n encoding_options: {slippage: (bps / 10_000).toString(), transfer_type: \"transfer_from\"},\n ...(excluded.length ? {route_filter: {exclude_protocols: excluded}} : {}),\n },\n };\n let res: Response;\n try {\n res = await fetch(ep.url, {method: \"POST\", headers: {\"content-type\": \"application/json\", ...ep.headers}, body: JSON.stringify(body), signal: signal ?? timeoutSignal(timeoutMs)});\n } catch (e) {\n return refuse(`API unreachable (${e instanceof Error ? e.name : \"error\"})`);\n }\n if (res.status === 401 || res.status === 403) return refuse(`API key rejected (HTTP ${res.status})`);\n if (res.status === 429) return refuse(\"rate limited (HTTP 429)\");\n if (res.status === 422) return refuse(\"no route for this pair\");\n if (!res.ok) return refuse(`API answered HTTP ${res.status}`);\n let parsed: {orders?: FyndOrderQuote[]};\n try {\n parsed = (await res.json()) as {orders?: FyndOrderQuote[]};\n } catch {\n return refuse(\"malformed API response\");\n }\n if (!Array.isArray(parsed.orders) || parsed.orders.length !== 1) return refuse(\"malformed API response (order count)\");\n const q = parsed.orders[0]!;\n if (q.status !== \"success\") return refuse(QUOTE_FAILURES[q.status ?? \"\"] ?? `quote status ${String(q.status).slice(0, 40)}`);\n\n const quotedIn = uint(q.amount_in);\n const gross = uint(q.amount_out);\n const routerFee = uint(q.fee_breakdown?.router_fee);\n const clientFee = uint(q.fee_breakdown?.client_fee);\n const maxSlippage = uint(q.fee_breakdown?.max_slippage);\n const minOut = uint(q.fee_breakdown?.min_amount_received);\n const value = uint(q.transaction?.value ?? \"\");\n if (quotedIn === null || gross === null || routerFee === null || clientFee === null || maxSlippage === null || minOut === null || value === null) {\n return refuse(\"malformed quote amounts\");\n }\n if (quotedIn !== amountIn) return refuse(`quoted ${quotedIn}, not the requested ${amountIn}`);\n if (clientFee !== 0n) return refuse(\"the quote carries a client fee\");\n if (routerFee + clientFee + maxSlippage + minOut !== gross) return refuse(\"fee breakdown does not add up to the output\");\n if (params.minAmountOut !== undefined && minOut < BigInt(params.minAmountOut)) return refuse(`floor breach (min=${minOut} < floor=${params.minAmountOut})`);\n const tx = {to: q.transaction?.to ?? \"\", value, data: q.transaction?.data ?? \"\"};\n try {\n checkFyndTransaction(tx, {chainId, tokenIn, tokenOut, amountIn, receiver, expectedAmountOut: gross, minAmountOut: minOut});\n } catch (e) {\n return refuse(e instanceof Error ? e.message.replace(/^fynd: /, \"\") : String(e));\n }\n const allProtocols = [...new Set((q.route?.swaps ?? []).map((s) => s.protocol).filter((p): p is string => typeof p === \"string\"))];\n const banned = allProtocols.find(isExcluded);\n if (banned) return refuse(`the route goes through ${protocolName(banned)}, which this wallet excludes`);\n // Native input needs no allowance: the exact transaction can be executed\n // against the latest block right now, so a route that would revert never\n // enters the auction. (ERC-20 input is simulated in preflightDeposit,\n // once the approval exists.)\n if (tokenIn === ZERO && config.rpcUrls?.[chainId]?.length) {\n try {\n await rpc(chainId, \"eth_call\", [{from: sender, to: tx.to, data: tx.data, value: `0x${value.toString(16)}`}, \"latest\", {[sender]: {balance: `0x${(value + 10n ** 18n).toString(16)}`}}]);\n } catch (e) {\n if ((e as {rpc?: boolean}).rpc) return refuse(`the route does not execute on-chain right now (simulation reverted via ${allProtocols.slice(0, 6).map(protocolName).join(\", \") || \"?\"})`);\n // no RPC answered: the router still enforces the minimum\n }\n }\n const gas = uint(q.gas_estimate) ?? 0n;\n const protocols = allProtocols.slice(0, 6).map(protocolName);\n return {\n provider: \"fynd\",\n intentId: `fynd:${chainId}`,\n depositAddress: \"\",\n depositMemo: null,\n txRequest: {\n to: getAddress(tx.to),\n data: tx.data,\n value: value.toString(),\n chainId,\n // the router's own overhead on top of Fynd's route estimate\n gasLimit: ((gas * 13n) / 10n + 80_000n).toString(),\n ...(tokenIn === ZERO ? {} : {approvalAddress: getAddress(tx.to)}),\n },\n sourceAmount: amountIn.toString(),\n // what the receiver gets at the quoted price: gross minus the router fee\n destAmount: (gross - routerFee).toString(),\n minDestAmount: minOut.toString(),\n feeUsd: 0,\n etaSeconds: 15,\n reliability: 0.96,\n validUntilMs: Date.now() + ttl,\n providerData: {\n minOut: minOut.toString(),\n sender,\n routerFee: routerFee.toString(),\n gross: gross.toString(),\n protocols: protocols.join(\",\"),\n ...(typeof q.price_impact_bps === \"number\" ? {priceImpactBps: String(q.price_impact_bps)} : {}),\n },\n };\n },\n\n /**\n * Simulate the exact transaction at the latest block (allowance already in place); refuse when it would revert.\n * Only the quote's own transaction is simulated: behind a backend `tx` is the client's and only the quote is\n * sealed, so a client's call (any target, a megabyte of calldata) never reaches this RPC (scan 2026-10-27 modules, F24).\n */\n async preflightDeposit(quote: Quote, tx: UnsignedTx): Promise<void> {\n const chainId = Number(quote.intentId.split(\":\")[1]);\n const sender = quote.providerData?.sender;\n if (!FYND_CHAINS[chainId] || !config.rpcUrls?.[chainId]?.length || !tx.data || !sender) return;\n const quoted = quote.txRequest;\n if (!quoted?.to || !quoted.data || !sameText(tx.to, quoted.to) || !sameText(tx.data, quoted.data) || !sameAmount(tx.value || \"0\", quoted.value ?? \"0\")) {\n throw new Error(\"fynd: the transaction is not the quoted swap. Nothing was sent; refresh the quote.\");\n }\n try {\n await rpc(chainId, \"eth_call\", [{from: sender, to: quoted.to, data: quoted.data, value: `0x${BigInt(quoted.value ?? \"0\").toString(16)}`}, \"latest\"]);\n } catch (e) {\n if ((e as {rpc?: boolean}).rpc) {\n throw new Error(`fynd: the swap would revert now (${e instanceof Error ? e.message.slice(0, 120) : \"revert\"}) — the price moved past your minimum. Nothing was sent; refresh the quote.`);\n }\n // no RPC answered: leave it to the router's own minimum\n }\n },\n\n async buildDeposit(quote: Quote): Promise<UnsignedTx> {\n const tx = quote.txRequest;\n if (!tx?.to || !tx.data) throw new Error(\"swap2: Fynd quote has no transaction request\");\n return {chain: \"\", to: tx.to, value: tx.value ?? \"0\", data: tx.data, ...(tx.chainId !== undefined ? {chainId: tx.chainId} : {}), ...(tx.gasLimit ? {gasLimit: tx.gasLimit} : {}), ...(tx.approvalAddress ? {approvalAddress: tx.approvalAddress} : {})};\n },\n\n async trackIntent(ref: IntentRef): Promise<IntentStatus> {\n if (!ref.sourceTxHash) return {state: \"pending\"};\n const chainId = Number(ref.intentId.split(\":\")[1]);\n if (!FYND_CHAINS[chainId] || !config.rpcUrls?.[chainId]?.length) return {state: \"pending\"};\n try {\n const receipt = (await rpc(chainId, \"eth_getTransactionReceipt\", [ref.sourceTxHash])) as {status?: string} | null;\n if (!receipt) return {state: \"pending\"};\n return receipt.status === \"0x1\" ? {state: \"completed\", destTxHash: ref.sourceTxHash} : {state: \"failed\"};\n } catch {\n return {state: \"pending\"};\n }\n },\n };\n}\n","/**\n * Activity recognition for Fynd: a swap entry point of the pinned\n * TychoRouterV3 on a chain the module serves → `swap`, \"Swapped A → B\" from\n * the owner's net transfers.\n */\nimport type {ActivityItem, ChainTx, DescribeActivityContext} from \"@anywalletkit/core\";\nimport {chainLabel, describeSwapLike, matchesCall} from \"@anywalletkit/protocol-swap2-core/activity\";\nimport {evmRef, isEvm} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {FYND_CHAINS, TYCHO_ROUTER_SELECTORS} from \"./router.js\";\n\nexport const FYND_MODULE = \"@anywalletkit/protocol-swap2-fynd\";\n\nexport function describeFyndActivity(tx: ChainTx, ctx: DescribeActivityContext): ActivityItem | null {\n if (!isEvm(tx.chain)) return null;\n const chain = FYND_CHAINS[evmRef(tx.chain)];\n if (!chain) return null;\n if (!matchesCall(tx, [chain.router], TYCHO_ROUTER_SELECTORS)) return null;\n return describeSwapLike(tx, ctx, {protocol: FYND_MODULE, subtitle: `Fynd · ${chainLabel(tx.chain)}`, kind: \"swap\"});\n}\n","/**\n * TychoRouterV3 — the only contract a Fynd quote may settle through (Fynd\n * License §3: every settlement of a Fynd output goes through the router in\n * Propeller Heads' registry). This file pins the router per chain and checks\n * Fynd's encoded transaction against the request before anything is approved\n * or sent: right contract, right method, the exact input, the requested\n * tokens, the user as receiver, the quoted output and minimum, and no client\n * fee. The route inside `swaps` is opaque and is not inspected here — the\n * router itself reverts below `minAmountOut`.\n */\nimport type {Quote, QuoteParams, RemotePins} from \"@anywalletkit/protocol-swap2-core/types\";\nimport {sourceAssetOf} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {decodeFunctionData, getAddress, parseAbi, toFunctionSelector, type Hex} from \"viem\";\n\n/** Router registry: github.com/propeller-heads/tycho crates/tycho-execution/config/deployment_registry.json (TychoRouterV3, effective 2026-09-01). */\nexport const FYND_CHAINS: Readonly<Record<number, {slug: string; name: string; router: `0x${string}`}>> = Object.freeze({\n 1: {slug: \"ethereum\", name: \"Ethereum\", router: \"0x1644D2477f809cc2C71bCCFd6Dc9497E3F83210d\"},\n 8453: {slug: \"base\", name: \"Base\", router: \"0xAbA5B53b03eAfaD1C5fc8BD5Fc765fC85Bb3de67\"},\n 42161: {slug: \"arbitrum\", name: \"Arbitrum\", router: \"0x924F147c50eA59f5180a26031A8b65B2aA1e81Cd\"},\n 56: {slug: \"bsc\", name: \"BNB Smart Chain\", router: \"0x7f3D12BBaFb8955E51B3ab9588b34c8ad95BDA4e\"},\n 137: {slug: \"polygon\", name: \"Polygon\", router: \"0xbd4e6011F03355C2A377Fd9Af939322A7d0A1bC1\"},\n 130: {slug: \"unichain\", name: \"Unichain\", router: \"0xCba5574597Ad00EA250Fd106DAB4Fc7461949635\"},\n 4663: {slug: \"robinhood\", name: \"Robinhood Chain\", router: \"0x09215a470bD585E59EB3F4B612fBD2678131fF9e\"},\n});\n\n/** The router's native-coin sentinel (lib/NativeETH.sol); Fynd's API uses the zero address instead. */\nexport const ROUTER_NATIVE = \"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE\";\nconst ZERO = \"0x0000000000000000000000000000000000000000\";\n\nconst PARAMS = \"uint256 amountIn, address tokenIn, address tokenOut, uint256 expectedAmountOut, uint256 minAmountOut\";\nconst FEE = \"(uint32 clientFeeBps, address clientFeeReceiver, uint256 maxClientContribution, uint256 deadline, bytes clientSignature) clientFeeParams\";\nconst PERMIT = \"((address token, uint160 amount, uint48 expiration, uint48 nonce) details, address spender, uint256 sigDeadline) permitSingle, bytes signature\";\n\n/** The three entry points a wallet swap uses (plain `transferFrom` from the user). */\nexport const TYCHO_ROUTER_ABI = parseAbi([\n `function singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData) payable returns (uint256)`,\n `function sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n `function splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n]);\n\n/** Every swap entry point of the router (Permit2 and vault variants included) — for recognizing history. */\nexport const TYCHO_ROUTER_SELECTORS: ReadonlySet<string> = new Set(\n [\n `singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwapUsingVault(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swapData)`,\n `sequentialSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n `splitSwapPermit2(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n ].map((sig) => toFunctionSelector(`function ${sig}`)),\n);\n\nexport class FyndTransactionError extends Error {\n constructor(message: string) {\n super(`fynd: ${message}`);\n this.name = \"FyndTransactionError\";\n }\n}\n\nexport interface ExpectedSwap {\n chainId: number;\n /** ERC-20 address, or the zero address for the native coin. */\n tokenIn: string;\n tokenOut: string;\n amountIn: bigint;\n receiver: string;\n /** Fynd's gross `amount_out` (before the router fee and slippage). */\n expectedAmountOut: bigint;\n /** Fynd's `min_amount_received` — what the router enforces on-chain. */\n minAmountOut: bigint;\n}\n\nexport interface EncodedTx {\n to: string;\n value: bigint;\n data: string;\n}\n\nconst sameAddress = (a: string, b: string) => {\n try {\n return getAddress(a) === getAddress(b);\n } catch {\n return false;\n }\n};\nconst routerToken = (t: string) => (sameAddress(t, ZERO) ? ROUTER_NATIVE : t);\n\n/** Refuses (throws {@link FyndTransactionError}) unless `tx` is exactly the swap the wallet asked for. */\nexport function checkFyndTransaction(tx: EncodedTx, expected: ExpectedSwap): {method: \"singleSwap\" | \"sequentialSwap\" | \"splitSwap\"} {\n const chain = FYND_CHAINS[expected.chainId];\n if (!chain) throw new FyndTransactionError(`chain ${expected.chainId} has no pinned Tycho router`);\n // `tx.to` is the API's: named in the refusal only when it is an address (scan 2026-10-27 modules, F34).\n if (!sameAddress(tx.to, chain.router)) throw new FyndTransactionError(`the transaction targets ${/^0x[0-9a-fA-F]{40}$/.test(tx.to) ? tx.to : \"something that is not an address\"}, not the Tycho router on ${chain.name}`);\n if (sameAddress(expected.receiver, ZERO) || sameAddress(expected.receiver, chain.router)) throw new FyndTransactionError(\"invalid receiver\");\n if (expected.amountIn <= 0n || expected.minAmountOut <= 0n || expected.minAmountOut > expected.expectedAmountOut) {\n throw new FyndTransactionError(\"invalid quote amounts\");\n }\n const nativeIn = sameAddress(expected.tokenIn, ZERO);\n if (tx.value !== (nativeIn ? expected.amountIn : 0n)) throw new FyndTransactionError(\"native value does not match the input\");\n if (!/^0x(?:[0-9a-fA-F]{2}){4,}$/.test(tx.data)) throw new FyndTransactionError(\"malformed calldata\");\n\n let call: ReturnType<typeof decodeFunctionData<typeof TYCHO_ROUTER_ABI>>;\n try {\n call = decodeFunctionData({abi: TYCHO_ROUTER_ABI, data: tx.data as Hex});\n } catch {\n throw new FyndTransactionError(\"not a plain singleSwap / sequentialSwap / splitSwap call (Permit2 and vault variants are not used)\");\n }\n const a = call.args;\n const [amountIn, tokenIn, tokenOut, expectedAmountOut, minAmountOut] = a;\n const isSplit = call.functionName === \"splitSwap\";\n const receiver = (isSplit ? a[6] : a[5]) as string;\n const fee = (isSplit ? a[7] : a[6]) as {clientFeeBps: number; clientFeeReceiver: string; maxClientContribution: bigint; deadline: bigint; clientSignature: string};\n const swaps = (isSplit ? a[8] : a[7]) as string;\n if (amountIn !== expected.amountIn) throw new FyndTransactionError(`calldata spends ${amountIn}, not ${expected.amountIn}`);\n if (!sameAddress(tokenIn, routerToken(expected.tokenIn)) || !sameAddress(tokenOut, routerToken(expected.tokenOut))) {\n throw new FyndTransactionError(\"calldata swaps other tokens than requested\");\n }\n if (!sameAddress(receiver, expected.receiver)) throw new FyndTransactionError(`calldata pays ${receiver}, not ${expected.receiver}`);\n if (expectedAmountOut !== expected.expectedAmountOut || minAmountOut !== expected.minAmountOut) {\n throw new FyndTransactionError(\"calldata amounts do not match the quote\");\n }\n if (fee.clientFeeBps !== 0 || fee.maxClientContribution !== 0n || !sameAddress(fee.clientFeeReceiver, ZERO)) {\n throw new FyndTransactionError(\"calldata carries client-fee parameters — this module charges none\");\n }\n if (swaps === \"0x\" || (isSplit && (a[5] as bigint) < 2n)) throw new FyndTransactionError(\"empty or invalid route\");\n return {method: call.functionName};\n}\n\n/**\n * {@link checkFyndTransaction} on a remote quote, against the request this app sent: the pinned\n * router, one chain, the exact input, the requested tokens, the app's recipient, the quoted minimum and\n * no client fee. The reason it fails, or null.\n */\nexport function fyndVerifyQuote(params: QuoteParams, quote: Quote): string | null {\n // Fynd swaps on one chain; a source-chain swap must not answer a cross-chain request (reaudit 2026-10-30, #3).\n if (params.sourceChain !== params.destChain) return \"same-chain swaps only\";\n try {\n const chainId = /^eip155:[0-9]+$/.test(params.sourceChain) ? Number(params.sourceChain.slice(7)) : NaN;\n const src = sourceAssetOf(params);\n if (!src) return `unknown source token ${params.sourceToken}`;\n const tx = quote.txRequest;\n if (!tx?.to || !tx.data) return \"the quote carries no router transaction\";\n checkFyndTransaction({to: tx.to, data: tx.data, value: BigInt(tx.value ?? \"0\")}, {\n chainId,\n tokenIn: src.contract ? getAddress(src.contract) : ZERO,\n tokenOut: /^0x0+$/.test(params.destToken) ? ZERO : getAddress(params.destToken),\n amountIn: BigInt(params.amountIn),\n receiver: getAddress(params.destAddress),\n expectedAmountOut: BigInt(quote.providerData?.gross ?? \"0\"),\n minAmountOut: BigInt(quote.minDestAmount),\n });\n return null;\n } catch (e) {\n return e instanceof Error ? e.message.replace(/^fynd: /, \"\") : String(e);\n }\n}\n\n/**\n * The Tycho router a Fynd swap calls and approves on a chain, and {@link fyndVerifyQuote} — the\n * app's side of `remoteProvider({name: \"fynd\", baseUrl, ...fyndPins})`, so a backend's quote is\n * checked against these routers and the app's own request, never against itself (scan 2026-10-27\n * modules, F9).\n */\nexport const fyndPins: RemotePins = Object.freeze({\n callTargets: (chain: string): readonly string[] => fyndRouterFor(chain),\n spenders: (chain: string): readonly string[] => fyndRouterFor(chain),\n verifyQuote: fyndVerifyQuote,\n});\nfunction fyndRouterFor(chain: string): readonly string[] {\n const id = /^eip155:[0-9]+$/.test(chain) ? Number(chain.slice(7)) : NaN;\n const c = Object.prototype.hasOwnProperty.call(FYND_CHAINS, id) ? FYND_CHAINS[id] : undefined;\n return c ? [c.router] : [];\n}\n"],"mappings":"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","names":["import_util","import_viem","import_util","import_util","ZERO"]}
|
package/dist/protocol.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FyndSwap2Protocol
|
|
3
|
-
} from "./chunk-
|
|
4
|
-
import "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
3
|
+
} from "./chunk-TMFP2W5C.js";
|
|
4
|
+
import "./chunk-4BCEAXID.js";
|
|
5
|
+
import "./chunk-PF2NGOA5.js";
|
|
6
|
+
import "./chunk-J6XM7WEG.js";
|
|
7
7
|
export {
|
|
8
8
|
FyndSwap2Protocol
|
|
9
9
|
};
|
package/dist/provider.cjs
CHANGED
|
@@ -118,6 +118,7 @@ function checkFyndTransaction(tx, expected) {
|
|
|
118
118
|
return { method: call.functionName };
|
|
119
119
|
}
|
|
120
120
|
function fyndVerifyQuote(params, quote) {
|
|
121
|
+
if (params.sourceChain !== params.destChain) return "same-chain swaps only";
|
|
121
122
|
try {
|
|
122
123
|
const chainId = /^eip155:[0-9]+$/.test(params.sourceChain) ? Number(params.sourceChain.slice(7)) : NaN;
|
|
123
124
|
const src = (0, import_util.sourceAssetOf)(params);
|
package/dist/provider.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provider.ts","../src/activity.ts","../src/router.ts"],"sourcesContent":["import {getAddress} from \"viem\";\nimport {evmRef, isEvm, timeoutSignal, sourceAssetOf} from \"@anywalletkit/protocol-swap2-core/util\";\nimport type {Asset, CrossChainProvider, IntentRef, IntentStatus, Quote, QuoteParams, UnsignedTx} from \"@anywalletkit/protocol-swap2-core/types\";\nimport {describeFyndActivity} from \"./activity.js\";\nimport {FYND_CHAINS, checkFyndTransaction} from \"./router.js\";\n\n/**\n * Fynd — Propeller Heads' DEX aggregator (docs.fynd.xyz) as a same-chain\n * swap2 provider: exact-input swaps on Ethereum, Base, Arbitrum, BNB Chain,\n * Polygon, Unichain and Robinhood Chain, priced over the pools Tycho indexes\n * and settled through TychoRouterV3.\n *\n * Fynd outputs are licensed under the Fynd License: a swap built from a Fynd\n * quote must settle through the Tycho router with its fees intact — which is\n * the only transaction this provider builds (see README).\n *\n * Two backends: the hosted API (`apiKey` — keep it on a server, behind\n * `remoteProvider`) or your own `fynd serve` per chain (`selfHosted`).\n */\n\nconst ZERO = \"0x0000000000000000000000000000000000000000\";\nconst DEFAULT_BASE_URL = \"https://fynd-api.propellerheads.xyz\";\nconst DEFAULT_SLIPPAGE_BPS = 50;\n/** A Fynd quote is priced at one block; the router enforces the minimum, the engine re-checks this TTL before broadcast. */\nconst DEFAULT_TTL_MS = 30_000;\n/**\n * Liquidity families left out of routing by default. 2026-10-24 on the hosted\n * API: every Ethereum route through `uniswap_v4_hooks` was quoted ~0.6–1 %\n * above the market and reverted when simulated (4 of 4 native-in pairs,\n * also USDT → USDC), while the same pairs without it executed (8 of 8 across\n * Ethereum, Base and Arbitrum). Override with `excludeProtocols`.\n */\nexport const FYND_DEFAULT_EXCLUDED_PROTOCOLS: readonly string[] = Object.freeze([\"uniswap_v4_hooks\"]);\nconst QUOTE_FAILURES: Readonly<Record<string, string>> = {\n no_route_found: \"no route for this pair\",\n insufficient_liquidity: \"not enough liquidity for this amount\",\n timeout: \"the solver timed out\",\n not_ready: \"the solver is still syncing\",\n price_check_failed: \"the price guard rejected the route\",\n encoding_failed: \"could not encode the swap (fees + slippage leave nothing)\",\n};\n\nexport interface FyndProviderConfig {\n enabled?: boolean;\n /** Hosted API key (raw `Authorization` value). Server-side only. */\n apiKey?: string;\n /** Hosted API base URL; requests go to `{baseUrl}/v1/{chain}/quote`. */\n baseUrl?: string;\n /** Your own Fynd servers per EVM chain id; requests go to `{url}/v1/quote`, no key. Takes precedence over the hosted API for that chain. */\n selfHosted?: Readonly<Record<number, string>>;\n /** JSON-RPC endpoints per chain id: pre-broadcast simulation and receipts. Optional. */\n rpcUrls?: Readonly<Record<number, readonly string[]>>;\n defaultSlippageBps?: number;\n quoteTtlMs?: number;\n timeoutMs?: number;\n /** Protocol names or family prefixes ending in \":\" Fynd must not route through. Default {@link FYND_DEFAULT_EXCLUDED_PROTOCOLS}. */\n excludeProtocols?: readonly string[];\n}\n\ninterface FyndOrderQuote {\n status?: string;\n amount_in?: string;\n amount_out?: string;\n gas_estimate?: string;\n price_impact_bps?: number | null;\n route?: {swaps?: {protocol?: string}[]};\n fee_breakdown?: {router_fee?: string; client_fee?: string; max_slippage?: string; min_amount_received?: string};\n transaction?: {to?: string; value?: string; data?: string};\n}\n\nconst uint = (v: unknown): bigint | null => (typeof v === \"string\" && /^\\d+$/.test(v) ? BigInt(v) : null);\n/** A protocol name from the API as it may appear in a refusal or the quote: at most 40 characters (scan 2026-10-27 modules, F34). */\nconst protocolName = (p: string): string => (p.length > 40 ? `${p.slice(0, 40)}…` : p);\n/** Hex or an address, compared without case (length first: the client's side may be a megabyte). */\nconst sameText = (a: string, b: string): boolean => a.length === b.length && a.toLowerCase() === b.toLowerCase();\nconst sameAmount = (a: string, b: string): boolean => /^\\d{1,80}$/.test(a) && /^\\d{1,80}$/.test(b) && BigInt(a) === BigInt(b);\n\nexport function fyndProvider(config: FyndProviderConfig): CrossChainProvider {\n const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const slippageDefault = config.defaultSlippageBps ?? DEFAULT_SLIPPAGE_BPS;\n const ttl = config.quoteTtlMs ?? DEFAULT_TTL_MS;\n const timeoutMs = config.timeoutMs ?? 10_000;\n const excluded = [...(config.excludeProtocols ?? FYND_DEFAULT_EXCLUDED_PROTOCOLS)];\n const isExcluded = (protocol: string) => excluded.some((x) => (x.endsWith(\":\") ? protocol.startsWith(x) : protocol === x));\n\n const endpoint = (chainId: number): {url: string; headers: Record<string, string>} | null => {\n const chain = FYND_CHAINS[chainId];\n if (!chain) return null;\n const own = config.selfHosted?.[chainId];\n if (own) return {url: `${own.replace(/\\/+$/, \"\")}/v1/quote`, headers: {}};\n if (!config.apiKey) return null;\n return {url: `${baseUrl}/v1/${chain.slug}/quote`, headers: {authorization: config.apiKey}};\n };\n const chainIdOf = (caip2: string): number | null => {\n if (!isEvm(caip2)) return null;\n const id = evmRef(caip2);\n return endpoint(id) ? id : null;\n };\n\n async function rpc(chainId: number, method: string, params: unknown[]): Promise<unknown> {\n let lastErr: unknown;\n for (const url of config.rpcUrls?.[chainId] ?? []) {\n try {\n const res = await fetch(url, {method: \"POST\", headers: {\"content-type\": \"application/json\"}, body: JSON.stringify({jsonrpc: \"2.0\", id: 1, method, params}), signal: timeoutSignal(12_000)});\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n const body = (await res.json()) as {result?: unknown; error?: {message?: string}};\n if (body.error) throw Object.assign(new Error(body.error.message ?? \"rpc error\"), {rpc: true});\n return body.result ?? null;\n } catch (e) {\n if ((e as {rpc?: boolean}).rpc) throw e;\n lastErr = e;\n }\n }\n throw new Error(`fynd: no RPC answered on chain ${chainId} (${lastErr instanceof Error ? lastErr.message : String(lastErr)})`);\n }\n\n return {\n name: \"fynd\",\n describeActivity: describeFyndActivity,\n supportsSameChain: true,\n enabled: () => config.enabled ?? true,\n supportsChain: (caip2: string) => chainIdOf(caip2) !== null,\n supportsChainPair: (from: string, to: string) => from === to && chainIdOf(from) !== null,\n supportsRoute: (from: Asset, to: Asset) => from.chain === to.chain && chainIdOf(from.chain) !== null,\n\n async getQuote(params: QuoteParams, signal?: AbortSignal): Promise<Quote | null> {\n const refuse = (why: string): null => {\n params.onRefusal?.(`fynd: ${why}`);\n return null;\n };\n if (params.flex) return refuse(\"amount-flexible deposits are not supported\");\n if (params.sourceChain !== params.destChain) return refuse(\"same-chain swaps only\");\n const chainId = chainIdOf(params.sourceChain);\n if (chainId === null) return refuse(`Fynd is not configured for ${params.sourceChain}`);\n const ep = endpoint(chainId)!;\n const src = sourceAssetOf(params);\n if (!src) return refuse(`unknown source token ${params.sourceToken}`);\n const tokenIn = src.contract ? getAddress(src.contract) : ZERO;\n const nativeOut = /^0x0+$/.test(params.destToken);\n if (!nativeOut && !/^0x[0-9a-fA-F]{40}$/.test(params.destToken)) return refuse(\"destination token must be an EVM address\");\n const tokenOut = nativeOut ? ZERO : getAddress(params.destToken);\n if (tokenIn === tokenOut) return refuse(\"same token in and out\");\n if (!/^0x[0-9a-fA-F]{40}$/.test(params.destAddress)) return refuse(\"recipient must be an EVM address\");\n const receiver = getAddress(params.destAddress);\n const sender = getAddress(params.refundAddress && /^0x[0-9a-fA-F]{40}$/.test(params.refundAddress) ? params.refundAddress : params.destAddress);\n let amountIn: bigint;\n try {\n amountIn = BigInt(params.amountIn);\n } catch {\n return refuse(\"amount is not an integer\");\n }\n if (amountIn <= 0n) return refuse(\"amount must be positive\");\n const bps = params.slippageBps ?? slippageDefault;\n if (!Number.isInteger(bps) || bps <= 0 || bps >= 10_000) return refuse(\"invalid slippage\");\n\n const body = {\n orders: [{token_in: tokenIn, token_out: tokenOut, amount: amountIn.toString(), side: \"sell\", sender, receiver}],\n options: {\n timeout_ms: Math.min(timeoutMs, 8_000),\n encoding_options: {slippage: (bps / 10_000).toString(), transfer_type: \"transfer_from\"},\n ...(excluded.length ? {route_filter: {exclude_protocols: excluded}} : {}),\n },\n };\n let res: Response;\n try {\n res = await fetch(ep.url, {method: \"POST\", headers: {\"content-type\": \"application/json\", ...ep.headers}, body: JSON.stringify(body), signal: signal ?? timeoutSignal(timeoutMs)});\n } catch (e) {\n return refuse(`API unreachable (${e instanceof Error ? e.name : \"error\"})`);\n }\n if (res.status === 401 || res.status === 403) return refuse(`API key rejected (HTTP ${res.status})`);\n if (res.status === 429) return refuse(\"rate limited (HTTP 429)\");\n if (res.status === 422) return refuse(\"no route for this pair\");\n if (!res.ok) return refuse(`API answered HTTP ${res.status}`);\n let parsed: {orders?: FyndOrderQuote[]};\n try {\n parsed = (await res.json()) as {orders?: FyndOrderQuote[]};\n } catch {\n return refuse(\"malformed API response\");\n }\n if (!Array.isArray(parsed.orders) || parsed.orders.length !== 1) return refuse(\"malformed API response (order count)\");\n const q = parsed.orders[0]!;\n if (q.status !== \"success\") return refuse(QUOTE_FAILURES[q.status ?? \"\"] ?? `quote status ${String(q.status).slice(0, 40)}`);\n\n const quotedIn = uint(q.amount_in);\n const gross = uint(q.amount_out);\n const routerFee = uint(q.fee_breakdown?.router_fee);\n const clientFee = uint(q.fee_breakdown?.client_fee);\n const maxSlippage = uint(q.fee_breakdown?.max_slippage);\n const minOut = uint(q.fee_breakdown?.min_amount_received);\n const value = uint(q.transaction?.value ?? \"\");\n if (quotedIn === null || gross === null || routerFee === null || clientFee === null || maxSlippage === null || minOut === null || value === null) {\n return refuse(\"malformed quote amounts\");\n }\n if (quotedIn !== amountIn) return refuse(`quoted ${quotedIn}, not the requested ${amountIn}`);\n if (clientFee !== 0n) return refuse(\"the quote carries a client fee\");\n if (routerFee + clientFee + maxSlippage + minOut !== gross) return refuse(\"fee breakdown does not add up to the output\");\n if (params.minAmountOut !== undefined && minOut < BigInt(params.minAmountOut)) return refuse(`floor breach (min=${minOut} < floor=${params.minAmountOut})`);\n const tx = {to: q.transaction?.to ?? \"\", value, data: q.transaction?.data ?? \"\"};\n try {\n checkFyndTransaction(tx, {chainId, tokenIn, tokenOut, amountIn, receiver, expectedAmountOut: gross, minAmountOut: minOut});\n } catch (e) {\n return refuse(e instanceof Error ? e.message.replace(/^fynd: /, \"\") : String(e));\n }\n const allProtocols = [...new Set((q.route?.swaps ?? []).map((s) => s.protocol).filter((p): p is string => typeof p === \"string\"))];\n const banned = allProtocols.find(isExcluded);\n if (banned) return refuse(`the route goes through ${protocolName(banned)}, which this wallet excludes`);\n // Native input needs no allowance: the exact transaction can be executed\n // against the latest block right now, so a route that would revert never\n // enters the auction. (ERC-20 input is simulated in preflightDeposit,\n // once the approval exists.)\n if (tokenIn === ZERO && config.rpcUrls?.[chainId]?.length) {\n try {\n await rpc(chainId, \"eth_call\", [{from: sender, to: tx.to, data: tx.data, value: `0x${value.toString(16)}`}, \"latest\", {[sender]: {balance: `0x${(value + 10n ** 18n).toString(16)}`}}]);\n } catch (e) {\n if ((e as {rpc?: boolean}).rpc) return refuse(`the route does not execute on-chain right now (simulation reverted via ${allProtocols.slice(0, 6).map(protocolName).join(\", \") || \"?\"})`);\n // no RPC answered: the router still enforces the minimum\n }\n }\n const gas = uint(q.gas_estimate) ?? 0n;\n const protocols = allProtocols.slice(0, 6).map(protocolName);\n return {\n provider: \"fynd\",\n intentId: `fynd:${chainId}`,\n depositAddress: \"\",\n depositMemo: null,\n txRequest: {\n to: getAddress(tx.to),\n data: tx.data,\n value: value.toString(),\n chainId,\n // the router's own overhead on top of Fynd's route estimate\n gasLimit: ((gas * 13n) / 10n + 80_000n).toString(),\n ...(tokenIn === ZERO ? {} : {approvalAddress: getAddress(tx.to)}),\n },\n sourceAmount: amountIn.toString(),\n // what the receiver gets at the quoted price: gross minus the router fee\n destAmount: (gross - routerFee).toString(),\n minDestAmount: minOut.toString(),\n feeUsd: 0,\n etaSeconds: 15,\n reliability: 0.96,\n validUntilMs: Date.now() + ttl,\n providerData: {\n minOut: minOut.toString(),\n sender,\n routerFee: routerFee.toString(),\n gross: gross.toString(),\n protocols: protocols.join(\",\"),\n ...(typeof q.price_impact_bps === \"number\" ? {priceImpactBps: String(q.price_impact_bps)} : {}),\n },\n };\n },\n\n /**\n * Simulate the exact transaction at the latest block (allowance already in place); refuse when it would revert.\n * Only the quote's own transaction is simulated: behind a backend `tx` is the client's and only the quote is\n * sealed, so a client's call (any target, a megabyte of calldata) never reaches this RPC (scan 2026-10-27 modules, F24).\n */\n async preflightDeposit(quote: Quote, tx: UnsignedTx): Promise<void> {\n const chainId = Number(quote.intentId.split(\":\")[1]);\n const sender = quote.providerData?.sender;\n if (!FYND_CHAINS[chainId] || !config.rpcUrls?.[chainId]?.length || !tx.data || !sender) return;\n const quoted = quote.txRequest;\n if (!quoted?.to || !quoted.data || !sameText(tx.to, quoted.to) || !sameText(tx.data, quoted.data) || !sameAmount(tx.value || \"0\", quoted.value ?? \"0\")) {\n throw new Error(\"fynd: the transaction is not the quoted swap. Nothing was sent; refresh the quote.\");\n }\n try {\n await rpc(chainId, \"eth_call\", [{from: sender, to: quoted.to, data: quoted.data, value: `0x${BigInt(quoted.value ?? \"0\").toString(16)}`}, \"latest\"]);\n } catch (e) {\n if ((e as {rpc?: boolean}).rpc) {\n throw new Error(`fynd: the swap would revert now (${e instanceof Error ? e.message.slice(0, 120) : \"revert\"}) — the price moved past your minimum. Nothing was sent; refresh the quote.`);\n }\n // no RPC answered: leave it to the router's own minimum\n }\n },\n\n async buildDeposit(quote: Quote): Promise<UnsignedTx> {\n const tx = quote.txRequest;\n if (!tx?.to || !tx.data) throw new Error(\"swap2: Fynd quote has no transaction request\");\n return {chain: \"\", to: tx.to, value: tx.value ?? \"0\", data: tx.data, ...(tx.chainId !== undefined ? {chainId: tx.chainId} : {}), ...(tx.gasLimit ? {gasLimit: tx.gasLimit} : {}), ...(tx.approvalAddress ? {approvalAddress: tx.approvalAddress} : {})};\n },\n\n async trackIntent(ref: IntentRef): Promise<IntentStatus> {\n if (!ref.sourceTxHash) return {state: \"pending\"};\n const chainId = Number(ref.intentId.split(\":\")[1]);\n if (!FYND_CHAINS[chainId] || !config.rpcUrls?.[chainId]?.length) return {state: \"pending\"};\n try {\n const receipt = (await rpc(chainId, \"eth_getTransactionReceipt\", [ref.sourceTxHash])) as {status?: string} | null;\n if (!receipt) return {state: \"pending\"};\n return receipt.status === \"0x1\" ? {state: \"completed\", destTxHash: ref.sourceTxHash} : {state: \"failed\"};\n } catch {\n return {state: \"pending\"};\n }\n },\n };\n}\n","/**\n * Activity recognition for Fynd: a swap entry point of the pinned\n * TychoRouterV3 on a chain the module serves → `swap`, \"Swapped A → B\" from\n * the owner's net transfers.\n */\nimport type {ActivityItem, ChainTx, DescribeActivityContext} from \"@anywalletkit/core\";\nimport {chainLabel, describeSwapLike, matchesCall} from \"@anywalletkit/protocol-swap2-core/activity\";\nimport {evmRef, isEvm} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {FYND_CHAINS, TYCHO_ROUTER_SELECTORS} from \"./router.js\";\n\nexport const FYND_MODULE = \"@anywalletkit/protocol-swap2-fynd\";\n\nexport function describeFyndActivity(tx: ChainTx, ctx: DescribeActivityContext): ActivityItem | null {\n if (!isEvm(tx.chain)) return null;\n const chain = FYND_CHAINS[evmRef(tx.chain)];\n if (!chain) return null;\n if (!matchesCall(tx, [chain.router], TYCHO_ROUTER_SELECTORS)) return null;\n return describeSwapLike(tx, ctx, {protocol: FYND_MODULE, subtitle: `Fynd · ${chainLabel(tx.chain)}`, kind: \"swap\"});\n}\n","/**\n * TychoRouterV3 — the only contract a Fynd quote may settle through (Fynd\n * License §3: every settlement of a Fynd output goes through the router in\n * Propeller Heads' registry). This file pins the router per chain and checks\n * Fynd's encoded transaction against the request before anything is approved\n * or sent: right contract, right method, the exact input, the requested\n * tokens, the user as receiver, the quoted output and minimum, and no client\n * fee. The route inside `swaps` is opaque and is not inspected here — the\n * router itself reverts below `minAmountOut`.\n */\nimport type {Quote, QuoteParams, RemotePins} from \"@anywalletkit/protocol-swap2-core/types\";\nimport {sourceAssetOf} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {decodeFunctionData, getAddress, parseAbi, toFunctionSelector, type Hex} from \"viem\";\n\n/** Router registry: github.com/propeller-heads/tycho crates/tycho-execution/config/deployment_registry.json (TychoRouterV3, effective 2026-09-01). */\nexport const FYND_CHAINS: Readonly<Record<number, {slug: string; name: string; router: `0x${string}`}>> = Object.freeze({\n 1: {slug: \"ethereum\", name: \"Ethereum\", router: \"0x1644D2477f809cc2C71bCCFd6Dc9497E3F83210d\"},\n 8453: {slug: \"base\", name: \"Base\", router: \"0xAbA5B53b03eAfaD1C5fc8BD5Fc765fC85Bb3de67\"},\n 42161: {slug: \"arbitrum\", name: \"Arbitrum\", router: \"0x924F147c50eA59f5180a26031A8b65B2aA1e81Cd\"},\n 56: {slug: \"bsc\", name: \"BNB Smart Chain\", router: \"0x7f3D12BBaFb8955E51B3ab9588b34c8ad95BDA4e\"},\n 137: {slug: \"polygon\", name: \"Polygon\", router: \"0xbd4e6011F03355C2A377Fd9Af939322A7d0A1bC1\"},\n 130: {slug: \"unichain\", name: \"Unichain\", router: \"0xCba5574597Ad00EA250Fd106DAB4Fc7461949635\"},\n 4663: {slug: \"robinhood\", name: \"Robinhood Chain\", router: \"0x09215a470bD585E59EB3F4B612fBD2678131fF9e\"},\n});\n\n/** The router's native-coin sentinel (lib/NativeETH.sol); Fynd's API uses the zero address instead. */\nexport const ROUTER_NATIVE = \"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE\";\nconst ZERO = \"0x0000000000000000000000000000000000000000\";\n\nconst PARAMS = \"uint256 amountIn, address tokenIn, address tokenOut, uint256 expectedAmountOut, uint256 minAmountOut\";\nconst FEE = \"(uint32 clientFeeBps, address clientFeeReceiver, uint256 maxClientContribution, uint256 deadline, bytes clientSignature) clientFeeParams\";\nconst PERMIT = \"((address token, uint160 amount, uint48 expiration, uint48 nonce) details, address spender, uint256 sigDeadline) permitSingle, bytes signature\";\n\n/** The three entry points a wallet swap uses (plain `transferFrom` from the user). */\nexport const TYCHO_ROUTER_ABI = parseAbi([\n `function singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData) payable returns (uint256)`,\n `function sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n `function splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n]);\n\n/** Every swap entry point of the router (Permit2 and vault variants included) — for recognizing history. */\nexport const TYCHO_ROUTER_SELECTORS: ReadonlySet<string> = new Set(\n [\n `singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwapUsingVault(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swapData)`,\n `sequentialSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n `splitSwapPermit2(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n ].map((sig) => toFunctionSelector(`function ${sig}`)),\n);\n\nexport class FyndTransactionError extends Error {\n constructor(message: string) {\n super(`fynd: ${message}`);\n this.name = \"FyndTransactionError\";\n }\n}\n\nexport interface ExpectedSwap {\n chainId: number;\n /** ERC-20 address, or the zero address for the native coin. */\n tokenIn: string;\n tokenOut: string;\n amountIn: bigint;\n receiver: string;\n /** Fynd's gross `amount_out` (before the router fee and slippage). */\n expectedAmountOut: bigint;\n /** Fynd's `min_amount_received` — what the router enforces on-chain. */\n minAmountOut: bigint;\n}\n\nexport interface EncodedTx {\n to: string;\n value: bigint;\n data: string;\n}\n\nconst sameAddress = (a: string, b: string) => {\n try {\n return getAddress(a) === getAddress(b);\n } catch {\n return false;\n }\n};\nconst routerToken = (t: string) => (sameAddress(t, ZERO) ? ROUTER_NATIVE : t);\n\n/** Refuses (throws {@link FyndTransactionError}) unless `tx` is exactly the swap the wallet asked for. */\nexport function checkFyndTransaction(tx: EncodedTx, expected: ExpectedSwap): {method: \"singleSwap\" | \"sequentialSwap\" | \"splitSwap\"} {\n const chain = FYND_CHAINS[expected.chainId];\n if (!chain) throw new FyndTransactionError(`chain ${expected.chainId} has no pinned Tycho router`);\n // `tx.to` is the API's: named in the refusal only when it is an address (scan 2026-10-27 modules, F34).\n if (!sameAddress(tx.to, chain.router)) throw new FyndTransactionError(`the transaction targets ${/^0x[0-9a-fA-F]{40}$/.test(tx.to) ? tx.to : \"something that is not an address\"}, not the Tycho router on ${chain.name}`);\n if (sameAddress(expected.receiver, ZERO) || sameAddress(expected.receiver, chain.router)) throw new FyndTransactionError(\"invalid receiver\");\n if (expected.amountIn <= 0n || expected.minAmountOut <= 0n || expected.minAmountOut > expected.expectedAmountOut) {\n throw new FyndTransactionError(\"invalid quote amounts\");\n }\n const nativeIn = sameAddress(expected.tokenIn, ZERO);\n if (tx.value !== (nativeIn ? expected.amountIn : 0n)) throw new FyndTransactionError(\"native value does not match the input\");\n if (!/^0x(?:[0-9a-fA-F]{2}){4,}$/.test(tx.data)) throw new FyndTransactionError(\"malformed calldata\");\n\n let call: ReturnType<typeof decodeFunctionData<typeof TYCHO_ROUTER_ABI>>;\n try {\n call = decodeFunctionData({abi: TYCHO_ROUTER_ABI, data: tx.data as Hex});\n } catch {\n throw new FyndTransactionError(\"not a plain singleSwap / sequentialSwap / splitSwap call (Permit2 and vault variants are not used)\");\n }\n const a = call.args;\n const [amountIn, tokenIn, tokenOut, expectedAmountOut, minAmountOut] = a;\n const isSplit = call.functionName === \"splitSwap\";\n const receiver = (isSplit ? a[6] : a[5]) as string;\n const fee = (isSplit ? a[7] : a[6]) as {clientFeeBps: number; clientFeeReceiver: string; maxClientContribution: bigint; deadline: bigint; clientSignature: string};\n const swaps = (isSplit ? a[8] : a[7]) as string;\n if (amountIn !== expected.amountIn) throw new FyndTransactionError(`calldata spends ${amountIn}, not ${expected.amountIn}`);\n if (!sameAddress(tokenIn, routerToken(expected.tokenIn)) || !sameAddress(tokenOut, routerToken(expected.tokenOut))) {\n throw new FyndTransactionError(\"calldata swaps other tokens than requested\");\n }\n if (!sameAddress(receiver, expected.receiver)) throw new FyndTransactionError(`calldata pays ${receiver}, not ${expected.receiver}`);\n if (expectedAmountOut !== expected.expectedAmountOut || minAmountOut !== expected.minAmountOut) {\n throw new FyndTransactionError(\"calldata amounts do not match the quote\");\n }\n if (fee.clientFeeBps !== 0 || fee.maxClientContribution !== 0n || !sameAddress(fee.clientFeeReceiver, ZERO)) {\n throw new FyndTransactionError(\"calldata carries client-fee parameters — this module charges none\");\n }\n if (swaps === \"0x\" || (isSplit && (a[5] as bigint) < 2n)) throw new FyndTransactionError(\"empty or invalid route\");\n return {method: call.functionName};\n}\n\n/**\n * {@link checkFyndTransaction} on a remote quote, against the request this app sent: the pinned\n * router, the exact input, the requested tokens, the app's recipient, the quoted minimum and no\n * client fee. The reason it fails, or null.\n */\nexport function fyndVerifyQuote(params: QuoteParams, quote: Quote): string | null {\n try {\n const chainId = /^eip155:[0-9]+$/.test(params.sourceChain) ? Number(params.sourceChain.slice(7)) : NaN;\n const src = sourceAssetOf(params);\n if (!src) return `unknown source token ${params.sourceToken}`;\n const tx = quote.txRequest;\n if (!tx?.to || !tx.data) return \"the quote carries no router transaction\";\n checkFyndTransaction({to: tx.to, data: tx.data, value: BigInt(tx.value ?? \"0\")}, {\n chainId,\n tokenIn: src.contract ? getAddress(src.contract) : ZERO,\n tokenOut: /^0x0+$/.test(params.destToken) ? ZERO : getAddress(params.destToken),\n amountIn: BigInt(params.amountIn),\n receiver: getAddress(params.destAddress),\n expectedAmountOut: BigInt(quote.providerData?.gross ?? \"0\"),\n minAmountOut: BigInt(quote.minDestAmount),\n });\n return null;\n } catch (e) {\n return e instanceof Error ? e.message.replace(/^fynd: /, \"\") : String(e);\n }\n}\n\n/**\n * The Tycho router a Fynd swap calls and approves on a chain, and {@link fyndVerifyQuote} — the\n * app's side of `remoteProvider({name: \"fynd\", baseUrl, ...fyndPins})`, so a backend's quote is\n * checked against these routers and the app's own request, never against itself (scan 2026-10-27\n * modules, F9).\n */\nexport const fyndPins: RemotePins = Object.freeze({\n callTargets: (chain: string): readonly string[] => fyndRouterFor(chain),\n spenders: (chain: string): readonly string[] => fyndRouterFor(chain),\n verifyQuote: fyndVerifyQuote,\n});\nfunction fyndRouterFor(chain: string): readonly string[] {\n const id = /^eip155:[0-9]+$/.test(chain) ? Number(chain.slice(7)) : NaN;\n const c = Object.prototype.hasOwnProperty.call(FYND_CHAINS, id) ? FYND_CHAINS[id] : undefined;\n return c ? [c.router] : [];\n}\n"],"mappings":"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","names":["import_viem","import_util","import_util","ZERO"]}
|
|
1
|
+
{"version":3,"sources":["../src/provider.ts","../src/activity.ts","../src/router.ts"],"sourcesContent":["import {getAddress} from \"viem\";\nimport {evmRef, isEvm, timeoutSignal, sourceAssetOf} from \"@anywalletkit/protocol-swap2-core/util\";\nimport type {Asset, CrossChainProvider, IntentRef, IntentStatus, Quote, QuoteParams, UnsignedTx} from \"@anywalletkit/protocol-swap2-core/types\";\nimport {describeFyndActivity} from \"./activity.js\";\nimport {FYND_CHAINS, checkFyndTransaction} from \"./router.js\";\n\n/**\n * Fynd — Propeller Heads' DEX aggregator (docs.fynd.xyz) as a same-chain\n * swap2 provider: exact-input swaps on Ethereum, Base, Arbitrum, BNB Chain,\n * Polygon, Unichain and Robinhood Chain, priced over the pools Tycho indexes\n * and settled through TychoRouterV3.\n *\n * Fynd outputs are licensed under the Fynd License: a swap built from a Fynd\n * quote must settle through the Tycho router with its fees intact — which is\n * the only transaction this provider builds (see README).\n *\n * Two backends: the hosted API (`apiKey` — keep it on a server, behind\n * `remoteProvider`) or your own `fynd serve` per chain (`selfHosted`).\n */\n\nconst ZERO = \"0x0000000000000000000000000000000000000000\";\nconst DEFAULT_BASE_URL = \"https://fynd-api.propellerheads.xyz\";\nconst DEFAULT_SLIPPAGE_BPS = 50;\n/** A Fynd quote is priced at one block; the router enforces the minimum, the engine re-checks this TTL before broadcast. */\nconst DEFAULT_TTL_MS = 30_000;\n/**\n * Liquidity families left out of routing by default. 2026-10-24 on the hosted\n * API: every Ethereum route through `uniswap_v4_hooks` was quoted ~0.6–1 %\n * above the market and reverted when simulated (4 of 4 native-in pairs,\n * also USDT → USDC), while the same pairs without it executed (8 of 8 across\n * Ethereum, Base and Arbitrum). Override with `excludeProtocols`.\n */\nexport const FYND_DEFAULT_EXCLUDED_PROTOCOLS: readonly string[] = Object.freeze([\"uniswap_v4_hooks\"]);\nconst QUOTE_FAILURES: Readonly<Record<string, string>> = {\n no_route_found: \"no route for this pair\",\n insufficient_liquidity: \"not enough liquidity for this amount\",\n timeout: \"the solver timed out\",\n not_ready: \"the solver is still syncing\",\n price_check_failed: \"the price guard rejected the route\",\n encoding_failed: \"could not encode the swap (fees + slippage leave nothing)\",\n};\n\nexport interface FyndProviderConfig {\n enabled?: boolean;\n /** Hosted API key (raw `Authorization` value). Server-side only. */\n apiKey?: string;\n /** Hosted API base URL; requests go to `{baseUrl}/v1/{chain}/quote`. */\n baseUrl?: string;\n /** Your own Fynd servers per EVM chain id; requests go to `{url}/v1/quote`, no key. Takes precedence over the hosted API for that chain. */\n selfHosted?: Readonly<Record<number, string>>;\n /** JSON-RPC endpoints per chain id: pre-broadcast simulation and receipts. Optional. */\n rpcUrls?: Readonly<Record<number, readonly string[]>>;\n defaultSlippageBps?: number;\n quoteTtlMs?: number;\n timeoutMs?: number;\n /** Protocol names or family prefixes ending in \":\" Fynd must not route through. Default {@link FYND_DEFAULT_EXCLUDED_PROTOCOLS}. */\n excludeProtocols?: readonly string[];\n}\n\ninterface FyndOrderQuote {\n status?: string;\n amount_in?: string;\n amount_out?: string;\n gas_estimate?: string;\n price_impact_bps?: number | null;\n route?: {swaps?: {protocol?: string}[]};\n fee_breakdown?: {router_fee?: string; client_fee?: string; max_slippage?: string; min_amount_received?: string};\n transaction?: {to?: string; value?: string; data?: string};\n}\n\nconst uint = (v: unknown): bigint | null => (typeof v === \"string\" && /^\\d+$/.test(v) ? BigInt(v) : null);\n/** A protocol name from the API as it may appear in a refusal or the quote: at most 40 characters (scan 2026-10-27 modules, F34). */\nconst protocolName = (p: string): string => (p.length > 40 ? `${p.slice(0, 40)}…` : p);\n/** Hex or an address, compared without case (length first: the client's side may be a megabyte). */\nconst sameText = (a: string, b: string): boolean => a.length === b.length && a.toLowerCase() === b.toLowerCase();\nconst sameAmount = (a: string, b: string): boolean => /^\\d{1,80}$/.test(a) && /^\\d{1,80}$/.test(b) && BigInt(a) === BigInt(b);\n\nexport function fyndProvider(config: FyndProviderConfig): CrossChainProvider {\n const baseUrl = (config.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/+$/, \"\");\n const slippageDefault = config.defaultSlippageBps ?? DEFAULT_SLIPPAGE_BPS;\n const ttl = config.quoteTtlMs ?? DEFAULT_TTL_MS;\n const timeoutMs = config.timeoutMs ?? 10_000;\n const excluded = [...(config.excludeProtocols ?? FYND_DEFAULT_EXCLUDED_PROTOCOLS)];\n const isExcluded = (protocol: string) => excluded.some((x) => (x.endsWith(\":\") ? protocol.startsWith(x) : protocol === x));\n\n const endpoint = (chainId: number): {url: string; headers: Record<string, string>} | null => {\n const chain = FYND_CHAINS[chainId];\n if (!chain) return null;\n const own = config.selfHosted?.[chainId];\n if (own) return {url: `${own.replace(/\\/+$/, \"\")}/v1/quote`, headers: {}};\n if (!config.apiKey) return null;\n return {url: `${baseUrl}/v1/${chain.slug}/quote`, headers: {authorization: config.apiKey}};\n };\n const chainIdOf = (caip2: string): number | null => {\n if (!isEvm(caip2)) return null;\n const id = evmRef(caip2);\n return endpoint(id) ? id : null;\n };\n\n async function rpc(chainId: number, method: string, params: unknown[]): Promise<unknown> {\n let lastErr: unknown;\n for (const url of config.rpcUrls?.[chainId] ?? []) {\n try {\n const res = await fetch(url, {method: \"POST\", headers: {\"content-type\": \"application/json\"}, body: JSON.stringify({jsonrpc: \"2.0\", id: 1, method, params}), signal: timeoutSignal(12_000)});\n if (!res.ok) throw new Error(`HTTP ${res.status}`);\n const body = (await res.json()) as {result?: unknown; error?: {message?: string}};\n if (body.error) throw Object.assign(new Error(body.error.message ?? \"rpc error\"), {rpc: true});\n return body.result ?? null;\n } catch (e) {\n if ((e as {rpc?: boolean}).rpc) throw e;\n lastErr = e;\n }\n }\n throw new Error(`fynd: no RPC answered on chain ${chainId} (${lastErr instanceof Error ? lastErr.message : String(lastErr)})`);\n }\n\n return {\n name: \"fynd\",\n describeActivity: describeFyndActivity,\n supportsSameChain: true,\n enabled: () => config.enabled ?? true,\n supportsChain: (caip2: string) => chainIdOf(caip2) !== null,\n supportsChainPair: (from: string, to: string) => from === to && chainIdOf(from) !== null,\n supportsRoute: (from: Asset, to: Asset) => from.chain === to.chain && chainIdOf(from.chain) !== null,\n\n async getQuote(params: QuoteParams, signal?: AbortSignal): Promise<Quote | null> {\n const refuse = (why: string): null => {\n params.onRefusal?.(`fynd: ${why}`);\n return null;\n };\n if (params.flex) return refuse(\"amount-flexible deposits are not supported\");\n if (params.sourceChain !== params.destChain) return refuse(\"same-chain swaps only\");\n const chainId = chainIdOf(params.sourceChain);\n if (chainId === null) return refuse(`Fynd is not configured for ${params.sourceChain}`);\n const ep = endpoint(chainId)!;\n const src = sourceAssetOf(params);\n if (!src) return refuse(`unknown source token ${params.sourceToken}`);\n const tokenIn = src.contract ? getAddress(src.contract) : ZERO;\n const nativeOut = /^0x0+$/.test(params.destToken);\n if (!nativeOut && !/^0x[0-9a-fA-F]{40}$/.test(params.destToken)) return refuse(\"destination token must be an EVM address\");\n const tokenOut = nativeOut ? ZERO : getAddress(params.destToken);\n if (tokenIn === tokenOut) return refuse(\"same token in and out\");\n if (!/^0x[0-9a-fA-F]{40}$/.test(params.destAddress)) return refuse(\"recipient must be an EVM address\");\n const receiver = getAddress(params.destAddress);\n const sender = getAddress(params.refundAddress && /^0x[0-9a-fA-F]{40}$/.test(params.refundAddress) ? params.refundAddress : params.destAddress);\n let amountIn: bigint;\n try {\n amountIn = BigInt(params.amountIn);\n } catch {\n return refuse(\"amount is not an integer\");\n }\n if (amountIn <= 0n) return refuse(\"amount must be positive\");\n const bps = params.slippageBps ?? slippageDefault;\n if (!Number.isInteger(bps) || bps <= 0 || bps >= 10_000) return refuse(\"invalid slippage\");\n\n const body = {\n orders: [{token_in: tokenIn, token_out: tokenOut, amount: amountIn.toString(), side: \"sell\", sender, receiver}],\n options: {\n timeout_ms: Math.min(timeoutMs, 8_000),\n encoding_options: {slippage: (bps / 10_000).toString(), transfer_type: \"transfer_from\"},\n ...(excluded.length ? {route_filter: {exclude_protocols: excluded}} : {}),\n },\n };\n let res: Response;\n try {\n res = await fetch(ep.url, {method: \"POST\", headers: {\"content-type\": \"application/json\", ...ep.headers}, body: JSON.stringify(body), signal: signal ?? timeoutSignal(timeoutMs)});\n } catch (e) {\n return refuse(`API unreachable (${e instanceof Error ? e.name : \"error\"})`);\n }\n if (res.status === 401 || res.status === 403) return refuse(`API key rejected (HTTP ${res.status})`);\n if (res.status === 429) return refuse(\"rate limited (HTTP 429)\");\n if (res.status === 422) return refuse(\"no route for this pair\");\n if (!res.ok) return refuse(`API answered HTTP ${res.status}`);\n let parsed: {orders?: FyndOrderQuote[]};\n try {\n parsed = (await res.json()) as {orders?: FyndOrderQuote[]};\n } catch {\n return refuse(\"malformed API response\");\n }\n if (!Array.isArray(parsed.orders) || parsed.orders.length !== 1) return refuse(\"malformed API response (order count)\");\n const q = parsed.orders[0]!;\n if (q.status !== \"success\") return refuse(QUOTE_FAILURES[q.status ?? \"\"] ?? `quote status ${String(q.status).slice(0, 40)}`);\n\n const quotedIn = uint(q.amount_in);\n const gross = uint(q.amount_out);\n const routerFee = uint(q.fee_breakdown?.router_fee);\n const clientFee = uint(q.fee_breakdown?.client_fee);\n const maxSlippage = uint(q.fee_breakdown?.max_slippage);\n const minOut = uint(q.fee_breakdown?.min_amount_received);\n const value = uint(q.transaction?.value ?? \"\");\n if (quotedIn === null || gross === null || routerFee === null || clientFee === null || maxSlippage === null || minOut === null || value === null) {\n return refuse(\"malformed quote amounts\");\n }\n if (quotedIn !== amountIn) return refuse(`quoted ${quotedIn}, not the requested ${amountIn}`);\n if (clientFee !== 0n) return refuse(\"the quote carries a client fee\");\n if (routerFee + clientFee + maxSlippage + minOut !== gross) return refuse(\"fee breakdown does not add up to the output\");\n if (params.minAmountOut !== undefined && minOut < BigInt(params.minAmountOut)) return refuse(`floor breach (min=${minOut} < floor=${params.minAmountOut})`);\n const tx = {to: q.transaction?.to ?? \"\", value, data: q.transaction?.data ?? \"\"};\n try {\n checkFyndTransaction(tx, {chainId, tokenIn, tokenOut, amountIn, receiver, expectedAmountOut: gross, minAmountOut: minOut});\n } catch (e) {\n return refuse(e instanceof Error ? e.message.replace(/^fynd: /, \"\") : String(e));\n }\n const allProtocols = [...new Set((q.route?.swaps ?? []).map((s) => s.protocol).filter((p): p is string => typeof p === \"string\"))];\n const banned = allProtocols.find(isExcluded);\n if (banned) return refuse(`the route goes through ${protocolName(banned)}, which this wallet excludes`);\n // Native input needs no allowance: the exact transaction can be executed\n // against the latest block right now, so a route that would revert never\n // enters the auction. (ERC-20 input is simulated in preflightDeposit,\n // once the approval exists.)\n if (tokenIn === ZERO && config.rpcUrls?.[chainId]?.length) {\n try {\n await rpc(chainId, \"eth_call\", [{from: sender, to: tx.to, data: tx.data, value: `0x${value.toString(16)}`}, \"latest\", {[sender]: {balance: `0x${(value + 10n ** 18n).toString(16)}`}}]);\n } catch (e) {\n if ((e as {rpc?: boolean}).rpc) return refuse(`the route does not execute on-chain right now (simulation reverted via ${allProtocols.slice(0, 6).map(protocolName).join(\", \") || \"?\"})`);\n // no RPC answered: the router still enforces the minimum\n }\n }\n const gas = uint(q.gas_estimate) ?? 0n;\n const protocols = allProtocols.slice(0, 6).map(protocolName);\n return {\n provider: \"fynd\",\n intentId: `fynd:${chainId}`,\n depositAddress: \"\",\n depositMemo: null,\n txRequest: {\n to: getAddress(tx.to),\n data: tx.data,\n value: value.toString(),\n chainId,\n // the router's own overhead on top of Fynd's route estimate\n gasLimit: ((gas * 13n) / 10n + 80_000n).toString(),\n ...(tokenIn === ZERO ? {} : {approvalAddress: getAddress(tx.to)}),\n },\n sourceAmount: amountIn.toString(),\n // what the receiver gets at the quoted price: gross minus the router fee\n destAmount: (gross - routerFee).toString(),\n minDestAmount: minOut.toString(),\n feeUsd: 0,\n etaSeconds: 15,\n reliability: 0.96,\n validUntilMs: Date.now() + ttl,\n providerData: {\n minOut: minOut.toString(),\n sender,\n routerFee: routerFee.toString(),\n gross: gross.toString(),\n protocols: protocols.join(\",\"),\n ...(typeof q.price_impact_bps === \"number\" ? {priceImpactBps: String(q.price_impact_bps)} : {}),\n },\n };\n },\n\n /**\n * Simulate the exact transaction at the latest block (allowance already in place); refuse when it would revert.\n * Only the quote's own transaction is simulated: behind a backend `tx` is the client's and only the quote is\n * sealed, so a client's call (any target, a megabyte of calldata) never reaches this RPC (scan 2026-10-27 modules, F24).\n */\n async preflightDeposit(quote: Quote, tx: UnsignedTx): Promise<void> {\n const chainId = Number(quote.intentId.split(\":\")[1]);\n const sender = quote.providerData?.sender;\n if (!FYND_CHAINS[chainId] || !config.rpcUrls?.[chainId]?.length || !tx.data || !sender) return;\n const quoted = quote.txRequest;\n if (!quoted?.to || !quoted.data || !sameText(tx.to, quoted.to) || !sameText(tx.data, quoted.data) || !sameAmount(tx.value || \"0\", quoted.value ?? \"0\")) {\n throw new Error(\"fynd: the transaction is not the quoted swap. Nothing was sent; refresh the quote.\");\n }\n try {\n await rpc(chainId, \"eth_call\", [{from: sender, to: quoted.to, data: quoted.data, value: `0x${BigInt(quoted.value ?? \"0\").toString(16)}`}, \"latest\"]);\n } catch (e) {\n if ((e as {rpc?: boolean}).rpc) {\n throw new Error(`fynd: the swap would revert now (${e instanceof Error ? e.message.slice(0, 120) : \"revert\"}) — the price moved past your minimum. Nothing was sent; refresh the quote.`);\n }\n // no RPC answered: leave it to the router's own minimum\n }\n },\n\n async buildDeposit(quote: Quote): Promise<UnsignedTx> {\n const tx = quote.txRequest;\n if (!tx?.to || !tx.data) throw new Error(\"swap2: Fynd quote has no transaction request\");\n return {chain: \"\", to: tx.to, value: tx.value ?? \"0\", data: tx.data, ...(tx.chainId !== undefined ? {chainId: tx.chainId} : {}), ...(tx.gasLimit ? {gasLimit: tx.gasLimit} : {}), ...(tx.approvalAddress ? {approvalAddress: tx.approvalAddress} : {})};\n },\n\n async trackIntent(ref: IntentRef): Promise<IntentStatus> {\n if (!ref.sourceTxHash) return {state: \"pending\"};\n const chainId = Number(ref.intentId.split(\":\")[1]);\n if (!FYND_CHAINS[chainId] || !config.rpcUrls?.[chainId]?.length) return {state: \"pending\"};\n try {\n const receipt = (await rpc(chainId, \"eth_getTransactionReceipt\", [ref.sourceTxHash])) as {status?: string} | null;\n if (!receipt) return {state: \"pending\"};\n return receipt.status === \"0x1\" ? {state: \"completed\", destTxHash: ref.sourceTxHash} : {state: \"failed\"};\n } catch {\n return {state: \"pending\"};\n }\n },\n };\n}\n","/**\n * Activity recognition for Fynd: a swap entry point of the pinned\n * TychoRouterV3 on a chain the module serves → `swap`, \"Swapped A → B\" from\n * the owner's net transfers.\n */\nimport type {ActivityItem, ChainTx, DescribeActivityContext} from \"@anywalletkit/core\";\nimport {chainLabel, describeSwapLike, matchesCall} from \"@anywalletkit/protocol-swap2-core/activity\";\nimport {evmRef, isEvm} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {FYND_CHAINS, TYCHO_ROUTER_SELECTORS} from \"./router.js\";\n\nexport const FYND_MODULE = \"@anywalletkit/protocol-swap2-fynd\";\n\nexport function describeFyndActivity(tx: ChainTx, ctx: DescribeActivityContext): ActivityItem | null {\n if (!isEvm(tx.chain)) return null;\n const chain = FYND_CHAINS[evmRef(tx.chain)];\n if (!chain) return null;\n if (!matchesCall(tx, [chain.router], TYCHO_ROUTER_SELECTORS)) return null;\n return describeSwapLike(tx, ctx, {protocol: FYND_MODULE, subtitle: `Fynd · ${chainLabel(tx.chain)}`, kind: \"swap\"});\n}\n","/**\n * TychoRouterV3 — the only contract a Fynd quote may settle through (Fynd\n * License §3: every settlement of a Fynd output goes through the router in\n * Propeller Heads' registry). This file pins the router per chain and checks\n * Fynd's encoded transaction against the request before anything is approved\n * or sent: right contract, right method, the exact input, the requested\n * tokens, the user as receiver, the quoted output and minimum, and no client\n * fee. The route inside `swaps` is opaque and is not inspected here — the\n * router itself reverts below `minAmountOut`.\n */\nimport type {Quote, QuoteParams, RemotePins} from \"@anywalletkit/protocol-swap2-core/types\";\nimport {sourceAssetOf} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {decodeFunctionData, getAddress, parseAbi, toFunctionSelector, type Hex} from \"viem\";\n\n/** Router registry: github.com/propeller-heads/tycho crates/tycho-execution/config/deployment_registry.json (TychoRouterV3, effective 2026-09-01). */\nexport const FYND_CHAINS: Readonly<Record<number, {slug: string; name: string; router: `0x${string}`}>> = Object.freeze({\n 1: {slug: \"ethereum\", name: \"Ethereum\", router: \"0x1644D2477f809cc2C71bCCFd6Dc9497E3F83210d\"},\n 8453: {slug: \"base\", name: \"Base\", router: \"0xAbA5B53b03eAfaD1C5fc8BD5Fc765fC85Bb3de67\"},\n 42161: {slug: \"arbitrum\", name: \"Arbitrum\", router: \"0x924F147c50eA59f5180a26031A8b65B2aA1e81Cd\"},\n 56: {slug: \"bsc\", name: \"BNB Smart Chain\", router: \"0x7f3D12BBaFb8955E51B3ab9588b34c8ad95BDA4e\"},\n 137: {slug: \"polygon\", name: \"Polygon\", router: \"0xbd4e6011F03355C2A377Fd9Af939322A7d0A1bC1\"},\n 130: {slug: \"unichain\", name: \"Unichain\", router: \"0xCba5574597Ad00EA250Fd106DAB4Fc7461949635\"},\n 4663: {slug: \"robinhood\", name: \"Robinhood Chain\", router: \"0x09215a470bD585E59EB3F4B612fBD2678131fF9e\"},\n});\n\n/** The router's native-coin sentinel (lib/NativeETH.sol); Fynd's API uses the zero address instead. */\nexport const ROUTER_NATIVE = \"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE\";\nconst ZERO = \"0x0000000000000000000000000000000000000000\";\n\nconst PARAMS = \"uint256 amountIn, address tokenIn, address tokenOut, uint256 expectedAmountOut, uint256 minAmountOut\";\nconst FEE = \"(uint32 clientFeeBps, address clientFeeReceiver, uint256 maxClientContribution, uint256 deadline, bytes clientSignature) clientFeeParams\";\nconst PERMIT = \"((address token, uint160 amount, uint48 expiration, uint48 nonce) details, address spender, uint256 sigDeadline) permitSingle, bytes signature\";\n\n/** The three entry points a wallet swap uses (plain `transferFrom` from the user). */\nexport const TYCHO_ROUTER_ABI = parseAbi([\n `function singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData) payable returns (uint256)`,\n `function sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n `function splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n]);\n\n/** Every swap entry point of the router (Permit2 and vault variants included) — for recognizing history. */\nexport const TYCHO_ROUTER_SELECTORS: ReadonlySet<string> = new Set(\n [\n `singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwapUsingVault(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swapData)`,\n `sequentialSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n `splitSwapPermit2(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n ].map((sig) => toFunctionSelector(`function ${sig}`)),\n);\n\nexport class FyndTransactionError extends Error {\n constructor(message: string) {\n super(`fynd: ${message}`);\n this.name = \"FyndTransactionError\";\n }\n}\n\nexport interface ExpectedSwap {\n chainId: number;\n /** ERC-20 address, or the zero address for the native coin. */\n tokenIn: string;\n tokenOut: string;\n amountIn: bigint;\n receiver: string;\n /** Fynd's gross `amount_out` (before the router fee and slippage). */\n expectedAmountOut: bigint;\n /** Fynd's `min_amount_received` — what the router enforces on-chain. */\n minAmountOut: bigint;\n}\n\nexport interface EncodedTx {\n to: string;\n value: bigint;\n data: string;\n}\n\nconst sameAddress = (a: string, b: string) => {\n try {\n return getAddress(a) === getAddress(b);\n } catch {\n return false;\n }\n};\nconst routerToken = (t: string) => (sameAddress(t, ZERO) ? ROUTER_NATIVE : t);\n\n/** Refuses (throws {@link FyndTransactionError}) unless `tx` is exactly the swap the wallet asked for. */\nexport function checkFyndTransaction(tx: EncodedTx, expected: ExpectedSwap): {method: \"singleSwap\" | \"sequentialSwap\" | \"splitSwap\"} {\n const chain = FYND_CHAINS[expected.chainId];\n if (!chain) throw new FyndTransactionError(`chain ${expected.chainId} has no pinned Tycho router`);\n // `tx.to` is the API's: named in the refusal only when it is an address (scan 2026-10-27 modules, F34).\n if (!sameAddress(tx.to, chain.router)) throw new FyndTransactionError(`the transaction targets ${/^0x[0-9a-fA-F]{40}$/.test(tx.to) ? tx.to : \"something that is not an address\"}, not the Tycho router on ${chain.name}`);\n if (sameAddress(expected.receiver, ZERO) || sameAddress(expected.receiver, chain.router)) throw new FyndTransactionError(\"invalid receiver\");\n if (expected.amountIn <= 0n || expected.minAmountOut <= 0n || expected.minAmountOut > expected.expectedAmountOut) {\n throw new FyndTransactionError(\"invalid quote amounts\");\n }\n const nativeIn = sameAddress(expected.tokenIn, ZERO);\n if (tx.value !== (nativeIn ? expected.amountIn : 0n)) throw new FyndTransactionError(\"native value does not match the input\");\n if (!/^0x(?:[0-9a-fA-F]{2}){4,}$/.test(tx.data)) throw new FyndTransactionError(\"malformed calldata\");\n\n let call: ReturnType<typeof decodeFunctionData<typeof TYCHO_ROUTER_ABI>>;\n try {\n call = decodeFunctionData({abi: TYCHO_ROUTER_ABI, data: tx.data as Hex});\n } catch {\n throw new FyndTransactionError(\"not a plain singleSwap / sequentialSwap / splitSwap call (Permit2 and vault variants are not used)\");\n }\n const a = call.args;\n const [amountIn, tokenIn, tokenOut, expectedAmountOut, minAmountOut] = a;\n const isSplit = call.functionName === \"splitSwap\";\n const receiver = (isSplit ? a[6] : a[5]) as string;\n const fee = (isSplit ? a[7] : a[6]) as {clientFeeBps: number; clientFeeReceiver: string; maxClientContribution: bigint; deadline: bigint; clientSignature: string};\n const swaps = (isSplit ? a[8] : a[7]) as string;\n if (amountIn !== expected.amountIn) throw new FyndTransactionError(`calldata spends ${amountIn}, not ${expected.amountIn}`);\n if (!sameAddress(tokenIn, routerToken(expected.tokenIn)) || !sameAddress(tokenOut, routerToken(expected.tokenOut))) {\n throw new FyndTransactionError(\"calldata swaps other tokens than requested\");\n }\n if (!sameAddress(receiver, expected.receiver)) throw new FyndTransactionError(`calldata pays ${receiver}, not ${expected.receiver}`);\n if (expectedAmountOut !== expected.expectedAmountOut || minAmountOut !== expected.minAmountOut) {\n throw new FyndTransactionError(\"calldata amounts do not match the quote\");\n }\n if (fee.clientFeeBps !== 0 || fee.maxClientContribution !== 0n || !sameAddress(fee.clientFeeReceiver, ZERO)) {\n throw new FyndTransactionError(\"calldata carries client-fee parameters — this module charges none\");\n }\n if (swaps === \"0x\" || (isSplit && (a[5] as bigint) < 2n)) throw new FyndTransactionError(\"empty or invalid route\");\n return {method: call.functionName};\n}\n\n/**\n * {@link checkFyndTransaction} on a remote quote, against the request this app sent: the pinned\n * router, one chain, the exact input, the requested tokens, the app's recipient, the quoted minimum and\n * no client fee. The reason it fails, or null.\n */\nexport function fyndVerifyQuote(params: QuoteParams, quote: Quote): string | null {\n // Fynd swaps on one chain; a source-chain swap must not answer a cross-chain request (reaudit 2026-10-30, #3).\n if (params.sourceChain !== params.destChain) return \"same-chain swaps only\";\n try {\n const chainId = /^eip155:[0-9]+$/.test(params.sourceChain) ? Number(params.sourceChain.slice(7)) : NaN;\n const src = sourceAssetOf(params);\n if (!src) return `unknown source token ${params.sourceToken}`;\n const tx = quote.txRequest;\n if (!tx?.to || !tx.data) return \"the quote carries no router transaction\";\n checkFyndTransaction({to: tx.to, data: tx.data, value: BigInt(tx.value ?? \"0\")}, {\n chainId,\n tokenIn: src.contract ? getAddress(src.contract) : ZERO,\n tokenOut: /^0x0+$/.test(params.destToken) ? ZERO : getAddress(params.destToken),\n amountIn: BigInt(params.amountIn),\n receiver: getAddress(params.destAddress),\n expectedAmountOut: BigInt(quote.providerData?.gross ?? \"0\"),\n minAmountOut: BigInt(quote.minDestAmount),\n });\n return null;\n } catch (e) {\n return e instanceof Error ? e.message.replace(/^fynd: /, \"\") : String(e);\n }\n}\n\n/**\n * The Tycho router a Fynd swap calls and approves on a chain, and {@link fyndVerifyQuote} — the\n * app's side of `remoteProvider({name: \"fynd\", baseUrl, ...fyndPins})`, so a backend's quote is\n * checked against these routers and the app's own request, never against itself (scan 2026-10-27\n * modules, F9).\n */\nexport const fyndPins: RemotePins = Object.freeze({\n callTargets: (chain: string): readonly string[] => fyndRouterFor(chain),\n spenders: (chain: string): readonly string[] => fyndRouterFor(chain),\n verifyQuote: fyndVerifyQuote,\n});\nfunction fyndRouterFor(chain: string): readonly string[] {\n const id = /^eip155:[0-9]+$/.test(chain) ? Number(chain.slice(7)) : NaN;\n const c = Object.prototype.hasOwnProperty.call(FYND_CHAINS, id) ? FYND_CHAINS[id] : undefined;\n return c ? [c.router] : [];\n}\n"],"mappings":"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","names":["import_viem","import_util","import_util","ZERO"]}
|
package/dist/provider.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
FYND_DEFAULT_EXCLUDED_PROTOCOLS,
|
|
3
3
|
fyndProvider
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-4BCEAXID.js";
|
|
5
|
+
import "./chunk-PF2NGOA5.js";
|
|
6
|
+
import "./chunk-J6XM7WEG.js";
|
|
7
7
|
export {
|
|
8
8
|
FYND_DEFAULT_EXCLUDED_PROTOCOLS,
|
|
9
9
|
fyndProvider
|
package/dist/router.cjs
CHANGED
|
@@ -116,6 +116,7 @@ function checkFyndTransaction(tx, expected) {
|
|
|
116
116
|
return { method: call.functionName };
|
|
117
117
|
}
|
|
118
118
|
function fyndVerifyQuote(params, quote) {
|
|
119
|
+
if (params.sourceChain !== params.destChain) return "same-chain swaps only";
|
|
119
120
|
try {
|
|
120
121
|
const chainId = /^eip155:[0-9]+$/.test(params.sourceChain) ? Number(params.sourceChain.slice(7)) : NaN;
|
|
121
122
|
const src = (0, import_util.sourceAssetOf)(params);
|
package/dist/router.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/router.ts"],"sourcesContent":["/**\n * TychoRouterV3 — the only contract a Fynd quote may settle through (Fynd\n * License §3: every settlement of a Fynd output goes through the router in\n * Propeller Heads' registry). This file pins the router per chain and checks\n * Fynd's encoded transaction against the request before anything is approved\n * or sent: right contract, right method, the exact input, the requested\n * tokens, the user as receiver, the quoted output and minimum, and no client\n * fee. The route inside `swaps` is opaque and is not inspected here — the\n * router itself reverts below `minAmountOut`.\n */\nimport type {Quote, QuoteParams, RemotePins} from \"@anywalletkit/protocol-swap2-core/types\";\nimport {sourceAssetOf} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {decodeFunctionData, getAddress, parseAbi, toFunctionSelector, type Hex} from \"viem\";\n\n/** Router registry: github.com/propeller-heads/tycho crates/tycho-execution/config/deployment_registry.json (TychoRouterV3, effective 2026-09-01). */\nexport const FYND_CHAINS: Readonly<Record<number, {slug: string; name: string; router: `0x${string}`}>> = Object.freeze({\n 1: {slug: \"ethereum\", name: \"Ethereum\", router: \"0x1644D2477f809cc2C71bCCFd6Dc9497E3F83210d\"},\n 8453: {slug: \"base\", name: \"Base\", router: \"0xAbA5B53b03eAfaD1C5fc8BD5Fc765fC85Bb3de67\"},\n 42161: {slug: \"arbitrum\", name: \"Arbitrum\", router: \"0x924F147c50eA59f5180a26031A8b65B2aA1e81Cd\"},\n 56: {slug: \"bsc\", name: \"BNB Smart Chain\", router: \"0x7f3D12BBaFb8955E51B3ab9588b34c8ad95BDA4e\"},\n 137: {slug: \"polygon\", name: \"Polygon\", router: \"0xbd4e6011F03355C2A377Fd9Af939322A7d0A1bC1\"},\n 130: {slug: \"unichain\", name: \"Unichain\", router: \"0xCba5574597Ad00EA250Fd106DAB4Fc7461949635\"},\n 4663: {slug: \"robinhood\", name: \"Robinhood Chain\", router: \"0x09215a470bD585E59EB3F4B612fBD2678131fF9e\"},\n});\n\n/** The router's native-coin sentinel (lib/NativeETH.sol); Fynd's API uses the zero address instead. */\nexport const ROUTER_NATIVE = \"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE\";\nconst ZERO = \"0x0000000000000000000000000000000000000000\";\n\nconst PARAMS = \"uint256 amountIn, address tokenIn, address tokenOut, uint256 expectedAmountOut, uint256 minAmountOut\";\nconst FEE = \"(uint32 clientFeeBps, address clientFeeReceiver, uint256 maxClientContribution, uint256 deadline, bytes clientSignature) clientFeeParams\";\nconst PERMIT = \"((address token, uint160 amount, uint48 expiration, uint48 nonce) details, address spender, uint256 sigDeadline) permitSingle, bytes signature\";\n\n/** The three entry points a wallet swap uses (plain `transferFrom` from the user). */\nexport const TYCHO_ROUTER_ABI = parseAbi([\n `function singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData) payable returns (uint256)`,\n `function sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n `function splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n]);\n\n/** Every swap entry point of the router (Permit2 and vault variants included) — for recognizing history. */\nexport const TYCHO_ROUTER_SELECTORS: ReadonlySet<string> = new Set(\n [\n `singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwapUsingVault(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swapData)`,\n `sequentialSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n `splitSwapPermit2(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n ].map((sig) => toFunctionSelector(`function ${sig}`)),\n);\n\nexport class FyndTransactionError extends Error {\n constructor(message: string) {\n super(`fynd: ${message}`);\n this.name = \"FyndTransactionError\";\n }\n}\n\nexport interface ExpectedSwap {\n chainId: number;\n /** ERC-20 address, or the zero address for the native coin. */\n tokenIn: string;\n tokenOut: string;\n amountIn: bigint;\n receiver: string;\n /** Fynd's gross `amount_out` (before the router fee and slippage). */\n expectedAmountOut: bigint;\n /** Fynd's `min_amount_received` — what the router enforces on-chain. */\n minAmountOut: bigint;\n}\n\nexport interface EncodedTx {\n to: string;\n value: bigint;\n data: string;\n}\n\nconst sameAddress = (a: string, b: string) => {\n try {\n return getAddress(a) === getAddress(b);\n } catch {\n return false;\n }\n};\nconst routerToken = (t: string) => (sameAddress(t, ZERO) ? ROUTER_NATIVE : t);\n\n/** Refuses (throws {@link FyndTransactionError}) unless `tx` is exactly the swap the wallet asked for. */\nexport function checkFyndTransaction(tx: EncodedTx, expected: ExpectedSwap): {method: \"singleSwap\" | \"sequentialSwap\" | \"splitSwap\"} {\n const chain = FYND_CHAINS[expected.chainId];\n if (!chain) throw new FyndTransactionError(`chain ${expected.chainId} has no pinned Tycho router`);\n // `tx.to` is the API's: named in the refusal only when it is an address (scan 2026-10-27 modules, F34).\n if (!sameAddress(tx.to, chain.router)) throw new FyndTransactionError(`the transaction targets ${/^0x[0-9a-fA-F]{40}$/.test(tx.to) ? tx.to : \"something that is not an address\"}, not the Tycho router on ${chain.name}`);\n if (sameAddress(expected.receiver, ZERO) || sameAddress(expected.receiver, chain.router)) throw new FyndTransactionError(\"invalid receiver\");\n if (expected.amountIn <= 0n || expected.minAmountOut <= 0n || expected.minAmountOut > expected.expectedAmountOut) {\n throw new FyndTransactionError(\"invalid quote amounts\");\n }\n const nativeIn = sameAddress(expected.tokenIn, ZERO);\n if (tx.value !== (nativeIn ? expected.amountIn : 0n)) throw new FyndTransactionError(\"native value does not match the input\");\n if (!/^0x(?:[0-9a-fA-F]{2}){4,}$/.test(tx.data)) throw new FyndTransactionError(\"malformed calldata\");\n\n let call: ReturnType<typeof decodeFunctionData<typeof TYCHO_ROUTER_ABI>>;\n try {\n call = decodeFunctionData({abi: TYCHO_ROUTER_ABI, data: tx.data as Hex});\n } catch {\n throw new FyndTransactionError(\"not a plain singleSwap / sequentialSwap / splitSwap call (Permit2 and vault variants are not used)\");\n }\n const a = call.args;\n const [amountIn, tokenIn, tokenOut, expectedAmountOut, minAmountOut] = a;\n const isSplit = call.functionName === \"splitSwap\";\n const receiver = (isSplit ? a[6] : a[5]) as string;\n const fee = (isSplit ? a[7] : a[6]) as {clientFeeBps: number; clientFeeReceiver: string; maxClientContribution: bigint; deadline: bigint; clientSignature: string};\n const swaps = (isSplit ? a[8] : a[7]) as string;\n if (amountIn !== expected.amountIn) throw new FyndTransactionError(`calldata spends ${amountIn}, not ${expected.amountIn}`);\n if (!sameAddress(tokenIn, routerToken(expected.tokenIn)) || !sameAddress(tokenOut, routerToken(expected.tokenOut))) {\n throw new FyndTransactionError(\"calldata swaps other tokens than requested\");\n }\n if (!sameAddress(receiver, expected.receiver)) throw new FyndTransactionError(`calldata pays ${receiver}, not ${expected.receiver}`);\n if (expectedAmountOut !== expected.expectedAmountOut || minAmountOut !== expected.minAmountOut) {\n throw new FyndTransactionError(\"calldata amounts do not match the quote\");\n }\n if (fee.clientFeeBps !== 0 || fee.maxClientContribution !== 0n || !sameAddress(fee.clientFeeReceiver, ZERO)) {\n throw new FyndTransactionError(\"calldata carries client-fee parameters — this module charges none\");\n }\n if (swaps === \"0x\" || (isSplit && (a[5] as bigint) < 2n)) throw new FyndTransactionError(\"empty or invalid route\");\n return {method: call.functionName};\n}\n\n/**\n * {@link checkFyndTransaction} on a remote quote, against the request this app sent: the pinned\n * router, the exact input, the requested tokens, the app's recipient, the quoted minimum and no\n * client fee. The reason it fails, or null.\n */\nexport function fyndVerifyQuote(params: QuoteParams, quote: Quote): string | null {\n try {\n const chainId = /^eip155:[0-9]+$/.test(params.sourceChain) ? Number(params.sourceChain.slice(7)) : NaN;\n const src = sourceAssetOf(params);\n if (!src) return `unknown source token ${params.sourceToken}`;\n const tx = quote.txRequest;\n if (!tx?.to || !tx.data) return \"the quote carries no router transaction\";\n checkFyndTransaction({to: tx.to, data: tx.data, value: BigInt(tx.value ?? \"0\")}, {\n chainId,\n tokenIn: src.contract ? getAddress(src.contract) : ZERO,\n tokenOut: /^0x0+$/.test(params.destToken) ? ZERO : getAddress(params.destToken),\n amountIn: BigInt(params.amountIn),\n receiver: getAddress(params.destAddress),\n expectedAmountOut: BigInt(quote.providerData?.gross ?? \"0\"),\n minAmountOut: BigInt(quote.minDestAmount),\n });\n return null;\n } catch (e) {\n return e instanceof Error ? e.message.replace(/^fynd: /, \"\") : String(e);\n }\n}\n\n/**\n * The Tycho router a Fynd swap calls and approves on a chain, and {@link fyndVerifyQuote} — the\n * app's side of `remoteProvider({name: \"fynd\", baseUrl, ...fyndPins})`, so a backend's quote is\n * checked against these routers and the app's own request, never against itself (scan 2026-10-27\n * modules, F9).\n */\nexport const fyndPins: RemotePins = Object.freeze({\n callTargets: (chain: string): readonly string[] => fyndRouterFor(chain),\n spenders: (chain: string): readonly string[] => fyndRouterFor(chain),\n verifyQuote: fyndVerifyQuote,\n});\nfunction fyndRouterFor(chain: string): readonly string[] {\n const id = /^eip155:[0-9]+$/.test(chain) ? Number(chain.slice(7)) : NaN;\n const c = Object.prototype.hasOwnProperty.call(FYND_CHAINS, id) ? FYND_CHAINS[id] : undefined;\n return c ? [c.router] : [];\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/router.ts"],"sourcesContent":["/**\n * TychoRouterV3 — the only contract a Fynd quote may settle through (Fynd\n * License §3: every settlement of a Fynd output goes through the router in\n * Propeller Heads' registry). This file pins the router per chain and checks\n * Fynd's encoded transaction against the request before anything is approved\n * or sent: right contract, right method, the exact input, the requested\n * tokens, the user as receiver, the quoted output and minimum, and no client\n * fee. The route inside `swaps` is opaque and is not inspected here — the\n * router itself reverts below `minAmountOut`.\n */\nimport type {Quote, QuoteParams, RemotePins} from \"@anywalletkit/protocol-swap2-core/types\";\nimport {sourceAssetOf} from \"@anywalletkit/protocol-swap2-core/util\";\nimport {decodeFunctionData, getAddress, parseAbi, toFunctionSelector, type Hex} from \"viem\";\n\n/** Router registry: github.com/propeller-heads/tycho crates/tycho-execution/config/deployment_registry.json (TychoRouterV3, effective 2026-09-01). */\nexport const FYND_CHAINS: Readonly<Record<number, {slug: string; name: string; router: `0x${string}`}>> = Object.freeze({\n 1: {slug: \"ethereum\", name: \"Ethereum\", router: \"0x1644D2477f809cc2C71bCCFd6Dc9497E3F83210d\"},\n 8453: {slug: \"base\", name: \"Base\", router: \"0xAbA5B53b03eAfaD1C5fc8BD5Fc765fC85Bb3de67\"},\n 42161: {slug: \"arbitrum\", name: \"Arbitrum\", router: \"0x924F147c50eA59f5180a26031A8b65B2aA1e81Cd\"},\n 56: {slug: \"bsc\", name: \"BNB Smart Chain\", router: \"0x7f3D12BBaFb8955E51B3ab9588b34c8ad95BDA4e\"},\n 137: {slug: \"polygon\", name: \"Polygon\", router: \"0xbd4e6011F03355C2A377Fd9Af939322A7d0A1bC1\"},\n 130: {slug: \"unichain\", name: \"Unichain\", router: \"0xCba5574597Ad00EA250Fd106DAB4Fc7461949635\"},\n 4663: {slug: \"robinhood\", name: \"Robinhood Chain\", router: \"0x09215a470bD585E59EB3F4B612fBD2678131fF9e\"},\n});\n\n/** The router's native-coin sentinel (lib/NativeETH.sol); Fynd's API uses the zero address instead. */\nexport const ROUTER_NATIVE = \"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE\";\nconst ZERO = \"0x0000000000000000000000000000000000000000\";\n\nconst PARAMS = \"uint256 amountIn, address tokenIn, address tokenOut, uint256 expectedAmountOut, uint256 minAmountOut\";\nconst FEE = \"(uint32 clientFeeBps, address clientFeeReceiver, uint256 maxClientContribution, uint256 deadline, bytes clientSignature) clientFeeParams\";\nconst PERMIT = \"((address token, uint160 amount, uint48 expiration, uint48 nonce) details, address spender, uint256 sigDeadline) permitSingle, bytes signature\";\n\n/** The three entry points a wallet swap uses (plain `transferFrom` from the user). */\nexport const TYCHO_ROUTER_ABI = parseAbi([\n `function singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData) payable returns (uint256)`,\n `function sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n `function splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps) payable returns (uint256)`,\n]);\n\n/** Every swap entry point of the router (Permit2 and vault variants included) — for recognizing history. */\nexport const TYCHO_ROUTER_SELECTORS: ReadonlySet<string> = new Set(\n [\n `singleSwap(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwap(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwap(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swapData)`,\n `sequentialSwapUsingVault(${PARAMS}, address receiver, ${FEE}, bytes swaps)`,\n `splitSwapUsingVault(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, bytes swaps)`,\n `singleSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swapData)`,\n `sequentialSwapPermit2(${PARAMS}, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n `splitSwapPermit2(${PARAMS}, uint256 nTokens, address receiver, ${FEE}, ${PERMIT}, bytes swaps)`,\n ].map((sig) => toFunctionSelector(`function ${sig}`)),\n);\n\nexport class FyndTransactionError extends Error {\n constructor(message: string) {\n super(`fynd: ${message}`);\n this.name = \"FyndTransactionError\";\n }\n}\n\nexport interface ExpectedSwap {\n chainId: number;\n /** ERC-20 address, or the zero address for the native coin. */\n tokenIn: string;\n tokenOut: string;\n amountIn: bigint;\n receiver: string;\n /** Fynd's gross `amount_out` (before the router fee and slippage). */\n expectedAmountOut: bigint;\n /** Fynd's `min_amount_received` — what the router enforces on-chain. */\n minAmountOut: bigint;\n}\n\nexport interface EncodedTx {\n to: string;\n value: bigint;\n data: string;\n}\n\nconst sameAddress = (a: string, b: string) => {\n try {\n return getAddress(a) === getAddress(b);\n } catch {\n return false;\n }\n};\nconst routerToken = (t: string) => (sameAddress(t, ZERO) ? ROUTER_NATIVE : t);\n\n/** Refuses (throws {@link FyndTransactionError}) unless `tx` is exactly the swap the wallet asked for. */\nexport function checkFyndTransaction(tx: EncodedTx, expected: ExpectedSwap): {method: \"singleSwap\" | \"sequentialSwap\" | \"splitSwap\"} {\n const chain = FYND_CHAINS[expected.chainId];\n if (!chain) throw new FyndTransactionError(`chain ${expected.chainId} has no pinned Tycho router`);\n // `tx.to` is the API's: named in the refusal only when it is an address (scan 2026-10-27 modules, F34).\n if (!sameAddress(tx.to, chain.router)) throw new FyndTransactionError(`the transaction targets ${/^0x[0-9a-fA-F]{40}$/.test(tx.to) ? tx.to : \"something that is not an address\"}, not the Tycho router on ${chain.name}`);\n if (sameAddress(expected.receiver, ZERO) || sameAddress(expected.receiver, chain.router)) throw new FyndTransactionError(\"invalid receiver\");\n if (expected.amountIn <= 0n || expected.minAmountOut <= 0n || expected.minAmountOut > expected.expectedAmountOut) {\n throw new FyndTransactionError(\"invalid quote amounts\");\n }\n const nativeIn = sameAddress(expected.tokenIn, ZERO);\n if (tx.value !== (nativeIn ? expected.amountIn : 0n)) throw new FyndTransactionError(\"native value does not match the input\");\n if (!/^0x(?:[0-9a-fA-F]{2}){4,}$/.test(tx.data)) throw new FyndTransactionError(\"malformed calldata\");\n\n let call: ReturnType<typeof decodeFunctionData<typeof TYCHO_ROUTER_ABI>>;\n try {\n call = decodeFunctionData({abi: TYCHO_ROUTER_ABI, data: tx.data as Hex});\n } catch {\n throw new FyndTransactionError(\"not a plain singleSwap / sequentialSwap / splitSwap call (Permit2 and vault variants are not used)\");\n }\n const a = call.args;\n const [amountIn, tokenIn, tokenOut, expectedAmountOut, minAmountOut] = a;\n const isSplit = call.functionName === \"splitSwap\";\n const receiver = (isSplit ? a[6] : a[5]) as string;\n const fee = (isSplit ? a[7] : a[6]) as {clientFeeBps: number; clientFeeReceiver: string; maxClientContribution: bigint; deadline: bigint; clientSignature: string};\n const swaps = (isSplit ? a[8] : a[7]) as string;\n if (amountIn !== expected.amountIn) throw new FyndTransactionError(`calldata spends ${amountIn}, not ${expected.amountIn}`);\n if (!sameAddress(tokenIn, routerToken(expected.tokenIn)) || !sameAddress(tokenOut, routerToken(expected.tokenOut))) {\n throw new FyndTransactionError(\"calldata swaps other tokens than requested\");\n }\n if (!sameAddress(receiver, expected.receiver)) throw new FyndTransactionError(`calldata pays ${receiver}, not ${expected.receiver}`);\n if (expectedAmountOut !== expected.expectedAmountOut || minAmountOut !== expected.minAmountOut) {\n throw new FyndTransactionError(\"calldata amounts do not match the quote\");\n }\n if (fee.clientFeeBps !== 0 || fee.maxClientContribution !== 0n || !sameAddress(fee.clientFeeReceiver, ZERO)) {\n throw new FyndTransactionError(\"calldata carries client-fee parameters — this module charges none\");\n }\n if (swaps === \"0x\" || (isSplit && (a[5] as bigint) < 2n)) throw new FyndTransactionError(\"empty or invalid route\");\n return {method: call.functionName};\n}\n\n/**\n * {@link checkFyndTransaction} on a remote quote, against the request this app sent: the pinned\n * router, one chain, the exact input, the requested tokens, the app's recipient, the quoted minimum and\n * no client fee. The reason it fails, or null.\n */\nexport function fyndVerifyQuote(params: QuoteParams, quote: Quote): string | null {\n // Fynd swaps on one chain; a source-chain swap must not answer a cross-chain request (reaudit 2026-10-30, #3).\n if (params.sourceChain !== params.destChain) return \"same-chain swaps only\";\n try {\n const chainId = /^eip155:[0-9]+$/.test(params.sourceChain) ? Number(params.sourceChain.slice(7)) : NaN;\n const src = sourceAssetOf(params);\n if (!src) return `unknown source token ${params.sourceToken}`;\n const tx = quote.txRequest;\n if (!tx?.to || !tx.data) return \"the quote carries no router transaction\";\n checkFyndTransaction({to: tx.to, data: tx.data, value: BigInt(tx.value ?? \"0\")}, {\n chainId,\n tokenIn: src.contract ? getAddress(src.contract) : ZERO,\n tokenOut: /^0x0+$/.test(params.destToken) ? ZERO : getAddress(params.destToken),\n amountIn: BigInt(params.amountIn),\n receiver: getAddress(params.destAddress),\n expectedAmountOut: BigInt(quote.providerData?.gross ?? \"0\"),\n minAmountOut: BigInt(quote.minDestAmount),\n });\n return null;\n } catch (e) {\n return e instanceof Error ? e.message.replace(/^fynd: /, \"\") : String(e);\n }\n}\n\n/**\n * The Tycho router a Fynd swap calls and approves on a chain, and {@link fyndVerifyQuote} — the\n * app's side of `remoteProvider({name: \"fynd\", baseUrl, ...fyndPins})`, so a backend's quote is\n * checked against these routers and the app's own request, never against itself (scan 2026-10-27\n * modules, F9).\n */\nexport const fyndPins: RemotePins = Object.freeze({\n callTargets: (chain: string): readonly string[] => fyndRouterFor(chain),\n spenders: (chain: string): readonly string[] => fyndRouterFor(chain),\n verifyQuote: fyndVerifyQuote,\n});\nfunction fyndRouterFor(chain: string): readonly string[] {\n const id = /^eip155:[0-9]+$/.test(chain) ? Number(chain.slice(7)) : NaN;\n const c = Object.prototype.hasOwnProperty.call(FYND_CHAINS, id) ? FYND_CHAINS[id] : undefined;\n return c ? [c.router] : [];\n}\n"],"mappings":"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","names":[]}
|
package/dist/router.d.cts
CHANGED
|
@@ -196,8 +196,8 @@ declare function checkFyndTransaction(tx: EncodedTx, expected: ExpectedSwap): {
|
|
|
196
196
|
};
|
|
197
197
|
/**
|
|
198
198
|
* {@link checkFyndTransaction} on a remote quote, against the request this app sent: the pinned
|
|
199
|
-
* router, the exact input, the requested tokens, the app's recipient, the quoted minimum and
|
|
200
|
-
* client fee. The reason it fails, or null.
|
|
199
|
+
* router, one chain, the exact input, the requested tokens, the app's recipient, the quoted minimum and
|
|
200
|
+
* no client fee. The reason it fails, or null.
|
|
201
201
|
*/
|
|
202
202
|
declare function fyndVerifyQuote(params: QuoteParams, quote: Quote): string | null;
|
|
203
203
|
/**
|
package/dist/router.d.ts
CHANGED
|
@@ -196,8 +196,8 @@ declare function checkFyndTransaction(tx: EncodedTx, expected: ExpectedSwap): {
|
|
|
196
196
|
};
|
|
197
197
|
/**
|
|
198
198
|
* {@link checkFyndTransaction} on a remote quote, against the request this app sent: the pinned
|
|
199
|
-
* router, the exact input, the requested tokens, the app's recipient, the quoted minimum and
|
|
200
|
-
* client fee. The reason it fails, or null.
|
|
199
|
+
* router, one chain, the exact input, the requested tokens, the app's recipient, the quoted minimum and
|
|
200
|
+
* no client fee. The reason it fails, or null.
|
|
201
201
|
*/
|
|
202
202
|
declare function fyndVerifyQuote(params: QuoteParams, quote: Quote): string | null;
|
|
203
203
|
/**
|
package/dist/router.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@anywalletkit/protocol-swap2-fynd",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.4",
|
|
4
4
|
"description": "Fynd (Propeller Heads) same-chain swaps for Any Wallet Kit: exact-input routes across the pools Tycho indexes on Ethereum, Base, Arbitrum, BNB Chain, Polygon, Unichain and Robinhood Chain, settled through TychoRouterV3 with the transaction checked against the request before signing.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"homepage": "https://docs.anywallet.tech",
|
|
@@ -55,17 +55,17 @@
|
|
|
55
55
|
],
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"viem": "^2.57.2",
|
|
58
|
-
"@anywalletkit/protocol-swap2-core": "0.
|
|
58
|
+
"@anywalletkit/protocol-swap2-core": "0.10.0",
|
|
59
59
|
"@anywalletkit/swap2-assets": "0.2.2"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
|
-
"@anywalletkit/core": "0.
|
|
62
|
+
"@anywalletkit/core": "0.8.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@types/node": "^22",
|
|
66
66
|
"vitest": "^5.0.3",
|
|
67
|
-
"@anywalletkit/
|
|
68
|
-
"@anywalletkit/
|
|
67
|
+
"@anywalletkit/conformance": "0.5.0",
|
|
68
|
+
"@anywalletkit/core": "0.8.0"
|
|
69
69
|
},
|
|
70
70
|
"keywords": [
|
|
71
71
|
"swap2",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|