@anywalletkit/protocol-swap2-core 0.7.0 → 0.8.0
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/README.md +1 -1
- package/dist/{chunk-WBYCNN6S.js → chunk-5FPDKWBG.js} +7 -2
- package/dist/chunk-5FPDKWBG.js.map +1 -0
- package/dist/{chunk-CT2REW2V.js → chunk-6MTZNYKM.js} +2 -1
- package/dist/chunk-6MTZNYKM.js.map +1 -0
- package/dist/{chunk-5E5FWYZQ.js → chunk-CRCDWV3L.js} +8 -2
- package/dist/chunk-CRCDWV3L.js.map +1 -0
- package/dist/{chunk-U653ZREM.js → chunk-FHEU7E5A.js} +3 -3
- package/dist/index.cjs +14 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -4
- package/dist/remote.cjs +1 -0
- package/dist/remote.cjs.map +1 -1
- package/dist/remote.d.cts +8 -1
- package/dist/remote.d.ts +8 -1
- package/dist/remote.js +1 -1
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +1 -1
- package/dist/swap2/base.cjs +7 -1
- package/dist/swap2/base.cjs.map +1 -1
- package/dist/swap2/base.js +1 -1
- package/dist/swap2/single.cjs +13 -2
- package/dist/swap2/single.cjs.map +1 -1
- package/dist/swap2/single.js +3 -3
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +17 -0
- package/dist/types.d.ts +17 -0
- package/dist/validate.cjs +6 -1
- package/dist/validate.cjs.map +1 -1
- package/dist/validate.d.cts +7 -5
- package/dist/validate.d.ts +7 -5
- package/dist/validate.js +1 -1
- package/package.json +6 -6
- package/dist/chunk-5E5FWYZQ.js.map +0 -1
- package/dist/chunk-CT2REW2V.js.map +0 -1
- package/dist/chunk-WBYCNN6S.js.map +0 -1
- /package/dist/{chunk-U653ZREM.js.map → chunk-FHEU7E5A.js.map} +0 -0
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
} from "./chunk-J4WXUNK2.js";
|
|
13
13
|
import {
|
|
14
14
|
SingleProviderSwap2
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-FHEU7E5A.js";
|
|
16
16
|
import {
|
|
17
17
|
BaseSwap2Protocol,
|
|
18
18
|
NATIVE_DECIMALS,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
nativeDecimals,
|
|
22
22
|
nativeUnits,
|
|
23
23
|
swap2ChainId
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-CRCDWV3L.js";
|
|
25
25
|
import "./chunk-55J6XMHW.js";
|
|
26
26
|
import {
|
|
27
27
|
Swap2ConfigurationError,
|
|
@@ -39,14 +39,14 @@ import {
|
|
|
39
39
|
import {
|
|
40
40
|
remotePinRefusal,
|
|
41
41
|
validQuote
|
|
42
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-5FPDKWBG.js";
|
|
43
43
|
import {
|
|
44
44
|
slippageClassOf,
|
|
45
45
|
suggestSlippage
|
|
46
46
|
} from "./chunk-VNVBGY7U.js";
|
|
47
47
|
import {
|
|
48
48
|
remoteProvider
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-6MTZNYKM.js";
|
|
50
50
|
import {
|
|
51
51
|
evmRef,
|
|
52
52
|
isEvm,
|
package/dist/remote.cjs
CHANGED
|
@@ -93,6 +93,7 @@ function remoteProvider(opts) {
|
|
|
93
93
|
name,
|
|
94
94
|
remote: true,
|
|
95
95
|
...opts.verifyBuild ? { verifiesBuilds: true } : {},
|
|
96
|
+
...opts.depositAddressRoutes === true ? { depositAddressRoutes: true } : {},
|
|
96
97
|
// The browser side has no dark-flag env; the backend enforces its own
|
|
97
98
|
// flags — a disabled provider simply quotes null / rejects build.
|
|
98
99
|
enabled: () => enabled ? enabled() : true,
|
package/dist/remote.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/remote.ts","../src/util.ts"],"sourcesContent":["import type {Asset, CrossChainProvider, IntentRef, IntentStatus, Quote, QuoteParams, UnsignedTx} from \"./types.js\";\nimport {anySignal, timeoutSignal} from \"./util.js\";\n\n/**\n * HTTP-proxied `CrossChainProvider`: forwards every provider call to a backend\n * that runs the real provider. This is how a BROWSER context (e.g. a wallet's\n * signing worker) executes the full swap2 layer — account binding,\n * approvals, cell-body guards, id/status lifecycle — while provider secrets\n * (API JWTs, affiliate config) and non-CORS REST endpoints stay server-side.\n *\n * Wire protocol: POST {baseUrl}/{action} with a JSON body, one action per\n * provider method:\n * quote {provider, params: QuoteParams} → Quote | null\n * build {provider, quote: Quote} → UnsignedTx\n * preflight {provider, quote: Quote, tx: UnsignedTx} → {}\n * track {provider, ref: IntentRef} → IntentStatus\n * notify {provider, intentId, txHash} → {}\n * register {provider, intentId, signature} → {}\n * claim {provider, ref: IntentRef} → UnsignedTx | null\n * supports {provider, from, to} → {supported: boolean}\n *\n * `supportsRoute`/`enabled` need a SYNC answer, so they are config: pass the\n * routes the backend advertises via `supports`, or leave the default\n * allow-and-let-quote-decide behavior (a null quote is a clean miss).\n */\nexport interface RemoteProviderOptions {\n /** Provider name the backend multiplexes on (e.g. \"near-intents\"). */\n name: string;\n /** Endpoint prefix, e.g. \"/api/v2/swap2\" (same-origin) or absolute URL. */\n baseUrl: string;\n /** Sync capability gate; defaults to true (the backend quote decides). */\n supportsRoute?: (from: Asset, to: Asset) => boolean;\n /** Optional synchronous dark flag for accurate client-side discovery. */\n enabled?: () => boolean;\n /** Optional chain-level discovery hooks mirrored from the backend. */\n supportsChain?: (caip2: string) => boolean;\n supportsChainPair?: (fromChain: string, toChain: string) => boolean;\n /** Explicit opt-in when the backend can execute same-chain swaps. */\n supportsSameChain?: boolean;\n /**\n * The contracts this provider's claims may call, per chain (e.g. `cctpClaimTargets` from\n * `@anywalletkit/protocol-swap2-cctp`). The app's list, not the server's: without it the engine\n * refuses claims from this stub (round 3, M24).\n */\n claimTargets?: (chain: string) => readonly string[];\n /**\n * The contracts this provider's EVM swaps may call, and the spenders they may be approved for,\n * per chain — the provider package's pins (`lifiPins`, `fyndPins`, `uniswapPins`…, spread into\n * these options). The app's lists, not the server's: without them the engine refuses this stub's\n * EVM calldata routes and approvals, so a compromised backend cannot name its own contract or\n * spender (scan 2026-10-27 modules, F8/F9/F16). Deposit-address routes need no pins.\n */\n callTargets?: (chain: string) => readonly string[];\n spenders?: (chain: string) => readonly string[];\n /**\n * The provider's own check of a quote's transaction, run here on the client against the request\n * the app sent (shipped with the pins, e.g. `fyndPins`): a quote it refuses never reaches the\n * auction. The backend runs the same check, but the backend is what this guards against.\n */\n verifyQuote?: (params: QuoteParams, quote: Quote) => string | null;\n /**\n * The provider's own check of a build on the client — a typed order, a cell body — against the quote it was built\n * for: the reason it fails, or null. A build it refuses is never approved or signed.\n */\n verifyBuild?: (quote: Quote, tx: UnsignedTx, params?: QuoteParams) => string | null | Promise<string | null>;\n /**\n * Allow a non-https absolute `baseUrl` (local development only). Quotes, builds and typed orders travel this\n * channel; over plain http anyone on the path can rewrite them (scan 2026-10-27 modules, F4).\n */\n allowInsecureBaseUrl?: boolean;\n /** Defaults to globalThis.fetch — injectable for tests. */\n fetchImpl?: typeof fetch;\n /** Extra headers (e.g. auth) attached to every call. */\n headers?: Record<string, string>;\n /** Per-request fail-closed timeout. Defaults to 12 seconds. */\n timeoutMs?: number;\n}\n\n/**\n * A provider that runs on your server: quotes, deposits and tracking go to `baseUrl`, so API keys\n * and affiliate settings never ship in the app. The engine still validates every quote and\n * transaction locally before signing.\n */\nexport function remoteProvider(opts: RemoteProviderOptions): CrossChainProvider {\n const name = opts.name;\n if (!/^[A-Za-z0-9_-]+$/.test(name)) throw new Error(\"swap2-remote: invalid provider name\");\n // Trimmed first: a URL parser drops leading spaces and tabs, so \" http://host\" would reach http (scan 2026-10-28 review).\n const baseUrl = opts.baseUrl.trim().replace(/\\/+$/, \"\");\n if (!baseUrl) throw new Error(\"swap2-remote: baseUrl is required\");\n // A same-origin path (\"/api/swap2\") inherits the page's scheme; an absolute URL must be https, and a\n // scheme-relative one (\"//host/…\") names another origin without saying how to reach it.\n const insecure = /^[a-z][a-z0-9+.-]*:/i.test(baseUrl) ? !/^https:\\/\\//i.test(baseUrl) : /^[\\\\/]{2}/.test(baseUrl);\n if (insecure && opts.allowInsecureBaseUrl !== true) {\n throw new Error(\"swap2-remote: baseUrl must be https:// or a same-origin path (allowInsecureBaseUrl for local development)\");\n }\n const supportsRoute = opts.supportsRoute;\n const enabled = opts.enabled;\n const headers = Object.freeze({...opts.headers});\n const doFetch = opts.fetchImpl ?? fetch;\n const timeoutMs = opts.timeoutMs ?? 12_000;\n if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 60_000) {\n throw new Error(\"swap2-remote: timeoutMs must be an integer between 1 and 60000\");\n }\n\n // The request each quote answered, for verifyBuild (the build only carries the quote). Bounded: a stub quotes often.\n const requests = new Map<string, QuoteParams>();\n const remember = (intentId: string, params: QuoteParams) => {\n requests.delete(intentId);\n requests.set(intentId, params);\n if (requests.size > 64) requests.delete(requests.keys().next().value!);\n };\n\n async function call<T>(action: string, body: Record<string, unknown>, signal?: AbortSignal): Promise<T> {\n const timeout = timeoutSignal(timeoutMs);\n const requestSignal = signal ? anySignal([signal, timeout]) : timeout;\n const res = await doFetch(`${baseUrl}/${action}`, {\n method: \"POST\",\n headers: {...headers, \"content-type\": \"application/json\"},\n // UnsignedTx carries bigint fields (value, gasLimit) — JSON.stringify\n // throws on them (\"Do not know how to serialize a BigInt\", seen\n // 2026-10-10 on a Solana → Base preflight). Wire them as decimal strings.\n body: JSON.stringify({provider: name, ...body}, (_k, v) => (typeof v === \"bigint\" ? v.toString() : v)),\n signal: requestSignal,\n });\n if (!res.ok) {\n // Surface the backend's message — provider errors are actionable\n // (\"quote expired\", \"route unsupported\"), not just status codes.\n const text = await res.text().catch(() => \"\");\n throw new Error(`swap2-remote ${name}/${action}: ${res.status} ${text.slice(0, 300)}`);\n }\n return (await res.json()) as T;\n }\n\n return {\n name,\n remote: true,\n ...(opts.verifyBuild ? {verifiesBuilds: true} : {}),\n // The browser side has no dark-flag env; the backend enforces its own\n // flags — a disabled provider simply quotes null / rejects build.\n enabled: () => enabled ? enabled() : true,\n supportsRoute(from: Asset, to: Asset): boolean {\n return supportsRoute ? supportsRoute(from, to) : true;\n },\n async getQuote(params: QuoteParams, signal?: AbortSignal): Promise<Quote | null> {\n const out = await call<Quote | {noQuote: string} | null>(\"quote\", {params}, signal);\n if (out && typeof out === \"object\" && \"noQuote\" in out) {\n // The backend forwarded the venue's own refusal — throw it so the\n // router records a REASON instead of an anonymous null.\n throw new Error(String((out as {noQuote: unknown}).noQuote).slice(0, 300));\n }\n if (out && typeof out === \"object\" && typeof out.intentId === \"string\") remember(out.intentId, params);\n if (out && opts.verifyQuote) {\n let refusal: string | null;\n try { refusal = opts.verifyQuote(params, out); } catch (e) { refusal = e instanceof Error ? e.message : String(e); }\n if (refusal) throw new Error(`${name}: the quote fails this app's own check — ${refusal.slice(0, 240)}`);\n }\n return out;\n },\n async buildDeposit(quote: Quote): Promise<UnsignedTx> {\n const tx = await call<UnsignedTx>(\"build\", {quote});\n if (opts.verifyBuild) {\n let refusal: string | null;\n try { refusal = await opts.verifyBuild(quote, tx, requests.get(quote.intentId)); } catch (e) { refusal = e instanceof Error ? e.message : String(e); }\n if (refusal) throw new Error(`${name}: the build fails this app's own check — ${refusal.slice(0, 240)}`);\n }\n return tx;\n },\n async preflightDeposit(quote: Quote, tx: UnsignedTx): Promise<void> {\n await call<Record<string, never>>(\"preflight\", {quote, tx});\n },\n async trackIntent(ref: IntentRef): Promise<IntentStatus> {\n return call<IntentStatus>(\"track\", {ref});\n },\n async notifyDeposit(intentId: string, txHash: string): Promise<void> {\n await call<Record<string, never>>(\"notify\", {intentId, txHash});\n },\n async registerSignedOrder(intentId: string, signature: string): Promise<void> {\n await call<Record<string, never>>(\"register\", {intentId, signature});\n },\n async buildClaim(ref: IntentRef): Promise<UnsignedTx | null> {\n const out = await call<UnsignedTx | null | Record<string, never>>(\"claim\", {ref});\n // A backend without the action (older deploy) answers {} — treat as \"not ready\".\n if (!out || typeof out !== \"object\" || !(\"to\" in out)) return null;\n return out as UnsignedTx;\n },\n ...(opts.supportsChain ? {supportsChain: opts.supportsChain.bind(opts)} : {}),\n ...(opts.supportsChainPair ? {supportsChainPair: opts.supportsChainPair.bind(opts)} : {}),\n ...(opts.supportsSameChain !== undefined ? {supportsSameChain: opts.supportsSameChain} : {}),\n ...(opts.claimTargets ? {claimTargets: opts.claimTargets} : {}),\n ...(opts.callTargets ? {callTargets: opts.callTargets} : {}),\n ...(opts.spenders ? {spenders: opts.spenders} : {}),\n };\n}\n","import {resolveAsset, type CcAsset} from \"@anywalletkit/swap2-assets\";\nimport type {QuoteParams} from \"./types.js\";\n/** Shared CAIP-2 helpers. Contribution packages never read deployment env. */\nexport function namespace(chain: string): string {\n return chain.split(\":\")[0] ?? \"\";\n}\n/** True for an `eip155:` chain id. */\nexport function isEvm(chain: string): boolean {\n return namespace(chain) === \"eip155\";\n}\n/** Numeric chain id of an `eip155:<id>` CAIP-2 id. */\nexport function evmRef(chain: string): number {\n return Number(chain.split(\":\")[1] ?? \"0\");\n}\n\n/**\n * `AbortSignal.timeout`, portably: Hermes (React Native) ships AbortController\n * but not the static helpers, and the unguarded call threw\n * \"AbortSignal.timeout is not a function\" on every remote execution — which\n * the auction then read as \"no route\". Falls back to a manual controller.\n */\nexport function timeoutSignal(ms: number): AbortSignal {\n const native = (AbortSignal as unknown as {timeout?: (ms: number) => AbortSignal}).timeout;\n if (typeof native === \"function\") return native.call(AbortSignal, ms);\n const ctrl = new AbortController();\n setTimeout(() => ctrl.abort(), ms);\n return ctrl.signal;\n}\n\n/** `AbortSignal.any`, portably (see timeoutSignal). */\nexport function anySignal(signals: readonly AbortSignal[]): AbortSignal {\n const native = (AbortSignal as unknown as {any?: (s: readonly AbortSignal[]) => AbortSignal}).any;\n if (typeof native === \"function\") return native.call(AbortSignal, signals);\n const ctrl = new AbortController();\n for (const s of signals) {\n if (s.aborted) {\n ctrl.abort();\n break;\n }\n s.addEventListener(\"abort\", () => ctrl.abort(), {once: true});\n }\n return ctrl.signal;\n}\n\n/** Longest URL scheme taken out (`https`, `wss`, `git+ssh`…): a letter and up to 31 of `[A-Za-z0-9+.-]`. */\nconst SCHEME_MAX = 32;\nconst isLetter = (c: number): boolean => (c >= 97 && c <= 122) || (c >= 65 && c <= 90);\nconst isSchemeChar = (c: number): boolean => isLetter(c) || (c >= 48 && c <= 57) || c === 43 || c === 45 || c === 46;\n/** What ends a URL in text: `\\s` as regexes define it, and `\"` `'` `<` `>`. */\nconst endsUrl = (c: number): boolean =>\n c === 34 || c === 39 || c === 60 || c === 62 || (c >= 9 && c <= 13) || c === 32 || c === 0xa0 || c === 0x1680 ||\n (c >= 0x2000 && c <= 0x200a) || c === 0x2028 || c === 0x2029 || c === 0x202f || c === 0x205f || c === 0x3000 || c === 0xfeff;\n\n/**\n * Remove URLs from text that may travel to a client (a refusal reason, a build error):\n * transport errors carry the endpoint — and with a keyed RPC (QuickNode, Alchemy,\n * Infura…) the key sits in its path (scan 2026-10-23 F8). What `/[a-z][a-z0-9+.-]{0,31}:\\/\\/[^\\s\"'<>]+/gi`\n * would replace, found from each `://` instead of tried at every position: linear in the length of\n * `text` whatever an API or a client puts in it (\"a.a.a…\" made the regex this replaced quadratic;\n * scan 2026-10-27 modules, F34). A URL glued to a word loses its last 32 scheme characters and the rest.\n */\nexport function redactUrls(text: string): string {\n let out = \"\";\n let done = 0; // text[0, done) is in `out`\n for (let at = text.indexOf(\"://\"); at !== -1; at = text.indexOf(\"://\", at + 3)) {\n let start = at;\n while (start > done && at - start < SCHEME_MAX && isSchemeChar(text.charCodeAt(start - 1))) start--;\n while (start < at && !isLetter(text.charCodeAt(start))) start++;\n if (start === at) continue; // no scheme: not a URL (and its tail is not scanned, so \"://://…\" stays linear)\n let end = at + 3;\n while (end < text.length && !endsUrl(text.charCodeAt(end))) end++;\n if (end === at + 3) continue; // nothing after \"://\"\n out += `${text.slice(done, start)}<url>`;\n done = end;\n at = end - 3;\n }\n return done === 0 ? text : out + text.slice(done);\n}\n\n/**\n * The source asset a provider is asked to sell: the catalog's entry for `params.sourceToken`, or —\n * only when the engine sent `sourceDecimals` (an unlisted token the user added) — an unlisted asset\n * named by its contract / mint. Providers that can trade any token use this instead of the catalog\n * lookup; the others keep refusing what the catalog does not list.\n */\nexport function sourceAssetOf(params: Pick<QuoteParams, \"sourceChain\" | \"sourceToken\" | \"sourceDecimals\">): CcAsset | undefined {\n const decimals = params.sourceDecimals;\n return resolveAsset(params.sourceChain, params.sourceToken, decimals === undefined ? {} : {allowUnlisted: true, decimals});\n}\n\nconst B64 = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n/** Standard or URL-safe base64 → bytes, without Buffer or atob (Hermes); null for anything else. */\nexport function base64Bytes(text: string): Uint8Array | null {\n const s = text.replace(/-/g, \"+\").replace(/_/g, \"/\").replace(/=+$/, \"\");\n if (!/^[A-Za-z0-9+/]*$/.test(s) || s.length % 4 === 1) return null;\n const out = new Uint8Array(Math.floor((s.length * 3) / 4));\n let bits = 0, acc = 0, n = 0;\n for (const ch of s) {\n acc = (acc << 6) | B64.indexOf(ch);\n bits += 6;\n if (bits >= 8) { bits -= 8; out[n++] = (acc >> bits) & 0xff; }\n }\n return out.subarray(0, n);\n}\n\n/** CRC16-XMODEM, the checksum of a user-friendly TON address. */\nfunction crc16(bytes: Uint8Array): number {\n let crc = 0;\n for (const b of bytes) {\n crc ^= b << 8;\n for (let i = 0; i < 8; i++) crc = crc & 0x8000 ? ((crc << 1) ^ 0x1021) & 0xffff : (crc << 1) & 0xffff;\n }\n return crc;\n}\n\n/**\n * A TON address in raw form (`<workchain>:<64 hex>`, lowercase) from a raw string, a user-friendly one (bounceable or\n * not, checksum verified), or an address object (`toRawString()` / `workChain` + `hash`); null for anything else. Two\n * spellings of one address compare equal in this form.\n */\nexport function tonRawAddress(address: unknown): string | null {\n if (address && typeof address === \"object\") {\n const o = address as {toRawString?: () => string; workChain?: unknown; hash?: unknown};\n if (typeof o.toRawString === \"function\") { try { return tonRawAddress(o.toRawString()); } catch { return null; } }\n if (typeof o.workChain === \"number\" && o.hash instanceof Uint8Array && o.hash.length === 32) {\n return `${o.workChain}:${Array.from(o.hash, (b) => b.toString(16).padStart(2, \"0\")).join(\"\")}`;\n }\n return null;\n }\n if (typeof address !== \"string\") return null;\n const raw = /^(-?[0-9]{1,3}):([0-9a-fA-F]{64})$/.exec(address.trim());\n if (raw) return `${Number(raw[1])}:${raw[2]!.toLowerCase()}`;\n if (!/^[A-Za-z0-9+/_-]{48}$/.test(address.trim())) return null;\n const bytes = base64Bytes(address.trim());\n if (!bytes || bytes.length !== 36 || crc16(bytes.subarray(0, 34)) !== ((bytes[34]! << 8) | bytes[35]!)) return null;\n const wc = bytes[1] === 0xff ? -1 : bytes[1]!;\n return `${wc}:${Array.from(bytes.subarray(2, 34), (b) => b.toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/remote.ts","../src/util.ts"],"sourcesContent":["import type {Asset, CrossChainProvider, IntentRef, IntentStatus, Quote, QuoteParams, UnsignedTx} from \"./types.js\";\nimport {anySignal, timeoutSignal} from \"./util.js\";\n\n/**\n * HTTP-proxied `CrossChainProvider`: forwards every provider call to a backend\n * that runs the real provider. This is how a BROWSER context (e.g. a wallet's\n * signing worker) executes the full swap2 layer — account binding,\n * approvals, cell-body guards, id/status lifecycle — while provider secrets\n * (API JWTs, affiliate config) and non-CORS REST endpoints stay server-side.\n *\n * Wire protocol: POST {baseUrl}/{action} with a JSON body, one action per\n * provider method:\n * quote {provider, params: QuoteParams} → Quote | null\n * build {provider, quote: Quote} → UnsignedTx\n * preflight {provider, quote: Quote, tx: UnsignedTx} → {}\n * track {provider, ref: IntentRef} → IntentStatus\n * notify {provider, intentId, txHash} → {}\n * register {provider, intentId, signature} → {}\n * claim {provider, ref: IntentRef} → UnsignedTx | null\n * supports {provider, from, to} → {supported: boolean}\n *\n * `supportsRoute`/`enabled` need a SYNC answer, so they are config: pass the\n * routes the backend advertises via `supports`, or leave the default\n * allow-and-let-quote-decide behavior (a null quote is a clean miss).\n */\nexport interface RemoteProviderOptions {\n /** Provider name the backend multiplexes on (e.g. \"near-intents\"). */\n name: string;\n /** Endpoint prefix, e.g. \"/api/v2/swap2\" (same-origin) or absolute URL. */\n baseUrl: string;\n /** Sync capability gate; defaults to true (the backend quote decides). */\n supportsRoute?: (from: Asset, to: Asset) => boolean;\n /** Optional synchronous dark flag for accurate client-side discovery. */\n enabled?: () => boolean;\n /** Optional chain-level discovery hooks mirrored from the backend. */\n supportsChain?: (caip2: string) => boolean;\n supportsChainPair?: (fromChain: string, toChain: string) => boolean;\n /** Explicit opt-in when the backend can execute same-chain swaps. */\n supportsSameChain?: boolean;\n /**\n * The contracts this provider's claims may call, per chain (e.g. `cctpClaimTargets` from\n * `@anywalletkit/protocol-swap2-cctp`). The app's list, not the server's: without it the engine\n * refuses claims from this stub (round 3, M24).\n */\n claimTargets?: (chain: string) => readonly string[];\n /**\n * The contracts this provider's EVM swaps may call, and the spenders they may be approved for,\n * per chain — the provider package's pins (`lifiPins`, `fyndPins`, `uniswapPins`…, spread into\n * these options). The app's lists, not the server's: without them the engine refuses this stub's\n * EVM calldata routes and approvals, so a compromised backend cannot name its own contract or\n * spender (scan 2026-10-27 modules, F8/F9/F16). Deposit-address routes need `depositAddressRoutes`.\n */\n callTargets?: (chain: string) => readonly string[];\n spenders?: (chain: string) => readonly string[];\n /**\n * The provider's own check of a quote's transaction, run here on the client against the request\n * the app sent (shipped with the pins, e.g. `fyndPins`): a quote it refuses never reaches the\n * auction. The backend runs the same check, but the backend is what this guards against.\n */\n verifyQuote?: (params: QuoteParams, quote: Quote) => string | null;\n /**\n * The provider's own check of a build on the client — a typed order, a cell body — against the quote it was built\n * for: the reason it fails, or null. A build it refuses is never approved or signed.\n */\n verifyBuild?: (quote: Quote, tx: UnsignedTx, params?: QuoteParams) => string | null | Promise<string | null>;\n /**\n * Let this stub's routes send the input to an address the backend names: a deposit address (NEAR Intents, a\n * THORChain or Chainflip Tron vault) or a plain value send. Off by default: such a route bypasses `callTargets`, so\n * a compromised backend could name its own address (scan 2026-10-28, F9). Opt in only for a provider whose deposit\n * addresses cannot be pinned, or whose `verifyQuote` / `verifyBuild` checks them (`thorchainPins`, `chainflipPins`).\n */\n depositAddressRoutes?: boolean;\n /**\n * Allow a non-https absolute `baseUrl` (local development only). Quotes, builds and typed orders travel this\n * channel; over plain http anyone on the path can rewrite them (scan 2026-10-27 modules, F4).\n */\n allowInsecureBaseUrl?: boolean;\n /** Defaults to globalThis.fetch — injectable for tests. */\n fetchImpl?: typeof fetch;\n /** Extra headers (e.g. auth) attached to every call. */\n headers?: Record<string, string>;\n /** Per-request fail-closed timeout. Defaults to 12 seconds. */\n timeoutMs?: number;\n}\n\n/**\n * A provider that runs on your server: quotes, deposits and tracking go to `baseUrl`, so API keys\n * and affiliate settings never ship in the app. The engine still validates every quote and\n * transaction locally before signing.\n */\nexport function remoteProvider(opts: RemoteProviderOptions): CrossChainProvider {\n const name = opts.name;\n if (!/^[A-Za-z0-9_-]+$/.test(name)) throw new Error(\"swap2-remote: invalid provider name\");\n // Trimmed first: a URL parser drops leading spaces and tabs, so \" http://host\" would reach http (scan 2026-10-28 review).\n const baseUrl = opts.baseUrl.trim().replace(/\\/+$/, \"\");\n if (!baseUrl) throw new Error(\"swap2-remote: baseUrl is required\");\n // A same-origin path (\"/api/swap2\") inherits the page's scheme; an absolute URL must be https, and a\n // scheme-relative one (\"//host/…\") names another origin without saying how to reach it.\n const insecure = /^[a-z][a-z0-9+.-]*:/i.test(baseUrl) ? !/^https:\\/\\//i.test(baseUrl) : /^[\\\\/]{2}/.test(baseUrl);\n if (insecure && opts.allowInsecureBaseUrl !== true) {\n throw new Error(\"swap2-remote: baseUrl must be https:// or a same-origin path (allowInsecureBaseUrl for local development)\");\n }\n const supportsRoute = opts.supportsRoute;\n const enabled = opts.enabled;\n const headers = Object.freeze({...opts.headers});\n const doFetch = opts.fetchImpl ?? fetch;\n const timeoutMs = opts.timeoutMs ?? 12_000;\n if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 60_000) {\n throw new Error(\"swap2-remote: timeoutMs must be an integer between 1 and 60000\");\n }\n\n // The request each quote answered, for verifyBuild (the build only carries the quote). Bounded: a stub quotes often.\n const requests = new Map<string, QuoteParams>();\n const remember = (intentId: string, params: QuoteParams) => {\n requests.delete(intentId);\n requests.set(intentId, params);\n if (requests.size > 64) requests.delete(requests.keys().next().value!);\n };\n\n async function call<T>(action: string, body: Record<string, unknown>, signal?: AbortSignal): Promise<T> {\n const timeout = timeoutSignal(timeoutMs);\n const requestSignal = signal ? anySignal([signal, timeout]) : timeout;\n const res = await doFetch(`${baseUrl}/${action}`, {\n method: \"POST\",\n headers: {...headers, \"content-type\": \"application/json\"},\n // UnsignedTx carries bigint fields (value, gasLimit) — JSON.stringify\n // throws on them (\"Do not know how to serialize a BigInt\", seen\n // 2026-10-10 on a Solana → Base preflight). Wire them as decimal strings.\n body: JSON.stringify({provider: name, ...body}, (_k, v) => (typeof v === \"bigint\" ? v.toString() : v)),\n signal: requestSignal,\n });\n if (!res.ok) {\n // Surface the backend's message — provider errors are actionable\n // (\"quote expired\", \"route unsupported\"), not just status codes.\n const text = await res.text().catch(() => \"\");\n throw new Error(`swap2-remote ${name}/${action}: ${res.status} ${text.slice(0, 300)}`);\n }\n return (await res.json()) as T;\n }\n\n return {\n name,\n remote: true,\n ...(opts.verifyBuild ? {verifiesBuilds: true} : {}),\n ...(opts.depositAddressRoutes === true ? {depositAddressRoutes: true} : {}),\n // The browser side has no dark-flag env; the backend enforces its own\n // flags — a disabled provider simply quotes null / rejects build.\n enabled: () => enabled ? enabled() : true,\n supportsRoute(from: Asset, to: Asset): boolean {\n return supportsRoute ? supportsRoute(from, to) : true;\n },\n async getQuote(params: QuoteParams, signal?: AbortSignal): Promise<Quote | null> {\n const out = await call<Quote | {noQuote: string} | null>(\"quote\", {params}, signal);\n if (out && typeof out === \"object\" && \"noQuote\" in out) {\n // The backend forwarded the venue's own refusal — throw it so the\n // router records a REASON instead of an anonymous null.\n throw new Error(String((out as {noQuote: unknown}).noQuote).slice(0, 300));\n }\n if (out && typeof out === \"object\" && typeof out.intentId === \"string\") remember(out.intentId, params);\n if (out && opts.verifyQuote) {\n let refusal: string | null;\n try { refusal = opts.verifyQuote(params, out); } catch (e) { refusal = e instanceof Error ? e.message : String(e); }\n if (refusal) throw new Error(`${name}: the quote fails this app's own check — ${refusal.slice(0, 240)}`);\n }\n return out;\n },\n async buildDeposit(quote: Quote): Promise<UnsignedTx> {\n const tx = await call<UnsignedTx>(\"build\", {quote});\n if (opts.verifyBuild) {\n let refusal: string | null;\n try { refusal = await opts.verifyBuild(quote, tx, requests.get(quote.intentId)); } catch (e) { refusal = e instanceof Error ? e.message : String(e); }\n if (refusal) throw new Error(`${name}: the build fails this app's own check — ${refusal.slice(0, 240)}`);\n }\n return tx;\n },\n async preflightDeposit(quote: Quote, tx: UnsignedTx): Promise<void> {\n await call<Record<string, never>>(\"preflight\", {quote, tx});\n },\n async trackIntent(ref: IntentRef): Promise<IntentStatus> {\n return call<IntentStatus>(\"track\", {ref});\n },\n async notifyDeposit(intentId: string, txHash: string): Promise<void> {\n await call<Record<string, never>>(\"notify\", {intentId, txHash});\n },\n async registerSignedOrder(intentId: string, signature: string): Promise<void> {\n await call<Record<string, never>>(\"register\", {intentId, signature});\n },\n async buildClaim(ref: IntentRef): Promise<UnsignedTx | null> {\n const out = await call<UnsignedTx | null | Record<string, never>>(\"claim\", {ref});\n // A backend without the action (older deploy) answers {} — treat as \"not ready\".\n if (!out || typeof out !== \"object\" || !(\"to\" in out)) return null;\n return out as UnsignedTx;\n },\n ...(opts.supportsChain ? {supportsChain: opts.supportsChain.bind(opts)} : {}),\n ...(opts.supportsChainPair ? {supportsChainPair: opts.supportsChainPair.bind(opts)} : {}),\n ...(opts.supportsSameChain !== undefined ? {supportsSameChain: opts.supportsSameChain} : {}),\n ...(opts.claimTargets ? {claimTargets: opts.claimTargets} : {}),\n ...(opts.callTargets ? {callTargets: opts.callTargets} : {}),\n ...(opts.spenders ? {spenders: opts.spenders} : {}),\n };\n}\n","import {resolveAsset, type CcAsset} from \"@anywalletkit/swap2-assets\";\nimport type {QuoteParams} from \"./types.js\";\n/** Shared CAIP-2 helpers. Contribution packages never read deployment env. */\nexport function namespace(chain: string): string {\n return chain.split(\":\")[0] ?? \"\";\n}\n/** True for an `eip155:` chain id. */\nexport function isEvm(chain: string): boolean {\n return namespace(chain) === \"eip155\";\n}\n/** Numeric chain id of an `eip155:<id>` CAIP-2 id. */\nexport function evmRef(chain: string): number {\n return Number(chain.split(\":\")[1] ?? \"0\");\n}\n\n/**\n * `AbortSignal.timeout`, portably: Hermes (React Native) ships AbortController\n * but not the static helpers, and the unguarded call threw\n * \"AbortSignal.timeout is not a function\" on every remote execution — which\n * the auction then read as \"no route\". Falls back to a manual controller.\n */\nexport function timeoutSignal(ms: number): AbortSignal {\n const native = (AbortSignal as unknown as {timeout?: (ms: number) => AbortSignal}).timeout;\n if (typeof native === \"function\") return native.call(AbortSignal, ms);\n const ctrl = new AbortController();\n setTimeout(() => ctrl.abort(), ms);\n return ctrl.signal;\n}\n\n/** `AbortSignal.any`, portably (see timeoutSignal). */\nexport function anySignal(signals: readonly AbortSignal[]): AbortSignal {\n const native = (AbortSignal as unknown as {any?: (s: readonly AbortSignal[]) => AbortSignal}).any;\n if (typeof native === \"function\") return native.call(AbortSignal, signals);\n const ctrl = new AbortController();\n for (const s of signals) {\n if (s.aborted) {\n ctrl.abort();\n break;\n }\n s.addEventListener(\"abort\", () => ctrl.abort(), {once: true});\n }\n return ctrl.signal;\n}\n\n/** Longest URL scheme taken out (`https`, `wss`, `git+ssh`…): a letter and up to 31 of `[A-Za-z0-9+.-]`. */\nconst SCHEME_MAX = 32;\nconst isLetter = (c: number): boolean => (c >= 97 && c <= 122) || (c >= 65 && c <= 90);\nconst isSchemeChar = (c: number): boolean => isLetter(c) || (c >= 48 && c <= 57) || c === 43 || c === 45 || c === 46;\n/** What ends a URL in text: `\\s` as regexes define it, and `\"` `'` `<` `>`. */\nconst endsUrl = (c: number): boolean =>\n c === 34 || c === 39 || c === 60 || c === 62 || (c >= 9 && c <= 13) || c === 32 || c === 0xa0 || c === 0x1680 ||\n (c >= 0x2000 && c <= 0x200a) || c === 0x2028 || c === 0x2029 || c === 0x202f || c === 0x205f || c === 0x3000 || c === 0xfeff;\n\n/**\n * Remove URLs from text that may travel to a client (a refusal reason, a build error):\n * transport errors carry the endpoint — and with a keyed RPC (QuickNode, Alchemy,\n * Infura…) the key sits in its path (scan 2026-10-23 F8). What `/[a-z][a-z0-9+.-]{0,31}:\\/\\/[^\\s\"'<>]+/gi`\n * would replace, found from each `://` instead of tried at every position: linear in the length of\n * `text` whatever an API or a client puts in it (\"a.a.a…\" made the regex this replaced quadratic;\n * scan 2026-10-27 modules, F34). A URL glued to a word loses its last 32 scheme characters and the rest.\n */\nexport function redactUrls(text: string): string {\n let out = \"\";\n let done = 0; // text[0, done) is in `out`\n for (let at = text.indexOf(\"://\"); at !== -1; at = text.indexOf(\"://\", at + 3)) {\n let start = at;\n while (start > done && at - start < SCHEME_MAX && isSchemeChar(text.charCodeAt(start - 1))) start--;\n while (start < at && !isLetter(text.charCodeAt(start))) start++;\n if (start === at) continue; // no scheme: not a URL (and its tail is not scanned, so \"://://…\" stays linear)\n let end = at + 3;\n while (end < text.length && !endsUrl(text.charCodeAt(end))) end++;\n if (end === at + 3) continue; // nothing after \"://\"\n out += `${text.slice(done, start)}<url>`;\n done = end;\n at = end - 3;\n }\n return done === 0 ? text : out + text.slice(done);\n}\n\n/**\n * The source asset a provider is asked to sell: the catalog's entry for `params.sourceToken`, or —\n * only when the engine sent `sourceDecimals` (an unlisted token the user added) — an unlisted asset\n * named by its contract / mint. Providers that can trade any token use this instead of the catalog\n * lookup; the others keep refusing what the catalog does not list.\n */\nexport function sourceAssetOf(params: Pick<QuoteParams, \"sourceChain\" | \"sourceToken\" | \"sourceDecimals\">): CcAsset | undefined {\n const decimals = params.sourceDecimals;\n return resolveAsset(params.sourceChain, params.sourceToken, decimals === undefined ? {} : {allowUnlisted: true, decimals});\n}\n\nconst B64 = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n/** Standard or URL-safe base64 → bytes, without Buffer or atob (Hermes); null for anything else. */\nexport function base64Bytes(text: string): Uint8Array | null {\n const s = text.replace(/-/g, \"+\").replace(/_/g, \"/\").replace(/=+$/, \"\");\n if (!/^[A-Za-z0-9+/]*$/.test(s) || s.length % 4 === 1) return null;\n const out = new Uint8Array(Math.floor((s.length * 3) / 4));\n let bits = 0, acc = 0, n = 0;\n for (const ch of s) {\n acc = (acc << 6) | B64.indexOf(ch);\n bits += 6;\n if (bits >= 8) { bits -= 8; out[n++] = (acc >> bits) & 0xff; }\n }\n return out.subarray(0, n);\n}\n\n/** CRC16-XMODEM, the checksum of a user-friendly TON address. */\nfunction crc16(bytes: Uint8Array): number {\n let crc = 0;\n for (const b of bytes) {\n crc ^= b << 8;\n for (let i = 0; i < 8; i++) crc = crc & 0x8000 ? ((crc << 1) ^ 0x1021) & 0xffff : (crc << 1) & 0xffff;\n }\n return crc;\n}\n\n/**\n * A TON address in raw form (`<workchain>:<64 hex>`, lowercase) from a raw string, a user-friendly one (bounceable or\n * not, checksum verified), or an address object (`toRawString()` / `workChain` + `hash`); null for anything else. Two\n * spellings of one address compare equal in this form.\n */\nexport function tonRawAddress(address: unknown): string | null {\n if (address && typeof address === \"object\") {\n const o = address as {toRawString?: () => string; workChain?: unknown; hash?: unknown};\n if (typeof o.toRawString === \"function\") { try { return tonRawAddress(o.toRawString()); } catch { return null; } }\n if (typeof o.workChain === \"number\" && o.hash instanceof Uint8Array && o.hash.length === 32) {\n return `${o.workChain}:${Array.from(o.hash, (b) => b.toString(16).padStart(2, \"0\")).join(\"\")}`;\n }\n return null;\n }\n if (typeof address !== \"string\") return null;\n const raw = /^(-?[0-9]{1,3}):([0-9a-fA-F]{64})$/.exec(address.trim());\n if (raw) return `${Number(raw[1])}:${raw[2]!.toLowerCase()}`;\n if (!/^[A-Za-z0-9+/_-]{48}$/.test(address.trim())) return null;\n const bytes = base64Bytes(address.trim());\n if (!bytes || bytes.length !== 36 || crc16(bytes.subarray(0, 34)) !== ((bytes[34]! << 8) | bytes[35]!)) return null;\n const wc = bytes[1] === 0xff ? -1 : bytes[1]!;\n return `${wc}:${Array.from(bytes.subarray(2, 34), (b) => b.toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n"],"mappings":"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","names":[]}
|
package/dist/remote.d.cts
CHANGED
|
@@ -48,7 +48,7 @@ interface RemoteProviderOptions {
|
|
|
48
48
|
* per chain — the provider package's pins (`lifiPins`, `fyndPins`, `uniswapPins`…, spread into
|
|
49
49
|
* these options). The app's lists, not the server's: without them the engine refuses this stub's
|
|
50
50
|
* EVM calldata routes and approvals, so a compromised backend cannot name its own contract or
|
|
51
|
-
* spender (scan 2026-10-27 modules, F8/F9/F16). Deposit-address routes need
|
|
51
|
+
* spender (scan 2026-10-27 modules, F8/F9/F16). Deposit-address routes need `depositAddressRoutes`.
|
|
52
52
|
*/
|
|
53
53
|
callTargets?: (chain: string) => readonly string[];
|
|
54
54
|
spenders?: (chain: string) => readonly string[];
|
|
@@ -63,6 +63,13 @@ interface RemoteProviderOptions {
|
|
|
63
63
|
* for: the reason it fails, or null. A build it refuses is never approved or signed.
|
|
64
64
|
*/
|
|
65
65
|
verifyBuild?: (quote: Quote, tx: UnsignedTx, params?: QuoteParams) => string | null | Promise<string | null>;
|
|
66
|
+
/**
|
|
67
|
+
* Let this stub's routes send the input to an address the backend names: a deposit address (NEAR Intents, a
|
|
68
|
+
* THORChain or Chainflip Tron vault) or a plain value send. Off by default: such a route bypasses `callTargets`, so
|
|
69
|
+
* a compromised backend could name its own address (scan 2026-10-28, F9). Opt in only for a provider whose deposit
|
|
70
|
+
* addresses cannot be pinned, or whose `verifyQuote` / `verifyBuild` checks them (`thorchainPins`, `chainflipPins`).
|
|
71
|
+
*/
|
|
72
|
+
depositAddressRoutes?: boolean;
|
|
66
73
|
/**
|
|
67
74
|
* Allow a non-https absolute `baseUrl` (local development only). Quotes, builds and typed orders travel this
|
|
68
75
|
* channel; over plain http anyone on the path can rewrite them (scan 2026-10-27 modules, F4).
|
package/dist/remote.d.ts
CHANGED
|
@@ -48,7 +48,7 @@ interface RemoteProviderOptions {
|
|
|
48
48
|
* per chain — the provider package's pins (`lifiPins`, `fyndPins`, `uniswapPins`…, spread into
|
|
49
49
|
* these options). The app's lists, not the server's: without them the engine refuses this stub's
|
|
50
50
|
* EVM calldata routes and approvals, so a compromised backend cannot name its own contract or
|
|
51
|
-
* spender (scan 2026-10-27 modules, F8/F9/F16). Deposit-address routes need
|
|
51
|
+
* spender (scan 2026-10-27 modules, F8/F9/F16). Deposit-address routes need `depositAddressRoutes`.
|
|
52
52
|
*/
|
|
53
53
|
callTargets?: (chain: string) => readonly string[];
|
|
54
54
|
spenders?: (chain: string) => readonly string[];
|
|
@@ -63,6 +63,13 @@ interface RemoteProviderOptions {
|
|
|
63
63
|
* for: the reason it fails, or null. A build it refuses is never approved or signed.
|
|
64
64
|
*/
|
|
65
65
|
verifyBuild?: (quote: Quote, tx: UnsignedTx, params?: QuoteParams) => string | null | Promise<string | null>;
|
|
66
|
+
/**
|
|
67
|
+
* Let this stub's routes send the input to an address the backend names: a deposit address (NEAR Intents, a
|
|
68
|
+
* THORChain or Chainflip Tron vault) or a plain value send. Off by default: such a route bypasses `callTargets`, so
|
|
69
|
+
* a compromised backend could name its own address (scan 2026-10-28, F9). Opt in only for a provider whose deposit
|
|
70
|
+
* addresses cannot be pinned, or whose `verifyQuote` / `verifyBuild` checks them (`thorchainPins`, `chainflipPins`).
|
|
71
|
+
*/
|
|
72
|
+
depositAddressRoutes?: boolean;
|
|
66
73
|
/**
|
|
67
74
|
* Allow a non-https absolute `baseUrl` (local development only). Quotes, builds and typed orders travel this
|
|
68
75
|
* channel; over plain http anyone on the path can rewrite them (scan 2026-10-27 modules, F4).
|
package/dist/remote.js
CHANGED
package/dist/server.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.ts","../src/util.ts","../src/validate.ts"],"sourcesContent":["import type {Asset, CrossChainProvider, IntentRef, Quote, QuoteParams, TxRequest, UnsignedTx} from \"./types.js\";\nimport {redactUrls} from \"./util.js\";\nimport {validQuote} from \"./validate.js\";\n\n/**\n * The server side of {@link remoteProvider}: runs real providers behind your backend so API\n * keys, affiliate settings and non-CORS endpoints never ship in the app.\n *\n * Framework-free: wire `handle(action, body, context)` into any HTTP route that receives\n * `POST {baseUrl}/{action}` with a JSON body, and send back `{status, body}` as JSON.\n *\n * What it guarantees:\n * - every field of every request is validated (types, lengths, digit-only amounts) before a\n * provider sees it; unknown actions, providers and malformed bodies are refused;\n * - `quote` answers are sealed with HMAC-SHA256 under `sealSecret`; `build` and `preflight`\n * refuse a quote that was not sealed here, was changed in transit, or has expired — so a\n * client cannot make your server build a deposit for terms the provider never quoted;\n * - every quote's `intentId` leaves with an intent token appended (`<id>~<tag>`): `track`,\n * `claim`, `notify` and `register` refuse an id without a valid one (403), and the provider\n * only ever sees its own id. With `subject`, quotes and tokens are bound to the caller, so\n * another user's sealed quote or swap id is refused too; without it the token is a bearer\n * capability for that one swap (share a transaction hash with support, not the swap id);\n * - a provider's named refusal travels in-band (`{noQuote}`) so the app can show WHY;\n * - error text crossing the boundary has URLs redacted (no upstream endpoints or keys).\n *\n * What it does NOT do, and you must: authenticate the caller and rate-limit per user/IP in\n * `guard` (see {@link Swap2HandlerOptions.guard}), cap the raw request size before parsing\n * (`serializedTx` may legitimately be ~2 MB), and log without request bodies.\n *\n * Server-only (WebCrypto HMAC; Node ≥ 20, Deno, Bun, edge runtimes). Never import it into an app.\n */\n\n/** One call, as `guard` sees it. `dry` quotes are display quotes: give them their own, larger bucket. */\nexport interface Swap2Call {\n action: Swap2Action;\n provider: string;\n dry: boolean;\n /**\n * The validated request as the provider will get it — amounts, recipients, the provider's own\n * intent id (token removed) — for limits that depend on what is asked. Not yet checked against\n * the seal or the intent token: those run after the guard.\n */\n request: Swap2Request;\n}\n\n/** The validated body of each action. */\nexport type Swap2Request =\n | {params: QuoteParams}\n | {quote: Quote; tx?: UnsignedTx}\n | {ref: IntentRef}\n | {intentId: string; txHash: string}\n | {intentId: string; signature: string}\n | {from: Asset; to: Asset};\n\nexport type Swap2Action = \"quote\" | \"build\" | \"preflight\" | \"track\" | \"claim\" | \"notify\" | \"register\" | \"supports\";\n\nexport interface Swap2HandlerOptions<C> {\n /** The real providers, by `name` (the same names the app's `remoteProvider`s use). */\n providers: readonly CrossChainProvider[];\n /** HMAC key for quote seals: at least 32 bytes of randomness, kept server-side, rotated with care. */\n sealSecret: string | Uint8Array;\n /**\n * Authentication and rate limiting — required, because this endpoint spends your provider\n * quotas and builds deposits. Throw {@link Swap2HandlerError} (401 / 403 / 429) to refuse.\n * `context` is whatever your route passes as the third argument (the request, a session…).\n */\n guard: (call: Swap2Call, context: C) => void | Promise<void>;\n /**\n * Who is calling: a stable id of the authenticated user — the Sign-In with Ethereum address,\n * not an email — or undefined. Quotes are sealed for, and intent tokens issued to, this subject:\n * another caller's quote or swap id is refused (403). Without it, the intent token in a swap\n * id is a bearer capability. Runs after `guard`.\n */\n subject?: (context: C) => string | undefined | Promise<string | undefined>;\n /**\n * Moving to this handler from another backend: until this time (ms since epoch), `track`\n * accepts a swap id without an intent token, so swaps started before the move can still be\n * followed. Read-only: `claim`, `notify` and `register` always require the token.\n */\n acceptUntokenedTrackUntil?: number;\n /**\n * Quote tokens the swap catalog does not list (`params.sourceToken` an EVM contract or Solana\n * mint, with `params.sourceDecimals`), for apps whose users add their own tokens. Off by default:\n * `sourceDecimals` is then dropped and the providers refuse an unlisted source. Destination tokens\n * are addresses either way.\n */\n allowUnlistedTokens?: boolean;\n /** Clock for expiry checks (tests). */\n now?: () => number;\n}\n\nexport interface Swap2Response {\n status: number;\n body: unknown;\n}\n\n/** Throw from `guard` (or anywhere) to answer with this status and message. */\nexport class Swap2HandlerError extends Error {\n readonly status: number;\n constructor(status: number, message: string) {\n super(message);\n this.name = \"Swap2HandlerError\";\n this.status = status;\n }\n}\n\nconst ACTIONS: ReadonlySet<string> = new Set([\"quote\", \"build\", \"preflight\", \"track\", \"claim\", \"notify\", \"register\", \"supports\"]);\nconst SEAL_FIELD = \"__remoteSeal\";\nconst SEAL_PREFIX = \"swap2-remote-quote:v1:\";\nconst INTENT_PREFIX = \"swap2-remote-intent:v1\";\n/** `<provider's intent id>~<128-bit HMAC tag>`. */\nconst TOKENED = /^([\\s\\S]+)~([0-9a-f]{32})$/;\nconst INTENT_MAX = 2000;\nconst WIRE_INTENT_MAX = INTENT_MAX + 33;\n\nexport function createSwap2Handler<C = unknown>(opts: Swap2HandlerOptions<C>): (action: string, body: unknown, context: C) => Promise<Swap2Response> {\n if (typeof opts.guard !== \"function\") throw new Error(\"swap2-server: guard is required (authentication + rate limiting)\");\n const secret = typeof opts.sealSecret === \"string\" ? new TextEncoder().encode(opts.sealSecret) : opts.sealSecret;\n if (!(secret instanceof Uint8Array) || secret.length < 32) throw new Error(\"swap2-server: sealSecret must be at least 32 bytes\");\n const byName = new Map<string, CrossChainProvider>();\n for (const p of opts.providers) {\n if (byName.has(p.name)) throw new Error(`swap2-server: duplicate provider '${p.name}'`);\n byName.set(p.name, p);\n }\n const now = opts.now ?? Date.now;\n const keyPromise = crypto.subtle.importKey(\"raw\", secret as BufferSource, {name: \"HMAC\", hash: \"SHA-256\"}, false, [\"sign\", \"verify\"]);\n\n // The subject is JSON-quoted, so it cannot run into the canonical quote that follows (\"{\").\n const sealInput = (provider: string, subject: string | undefined, quote: Quote) =>\n new TextEncoder().encode(SEAL_PREFIX + provider + \":\" + (subject === undefined ? \"\" : JSON.stringify(subject) + \":\") + canonical(withoutSeal(quote)));\n async function seal(provider: string, subject: string | undefined, quote: Quote): Promise<Quote> {\n const tag = new Uint8Array(await crypto.subtle.sign(\"HMAC\", await keyPromise, sealInput(provider, subject, quote)));\n return {...quote, providerData: {...(quote.providerData ?? {}), [SEAL_FIELD]: toHex(tag)}};\n }\n async function verify(provider: string, subject: string | undefined, quote: Quote): Promise<boolean> {\n if (quote.provider !== provider) return false;\n const sealHex = quote.providerData?.[SEAL_FIELD];\n if (!sealHex || !/^[0-9a-f]{64}$/.test(sealHex)) return false;\n return crypto.subtle.verify(\"HMAC\", await keyPromise, fromHex(sealHex) as BufferSource, sealInput(provider, subject, quote)); // constant-time\n }\n /** The intent token: HMAC over (provider, subject, the provider's intent id), domain-separated from the seal. */\n async function intentTag(provider: string, subject: string | undefined, intentId: string): Promise<string> {\n const data = new TextEncoder().encode(JSON.stringify([INTENT_PREFIX, provider, subject ?? null, intentId]));\n return toHex(new Uint8Array(await crypto.subtle.sign(\"HMAC\", await keyPromise, data)).subarray(0, 16));\n }\n /** A swap id from the app → the provider's own id, or 403. */\n async function untoken(provider: string, subject: string | undefined, wireId: string, action: Swap2Action): Promise<string> {\n const m = TOKENED.exec(wireId);\n if (!m) {\n if (action === \"track\" && opts.acceptUntokenedTrackUntil !== undefined && now() < opts.acceptUntokenedTrackUntil) return wireId;\n throw new Swap2HandlerError(403, \"swap id carries no intent token from this server\");\n }\n if (!sameHex(await intentTag(provider, subject, m[1]!), m[2]!)) {\n throw new Swap2HandlerError(403, subject === undefined ? \"swap id carries an invalid intent token\" : \"swap id was not issued to this caller\");\n }\n return m[1]!;\n }\n /** The quote as the provider issued it: no seal, its own intent id. */\n const forProvider = (quote: Quote): Quote => ({...stripSeal(quote), intentId: TOKENED.exec(quote.intentId)?.[1] ?? quote.intentId});\n\n return async function handle(action: string, body: unknown, context: C): Promise<Swap2Response> {\n try {\n if (!ACTIONS.has(action)) return fail(404, `unknown action '${String(action).slice(0, 40)}'`);\n const b = record(body, \"body\");\n const providerName = str(b.provider, \"provider\", 40);\n const act = action as Swap2Action;\n const request = validated(act, b, opts.allowUnlistedTokens === true);\n const dry = \"params\" in request && request.params.dry === true;\n // The guard sees the provider's own intent id, as the provider will.\n const shown: Swap2Request = \"ref\" in request ? {ref: {...request.ref, intentId: TOKENED.exec(request.ref.intentId)?.[1] ?? request.ref.intentId}}\n : \"quote\" in request ? {...request, quote: forProvider(request.quote)}\n : \"intentId\" in request ? {...request, intentId: TOKENED.exec(request.intentId)?.[1] ?? request.intentId}\n : request;\n await opts.guard({action: act, provider: providerName, dry, request: shown}, context);\n const subject = opts.subject ? await opts.subject(context) : undefined;\n\n const provider = byName.get(providerName);\n if (!provider) return fail(404, `unknown provider '${providerName}'`);\n if (!provider.enabled()) return fail(403, `provider '${providerName}' is disabled`);\n\n switch (act) {\n case \"quote\": {\n const {params} = request as {params: QuoteParams};\n let refusal: string | null = null; // per request: a shared field races under parallel quotes\n const quote = await provider.getQuote({...params, onRefusal: (reason) => { refusal = reason; }});\n if (quote) {\n let normalized: Quote;\n try { normalized = normalizeQuote(quote, INTENT_MAX); } catch { return fail(502, `${providerName} returned a quote this server refuses to seal`); }\n // A seal vouches for the quote: hold it to the engine's own checks (provider, input, floor,\n // slippage, expiry) so a client that does not re-validate cannot be handed a bad one.\n if (!validQuote(normalized, params, providerName) || normalized.validUntilMs <= now()) {\n return fail(502, `${providerName} returned a quote the swap engine would reject`);\n }\n const tokened = {...normalized, intentId: `${normalized.intentId}~${await intentTag(providerName, subject, normalized.intentId)}`};\n return ok(await seal(providerName, subject, tokened));\n }\n if (refusal) return ok({noQuote: clientText(String(refusal))});\n return ok(params.dry === false ? {noQuote: `${providerName} refused without naming a reason`} : null);\n }\n case \"build\":\n case \"preflight\": {\n const {quote, tx} = request as {quote: Quote; tx?: UnsignedTx};\n if (!(await verify(providerName, subject, quote))) {\n return subject === undefined ? fail(400, \"quote integrity check failed\") : fail(403, \"quote was not sealed for this caller\");\n }\n if (quote.validUntilMs < now()) return fail(410, \"quote expired: ask for a new one\");\n if (act === \"build\") return ok(await refused(() => provider.buildDeposit(forProvider(quote))));\n if (!provider.preflightDeposit) return ok({});\n await refused(() => provider.preflightDeposit!(forProvider(quote), tx!));\n return ok({});\n }\n case \"track\":\n case \"claim\": {\n const {ref} = request as {ref: IntentRef};\n const own = {...ref, intentId: await untoken(providerName, subject, ref.intentId, act)};\n if (act === \"track\") return ok(await provider.trackIntent(own));\n if (!provider.buildClaim) return ok({});\n return ok(await refused(() => provider.buildClaim!(own)));\n }\n case \"notify\": {\n const {intentId, txHash} = request as {intentId: string; txHash: string};\n await provider.notifyDeposit?.(await untoken(providerName, subject, intentId, act), txHash);\n return ok({});\n }\n case \"register\": {\n if (!provider.registerSignedOrder) return fail(400, `provider '${providerName}' has no signed-order flow`);\n const {intentId, signature} = request as {intentId: string; signature: string};\n await provider.registerSignedOrder(await untoken(providerName, subject, intentId, act), signature);\n return ok({});\n }\n case \"supports\": {\n const {from, to} = request as {from: Asset; to: Asset};\n return ok({supported: provider.supportsRoute(from, to)});\n }\n }\n } catch (err) {\n if (err instanceof Swap2HandlerError) return fail(err.status, err.message);\n return fail(500, \"internal error\"); // never echo provider internals or upstream URLs\n }\n };\n}\n\n/** Each action's body, validated field by field before anyone (guard, provider) sees it. */\nfunction validated(action: Swap2Action, b: Record<string, unknown>, allowUnlisted: boolean): Swap2Request {\n switch (action) {\n case \"quote\": return {params: quoteParams(b.params, allowUnlisted)};\n case \"build\": return {quote: normalizeQuote(b.quote, WIRE_INTENT_MAX)};\n case \"preflight\": return {quote: normalizeQuote(b.quote, WIRE_INTENT_MAX), tx: unsignedTx(b.tx)};\n case \"track\":\n case \"claim\": return {ref: intentRef(b.ref)};\n case \"notify\": return {intentId: str(b.intentId, \"intentId\", WIRE_INTENT_MAX), txHash: str(b.txHash, \"txHash\", 200)};\n case \"register\": {\n const signature = str(b.signature, \"signature\", 302);\n if (!/^0x[0-9a-fA-F]{2,300}$/.test(signature)) throw new Swap2HandlerError(400, \"signature: not a hex signature\");\n return {intentId: str(b.intentId, \"intentId\", WIRE_INTENT_MAX), signature};\n }\n case \"supports\": return {from: asset(b.from, \"from\"), to: asset(b.to, \"to\")};\n }\n}\n\n/** Constant-time comparison of two lowercase hex strings. */\nfunction sameHex(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return diff === 0;\n}\n\n// ---- refusals -------------------------------------------------------------------------------\n\n/** A provider's own refusal (\"lifi: …\") is actionable: 422 with the redacted text. Anything else is a 500. */\nasync function refused<T>(fn: () => Promise<T>): Promise<T> {\n try {\n return await fn();\n } catch (err) {\n if (err instanceof Error && /^[a-z0-9_-]+: \\S/i.test(err.message)) {\n throw new Swap2HandlerError(422, clientText(err.message));\n }\n throw err;\n }\n}\n\n/** What a client is shown of a provider's text: cut before the URL pass runs (scan 2026-10-27 modules, F34), no URLs, 300 characters. */\nconst clientText = (text: string): string => redactUrls(text.slice(0, 2_048)).slice(0, 300);\n\nconst ok = (body: unknown): Swap2Response => ({status: 200, body: body === undefined ? null : body});\nconst fail = (status: number, error: string): Swap2Response => ({status, body: {error: clientText(error)}});\n\n// ---- validation (the limits mirror a production backend's schema) --------------------------\n\nconst DIGITS = (max: number) => new RegExp(`^\\\\d{1,${max}}$`);\n\nfunction bad(field: string, why: string): never {\n throw new Swap2HandlerError(400, `${field}: ${why}`);\n}\nfunction record(v: unknown, field: string): Record<string, unknown> {\n if (!v || typeof v !== \"object\" || Array.isArray(v)) bad(field, \"expected an object\");\n return v as Record<string, unknown>;\n}\nfunction str(v: unknown, field: string, max: number): string {\n if (typeof v !== \"string\") bad(field, \"expected a string\");\n if (v.length > max) bad(field, `longer than ${max}`);\n return v;\n}\nfunction optStr(v: unknown, field: string, max: number): string | undefined {\n return v === undefined ? undefined : str(v, field, max);\n}\nfunction digits(v: unknown, field: string, max: number): string {\n const s = str(v, field, max);\n if (!DIGITS(max).test(s)) bad(field, \"expected base units (digits only)\");\n return s;\n}\nfunction num(v: unknown, field: string, min = 0, max = Number.MAX_SAFE_INTEGER): number {\n if (typeof v !== \"number\" || !Number.isFinite(v) || v < min || v > max) bad(field, \"expected a finite number in range\");\n return v;\n}\nfunction bool(v: unknown, field: string): boolean | undefined {\n if (v === undefined) return undefined;\n if (typeof v !== \"boolean\") bad(field, \"expected a boolean\");\n return v;\n}\n\nfunction quoteParams(v: unknown, allowUnlisted = false): QuoteParams {\n const p = record(v, \"params\");\n const sourceDecimals = !allowUnlisted || p.sourceDecimals === undefined ? undefined : num(p.sourceDecimals, \"params.sourceDecimals\", 0, 77);\n if (sourceDecimals !== undefined && !Number.isInteger(sourceDecimals)) bad(\"params.sourceDecimals\", \"expected an integer\");\n const slippage = p.slippageBps === undefined ? undefined : num(p.slippageBps, \"params.slippageBps\", 0, 10_000);\n if (slippage !== undefined && !Number.isInteger(slippage)) bad(\"params.slippageBps\", \"expected an integer\");\n const refundAddress = optStr(p.refundAddress, \"params.refundAddress\", 120);\n const minAmountOut = p.minAmountOut === undefined ? undefined : digits(p.minAmountOut, \"params.minAmountOut\", 60);\n const dry = bool(p.dry, \"params.dry\");\n const flex = bool(p.flex, \"params.flex\");\n return {\n sourceChain: str(p.sourceChain, \"params.sourceChain\", 64),\n sourceToken: str(p.sourceToken, \"params.sourceToken\", 64),\n ...(sourceDecimals !== undefined ? {sourceDecimals} : {}),\n destChain: str(p.destChain, \"params.destChain\", 64),\n destToken: str(p.destToken, \"params.destToken\", 120),\n amountIn: digits(p.amountIn, \"params.amountIn\", 40),\n destAddress: str(p.destAddress, \"params.destAddress\", 120),\n ...(refundAddress !== undefined ? {refundAddress} : {}),\n ...(minAmountOut !== undefined ? {minAmountOut} : {}),\n ...(slippage !== undefined ? {slippageBps: slippage} : {}),\n ...(dry !== undefined ? {dry} : {}),\n ...(flex !== undefined ? {flex} : {}),\n };\n}\n\nconst FEE_TYPES = new Set([\"network\", \"protocol\", \"affiliate\", \"other\"]);\n\n/** Keeps only the known Quote fields, each checked: what is sealed is exactly what is verified. */\nfunction normalizeQuote(v: unknown, intentMax: number): Quote {\n const q = record(v, \"quote\");\n const out: Quote = {\n provider: str(q.provider, \"quote.provider\", 40),\n intentId: str(q.intentId, \"quote.intentId\", intentMax),\n depositAddress: str(q.depositAddress, \"quote.depositAddress\", 200),\n depositMemo: q.depositMemo === null ? null : str(q.depositMemo, \"quote.depositMemo\", 400),\n sourceAmount: digits(q.sourceAmount, \"quote.sourceAmount\", 60),\n destAmount: digits(q.destAmount, \"quote.destAmount\", 60),\n minDestAmount: digits(q.minDestAmount, \"quote.minDestAmount\", 60),\n feeUsd: num(q.feeUsd, \"quote.feeUsd\"),\n etaSeconds: num(q.etaSeconds, \"quote.etaSeconds\"),\n reliability: num(q.reliability, \"quote.reliability\", 0, 1),\n validUntilMs: num(q.validUntilMs, \"quote.validUntilMs\"),\n };\n if (q.txRequest !== undefined) out.txRequest = txRequest(q.txRequest);\n if (q.fees !== undefined) {\n if (!Array.isArray(q.fees) || q.fees.length > 32) bad(\"quote.fees\", \"expected up to 32 entries\");\n out.fees = q.fees.map((f, i) => {\n const fee = record(f, `quote.fees[${i}]`);\n const type = str(fee.type, `quote.fees[${i}].type`, 20);\n if (!FEE_TYPES.has(type)) bad(`quote.fees[${i}].type`, \"unknown fee type\");\n const description = optStr(fee.description, `quote.fees[${i}].description`, 500);\n const included = bool(fee.included, `quote.fees[${i}].included`);\n if (included === undefined) bad(`quote.fees[${i}].included`, \"expected a boolean\");\n return {\n type: type as \"network\" | \"protocol\" | \"affiliate\" | \"other\",\n amount: digits(fee.amount, `quote.fees[${i}].amount`, 80),\n token: str(fee.token, `quote.fees[${i}].token`, 200),\n included,\n ...(description !== undefined ? {description} : {}),\n };\n });\n }\n if (q.providerData !== undefined) {\n const data = record(q.providerData, \"quote.providerData\");\n const keys = Object.keys(data);\n if (keys.length > 64) bad(\"quote.providerData\", \"more than 64 entries\");\n out.providerData = Object.fromEntries(keys.map((k) => [str(k, \"quote.providerData key\", 200), str(data[k], `quote.providerData.${k.slice(0, 40)}`, 10_000)]));\n }\n return out;\n}\n\nfunction txRequest(v: unknown): TxRequest {\n const t = record(v, \"quote.txRequest\");\n const out: TxRequest = {};\n if (t.to !== undefined) out.to = str(t.to, \"quote.txRequest.to\", 200);\n if (t.data !== undefined) out.data = str(t.data, \"quote.txRequest.data\", 1_000_000);\n if (t.value !== undefined) out.value = digits(t.value, \"quote.txRequest.value\", 80);\n if (t.chainId !== undefined) {\n const id = num(t.chainId, \"quote.txRequest.chainId\", 1);\n if (!Number.isInteger(id)) bad(\"quote.txRequest.chainId\", \"expected an integer\");\n out.chainId = id;\n }\n if (t.gasLimit !== undefined) out.gasLimit = digits(t.gasLimit, \"quote.txRequest.gasLimit\", 80);\n if (t.approvalAddress !== undefined) out.approvalAddress = str(t.approvalAddress, \"quote.txRequest.approvalAddress\", 200);\n if (t.serializedTx !== undefined) out.serializedTx = str(t.serializedTx, \"quote.txRequest.serializedTx\", 2_000_000);\n if (t.nativeFee !== undefined) out.nativeFee = digits(t.nativeFee, \"quote.txRequest.nativeFee\", 80);\n return out;\n}\n\n/** The transaction the client built from `build` (bigints arrive as decimal strings). */\nfunction unsignedTx(v: unknown): UnsignedTx {\n const t = record(v, \"tx\");\n const out: UnsignedTx = {chain: str(t.chain, \"tx.chain\", 64), to: str(t.to, \"tx.to\", 200), value: digits(t.value, \"tx.value\", 80)};\n const optional: [keyof UnsignedTx, number][] = [[\"data\", 1_000_000], [\"gasLimit\", 80], [\"approvalAddress\", 200], [\"serializedTx\", 2_000_000], [\"memo\", 2_000], [\"bodyBoc\", 200_000], [\"stateInitBoc\", 200_000], [\"typedData\", 200_000]];\n for (const [k, max] of optional) if (t[k] !== undefined) (out as unknown as Record<string, unknown>)[k] = k === \"gasLimit\" ? digits(t[k], `tx.${k}`, max) : str(t[k], `tx.${k}`, max);\n if (t.memoEncoding !== undefined) {\n if (t.memoEncoding !== \"utf8\" && t.memoEncoding !== \"hex\") bad(\"tx.memoEncoding\", \"expected utf8 or hex\");\n out.memoEncoding = t.memoEncoding;\n }\n if (t.chainId !== undefined) out.chainId = num(t.chainId, \"tx.chainId\", 1);\n return out;\n}\n\nfunction intentRef(v: unknown): IntentRef {\n const r = record(v, \"ref\");\n const nullable = (x: unknown, f: string) => (x === null || x === undefined ? undefined : str(x, f, 200));\n const sourceTxHash = nullable(r.sourceTxHash, \"ref.sourceTxHash\");\n const destTxHash = nullable(r.destTxHash, \"ref.destTxHash\");\n return {\n intentId: str(r.intentId, \"ref.intentId\", WIRE_INTENT_MAX),\n ...(sourceTxHash !== undefined ? {sourceTxHash} : {}),\n ...(destTxHash !== undefined ? {destTxHash} : {}),\n };\n}\n\nfunction asset(v: unknown, field: string): Asset {\n const a = record(v, field);\n return {chain: str(a.chain, `${field}.chain`, 64), token: str(a.token, `${field}.token`, 120)};\n}\n\n// ---- seal ------------------------------------------------------------------------------------\n\nfunction withoutSeal(quote: Quote): Quote {\n const {providerData, ...rest} = quote;\n if (!providerData) return rest;\n const {[SEAL_FIELD]: _seal, ...data} = providerData;\n return Object.keys(data).length ? {...rest, providerData: data} : rest;\n}\n/** The provider gets its quote back exactly as it produced it. */\nconst stripSeal = withoutSeal;\n\n/** Deterministic JSON: sorted keys, `undefined` dropped, non-finite numbers refused. */\nfunction canonical(value: unknown): string {\n if (value === null || typeof value === \"string\" || typeof value === \"boolean\") return JSON.stringify(value);\n if (typeof value === \"number\") {\n if (!Number.isFinite(value)) throw new Swap2HandlerError(400, \"quote contains a non-finite number\");\n return JSON.stringify(value);\n }\n if (Array.isArray(value)) return `[${value.map(canonical).join(\",\")}]`;\n if (typeof value === \"object\") {\n const r = value as Record<string, unknown>;\n return `{${Object.keys(r).filter((k) => r[k] !== undefined).sort().map((k) => `${JSON.stringify(k)}:${canonical(r[k])}`).join(\",\")}}`;\n }\n throw new Swap2HandlerError(400, \"quote is not JSON-safe\");\n}\n\nfunction toHex(bytes: Uint8Array): string {\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\nfunction fromHex(hex: string): Uint8Array {\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n","import {resolveAsset, type CcAsset} from \"@anywalletkit/swap2-assets\";\nimport type {QuoteParams} from \"./types.js\";\n/** Shared CAIP-2 helpers. Contribution packages never read deployment env. */\nexport function namespace(chain: string): string {\n return chain.split(\":\")[0] ?? \"\";\n}\n/** True for an `eip155:` chain id. */\nexport function isEvm(chain: string): boolean {\n return namespace(chain) === \"eip155\";\n}\n/** Numeric chain id of an `eip155:<id>` CAIP-2 id. */\nexport function evmRef(chain: string): number {\n return Number(chain.split(\":\")[1] ?? \"0\");\n}\n\n/**\n * `AbortSignal.timeout`, portably: Hermes (React Native) ships AbortController\n * but not the static helpers, and the unguarded call threw\n * \"AbortSignal.timeout is not a function\" on every remote execution — which\n * the auction then read as \"no route\". Falls back to a manual controller.\n */\nexport function timeoutSignal(ms: number): AbortSignal {\n const native = (AbortSignal as unknown as {timeout?: (ms: number) => AbortSignal}).timeout;\n if (typeof native === \"function\") return native.call(AbortSignal, ms);\n const ctrl = new AbortController();\n setTimeout(() => ctrl.abort(), ms);\n return ctrl.signal;\n}\n\n/** `AbortSignal.any`, portably (see timeoutSignal). */\nexport function anySignal(signals: readonly AbortSignal[]): AbortSignal {\n const native = (AbortSignal as unknown as {any?: (s: readonly AbortSignal[]) => AbortSignal}).any;\n if (typeof native === \"function\") return native.call(AbortSignal, signals);\n const ctrl = new AbortController();\n for (const s of signals) {\n if (s.aborted) {\n ctrl.abort();\n break;\n }\n s.addEventListener(\"abort\", () => ctrl.abort(), {once: true});\n }\n return ctrl.signal;\n}\n\n/** Longest URL scheme taken out (`https`, `wss`, `git+ssh`…): a letter and up to 31 of `[A-Za-z0-9+.-]`. */\nconst SCHEME_MAX = 32;\nconst isLetter = (c: number): boolean => (c >= 97 && c <= 122) || (c >= 65 && c <= 90);\nconst isSchemeChar = (c: number): boolean => isLetter(c) || (c >= 48 && c <= 57) || c === 43 || c === 45 || c === 46;\n/** What ends a URL in text: `\\s` as regexes define it, and `\"` `'` `<` `>`. */\nconst endsUrl = (c: number): boolean =>\n c === 34 || c === 39 || c === 60 || c === 62 || (c >= 9 && c <= 13) || c === 32 || c === 0xa0 || c === 0x1680 ||\n (c >= 0x2000 && c <= 0x200a) || c === 0x2028 || c === 0x2029 || c === 0x202f || c === 0x205f || c === 0x3000 || c === 0xfeff;\n\n/**\n * Remove URLs from text that may travel to a client (a refusal reason, a build error):\n * transport errors carry the endpoint — and with a keyed RPC (QuickNode, Alchemy,\n * Infura…) the key sits in its path (scan 2026-10-23 F8). What `/[a-z][a-z0-9+.-]{0,31}:\\/\\/[^\\s\"'<>]+/gi`\n * would replace, found from each `://` instead of tried at every position: linear in the length of\n * `text` whatever an API or a client puts in it (\"a.a.a…\" made the regex this replaced quadratic;\n * scan 2026-10-27 modules, F34). A URL glued to a word loses its last 32 scheme characters and the rest.\n */\nexport function redactUrls(text: string): string {\n let out = \"\";\n let done = 0; // text[0, done) is in `out`\n for (let at = text.indexOf(\"://\"); at !== -1; at = text.indexOf(\"://\", at + 3)) {\n let start = at;\n while (start > done && at - start < SCHEME_MAX && isSchemeChar(text.charCodeAt(start - 1))) start--;\n while (start < at && !isLetter(text.charCodeAt(start))) start++;\n if (start === at) continue; // no scheme: not a URL (and its tail is not scanned, so \"://://…\" stays linear)\n let end = at + 3;\n while (end < text.length && !endsUrl(text.charCodeAt(end))) end++;\n if (end === at + 3) continue; // nothing after \"://\"\n out += `${text.slice(done, start)}<url>`;\n done = end;\n at = end - 3;\n }\n return done === 0 ? text : out + text.slice(done);\n}\n\n/**\n * The source asset a provider is asked to sell: the catalog's entry for `params.sourceToken`, or —\n * only when the engine sent `sourceDecimals` (an unlisted token the user added) — an unlisted asset\n * named by its contract / mint. Providers that can trade any token use this instead of the catalog\n * lookup; the others keep refusing what the catalog does not list.\n */\nexport function sourceAssetOf(params: Pick<QuoteParams, \"sourceChain\" | \"sourceToken\" | \"sourceDecimals\">): CcAsset | undefined {\n const decimals = params.sourceDecimals;\n return resolveAsset(params.sourceChain, params.sourceToken, decimals === undefined ? {} : {allowUnlisted: true, decimals});\n}\n\nconst B64 = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n/** Standard or URL-safe base64 → bytes, without Buffer or atob (Hermes); null for anything else. */\nexport function base64Bytes(text: string): Uint8Array | null {\n const s = text.replace(/-/g, \"+\").replace(/_/g, \"/\").replace(/=+$/, \"\");\n if (!/^[A-Za-z0-9+/]*$/.test(s) || s.length % 4 === 1) return null;\n const out = new Uint8Array(Math.floor((s.length * 3) / 4));\n let bits = 0, acc = 0, n = 0;\n for (const ch of s) {\n acc = (acc << 6) | B64.indexOf(ch);\n bits += 6;\n if (bits >= 8) { bits -= 8; out[n++] = (acc >> bits) & 0xff; }\n }\n return out.subarray(0, n);\n}\n\n/** CRC16-XMODEM, the checksum of a user-friendly TON address. */\nfunction crc16(bytes: Uint8Array): number {\n let crc = 0;\n for (const b of bytes) {\n crc ^= b << 8;\n for (let i = 0; i < 8; i++) crc = crc & 0x8000 ? ((crc << 1) ^ 0x1021) & 0xffff : (crc << 1) & 0xffff;\n }\n return crc;\n}\n\n/**\n * A TON address in raw form (`<workchain>:<64 hex>`, lowercase) from a raw string, a user-friendly one (bounceable or\n * not, checksum verified), or an address object (`toRawString()` / `workChain` + `hash`); null for anything else. Two\n * spellings of one address compare equal in this form.\n */\nexport function tonRawAddress(address: unknown): string | null {\n if (address && typeof address === \"object\") {\n const o = address as {toRawString?: () => string; workChain?: unknown; hash?: unknown};\n if (typeof o.toRawString === \"function\") { try { return tonRawAddress(o.toRawString()); } catch { return null; } }\n if (typeof o.workChain === \"number\" && o.hash instanceof Uint8Array && o.hash.length === 32) {\n return `${o.workChain}:${Array.from(o.hash, (b) => b.toString(16).padStart(2, \"0\")).join(\"\")}`;\n }\n return null;\n }\n if (typeof address !== \"string\") return null;\n const raw = /^(-?[0-9]{1,3}):([0-9a-fA-F]{64})$/.exec(address.trim());\n if (raw) return `${Number(raw[1])}:${raw[2]!.toLowerCase()}`;\n if (!/^[A-Za-z0-9+/_-]{48}$/.test(address.trim())) return null;\n const bytes = base64Bytes(address.trim());\n if (!bytes || bytes.length !== 36 || crc16(bytes.subarray(0, 34)) !== ((bytes[34]! << 8) | bytes[35]!)) return null;\n const wc = bytes[1] === 0xff ? -1 : bytes[1]!;\n return `${wc}:${Array.from(bytes.subarray(2, 34), (b) => b.toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n","import type {CrossChainProvider, Quote, QuoteParams} from \"./types.js\";\n\n/** Shared quote validator used by both the aggregate and single-provider paths. */\nexport function validQuote(q: Quote, params: QuoteParams, provider: string): boolean {\n try {\n if (q.provider !== provider || q.sourceAmount !== params.amountIn) return false;\n if (typeof q.intentId !== \"string\" || !q.intentId || typeof q.depositAddress !== \"string\" || (q.depositMemo !== null && typeof q.depositMemo !== \"string\")) return false;\n if (![q.sourceAmount, q.destAmount, q.minDestAmount].every((value) => /^[0-9]+$/.test(value))) return false;\n const source = BigInt(q.sourceAmount);\n const dest = BigInt(q.destAmount);\n const min = BigInt(q.minDestAmount);\n if (source <= 0n || dest <= 0n || min <= 0n || min > dest) return false;\n if (params.minAmountOut !== undefined) {\n if (!/^[0-9]+$/.test(params.minAmountOut) || BigInt(params.minAmountOut) <= 0n || min < BigInt(params.minAmountOut)) return false;\n }\n if (params.slippageBps !== undefined) {\n if (!Number.isInteger(params.slippageBps) || params.slippageBps < 0 || params.slippageBps >= 10_000) return false;\n // Providers round the floor DOWN (1Click, LI.FI: floor(dest × (1 − s))).\n // A ceil here refused nearly every slippage-bounded quote by one base unit.\n const required = (dest * BigInt(10_000 - params.slippageBps)) / 10_000n;\n if (min < required) return false;\n }\n if (!Number.isFinite(q.etaSeconds) || q.etaSeconds < 0) return false;\n if (!Number.isFinite(q.reliability) || q.reliability < 0 || q.reliability > 1) return false;\n if (!Number.isFinite(q.feeUsd) || q.feeUsd < 0) return false;\n if (q.fees && (!Array.isArray(q.fees) || !q.fees.every((fee) =>\n [\"network\", \"protocol\", \"affiliate\", \"other\"].includes(fee.type) &&\n /^[0-9]+$/.test(fee.amount) &&\n typeof fee.token === \"string\" && fee.token.length > 0 &&\n typeof fee.included === \"boolean\" &&\n (fee.description === undefined || typeof fee.description === \"string\")\n ))) return false;\n if (q.providerData && (Array.isArray(q.providerData) || typeof q.providerData !== \"object\" || !Object.values(q.providerData).every((value) => typeof value === \"string\"))) return false;\n const tx = q.txRequest;\n if (tx) {\n if (tx.value !== undefined && !/^[0-9]+$/.test(tx.value)) return false;\n if (tx.gasLimit !== undefined && !/^[0-9]+$/.test(tx.gasLimit)) return false;\n if (tx.chainId !== undefined && (!Number.isSafeInteger(tx.chainId) || tx.chainId <= 0)) return false;\n if ([tx.to, tx.data, tx.approvalAddress, tx.serializedTx].some((value) => value !== undefined && typeof value !== \"string\")) return false;\n }\n // Execution is always re-quoted. An unknown/zero expiry cannot prove that\n // the transaction is still safe to sign, so contribution packages reject it.\n if (!Number.isFinite(q.validUntilMs) || q.validUntilMs <= Date.now()) return false;\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Why a remote provider's quote cannot execute in this app, or null: its EVM transaction calls or\n * approves an address the app did not pin (`remoteProvider` `callTargets` / `spenders`). The router\n * and the single-provider path drop such a quote with this reason, so the auction never picks a\n * route execution would refuse (scan 2026-10-27 modules, F8/F9/F16); execution checks the build\n * again, in full. Local providers and deposit-address routes are not judged here.\n */\nexport function remotePinRefusal(provider: Pick<CrossChainProvider, \"name\" | \"remote\" | \"callTargets\" | \"spenders\">, q: Quote, chain: string): string | null {\n if (provider.remote !== true || !chain.startsWith(\"eip155:\")) return null;\n const tx = q.txRequest;\n if (!tx || tx.serializedTx !== undefined) return null;\n const pinned = (list: readonly string[] | undefined, address: string) =>\n (list ?? []).some((a) => typeof a === \"string\" && a.toLowerCase() === address.toLowerCase());\n if (tx.approvalAddress && !pinned(provider.spenders?.(chain), tx.approvalAddress)) {\n return `${provider.name}: the route approves ${tx.approvalAddress}, which the app did not pin as a spender on ${chain} (remoteProvider spenders)`;\n }\n if (tx.data !== undefined && tx.to && !pinned(provider.callTargets?.(chain), tx.to)) {\n return `${provider.name}: the route calls ${tx.to}, which the app did not pin as a call target on ${chain} (remoteProvider callTargets)`;\n }\n return null;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/server.ts","../src/util.ts","../src/validate.ts"],"sourcesContent":["import type {Asset, CrossChainProvider, IntentRef, Quote, QuoteParams, TxRequest, UnsignedTx} from \"./types.js\";\nimport {redactUrls} from \"./util.js\";\nimport {validQuote} from \"./validate.js\";\n\n/**\n * The server side of {@link remoteProvider}: runs real providers behind your backend so API\n * keys, affiliate settings and non-CORS endpoints never ship in the app.\n *\n * Framework-free: wire `handle(action, body, context)` into any HTTP route that receives\n * `POST {baseUrl}/{action}` with a JSON body, and send back `{status, body}` as JSON.\n *\n * What it guarantees:\n * - every field of every request is validated (types, lengths, digit-only amounts) before a\n * provider sees it; unknown actions, providers and malformed bodies are refused;\n * - `quote` answers are sealed with HMAC-SHA256 under `sealSecret`; `build` and `preflight`\n * refuse a quote that was not sealed here, was changed in transit, or has expired — so a\n * client cannot make your server build a deposit for terms the provider never quoted;\n * - every quote's `intentId` leaves with an intent token appended (`<id>~<tag>`): `track`,\n * `claim`, `notify` and `register` refuse an id without a valid one (403), and the provider\n * only ever sees its own id. With `subject`, quotes and tokens are bound to the caller, so\n * another user's sealed quote or swap id is refused too; without it the token is a bearer\n * capability for that one swap (share a transaction hash with support, not the swap id);\n * - a provider's named refusal travels in-band (`{noQuote}`) so the app can show WHY;\n * - error text crossing the boundary has URLs redacted (no upstream endpoints or keys).\n *\n * What it does NOT do, and you must: authenticate the caller and rate-limit per user/IP in\n * `guard` (see {@link Swap2HandlerOptions.guard}), cap the raw request size before parsing\n * (`serializedTx` may legitimately be ~2 MB), and log without request bodies.\n *\n * Server-only (WebCrypto HMAC; Node ≥ 20, Deno, Bun, edge runtimes). Never import it into an app.\n */\n\n/** One call, as `guard` sees it. `dry` quotes are display quotes: give them their own, larger bucket. */\nexport interface Swap2Call {\n action: Swap2Action;\n provider: string;\n dry: boolean;\n /**\n * The validated request as the provider will get it — amounts, recipients, the provider's own\n * intent id (token removed) — for limits that depend on what is asked. Not yet checked against\n * the seal or the intent token: those run after the guard.\n */\n request: Swap2Request;\n}\n\n/** The validated body of each action. */\nexport type Swap2Request =\n | {params: QuoteParams}\n | {quote: Quote; tx?: UnsignedTx}\n | {ref: IntentRef}\n | {intentId: string; txHash: string}\n | {intentId: string; signature: string}\n | {from: Asset; to: Asset};\n\nexport type Swap2Action = \"quote\" | \"build\" | \"preflight\" | \"track\" | \"claim\" | \"notify\" | \"register\" | \"supports\";\n\nexport interface Swap2HandlerOptions<C> {\n /** The real providers, by `name` (the same names the app's `remoteProvider`s use). */\n providers: readonly CrossChainProvider[];\n /** HMAC key for quote seals: at least 32 bytes of randomness, kept server-side, rotated with care. */\n sealSecret: string | Uint8Array;\n /**\n * Authentication and rate limiting — required, because this endpoint spends your provider\n * quotas and builds deposits. Throw {@link Swap2HandlerError} (401 / 403 / 429) to refuse.\n * `context` is whatever your route passes as the third argument (the request, a session…).\n */\n guard: (call: Swap2Call, context: C) => void | Promise<void>;\n /**\n * Who is calling: a stable id of the authenticated user — the Sign-In with Ethereum address,\n * not an email — or undefined. Quotes are sealed for, and intent tokens issued to, this subject:\n * another caller's quote or swap id is refused (403). Without it, the intent token in a swap\n * id is a bearer capability. Runs after `guard`.\n */\n subject?: (context: C) => string | undefined | Promise<string | undefined>;\n /**\n * Moving to this handler from another backend: until this time (ms since epoch), `track`\n * accepts a swap id without an intent token, so swaps started before the move can still be\n * followed. Read-only: `claim`, `notify` and `register` always require the token.\n */\n acceptUntokenedTrackUntil?: number;\n /**\n * Quote tokens the swap catalog does not list (`params.sourceToken` an EVM contract or Solana\n * mint, with `params.sourceDecimals`), for apps whose users add their own tokens. Off by default:\n * `sourceDecimals` is then dropped and the providers refuse an unlisted source. Destination tokens\n * are addresses either way.\n */\n allowUnlistedTokens?: boolean;\n /** Clock for expiry checks (tests). */\n now?: () => number;\n}\n\nexport interface Swap2Response {\n status: number;\n body: unknown;\n}\n\n/** Throw from `guard` (or anywhere) to answer with this status and message. */\nexport class Swap2HandlerError extends Error {\n readonly status: number;\n constructor(status: number, message: string) {\n super(message);\n this.name = \"Swap2HandlerError\";\n this.status = status;\n }\n}\n\nconst ACTIONS: ReadonlySet<string> = new Set([\"quote\", \"build\", \"preflight\", \"track\", \"claim\", \"notify\", \"register\", \"supports\"]);\nconst SEAL_FIELD = \"__remoteSeal\";\nconst SEAL_PREFIX = \"swap2-remote-quote:v1:\";\nconst INTENT_PREFIX = \"swap2-remote-intent:v1\";\n/** `<provider's intent id>~<128-bit HMAC tag>`. */\nconst TOKENED = /^([\\s\\S]+)~([0-9a-f]{32})$/;\nconst INTENT_MAX = 2000;\nconst WIRE_INTENT_MAX = INTENT_MAX + 33;\n\nexport function createSwap2Handler<C = unknown>(opts: Swap2HandlerOptions<C>): (action: string, body: unknown, context: C) => Promise<Swap2Response> {\n if (typeof opts.guard !== \"function\") throw new Error(\"swap2-server: guard is required (authentication + rate limiting)\");\n const secret = typeof opts.sealSecret === \"string\" ? new TextEncoder().encode(opts.sealSecret) : opts.sealSecret;\n if (!(secret instanceof Uint8Array) || secret.length < 32) throw new Error(\"swap2-server: sealSecret must be at least 32 bytes\");\n const byName = new Map<string, CrossChainProvider>();\n for (const p of opts.providers) {\n if (byName.has(p.name)) throw new Error(`swap2-server: duplicate provider '${p.name}'`);\n byName.set(p.name, p);\n }\n const now = opts.now ?? Date.now;\n const keyPromise = crypto.subtle.importKey(\"raw\", secret as BufferSource, {name: \"HMAC\", hash: \"SHA-256\"}, false, [\"sign\", \"verify\"]);\n\n // The subject is JSON-quoted, so it cannot run into the canonical quote that follows (\"{\").\n const sealInput = (provider: string, subject: string | undefined, quote: Quote) =>\n new TextEncoder().encode(SEAL_PREFIX + provider + \":\" + (subject === undefined ? \"\" : JSON.stringify(subject) + \":\") + canonical(withoutSeal(quote)));\n async function seal(provider: string, subject: string | undefined, quote: Quote): Promise<Quote> {\n const tag = new Uint8Array(await crypto.subtle.sign(\"HMAC\", await keyPromise, sealInput(provider, subject, quote)));\n return {...quote, providerData: {...(quote.providerData ?? {}), [SEAL_FIELD]: toHex(tag)}};\n }\n async function verify(provider: string, subject: string | undefined, quote: Quote): Promise<boolean> {\n if (quote.provider !== provider) return false;\n const sealHex = quote.providerData?.[SEAL_FIELD];\n if (!sealHex || !/^[0-9a-f]{64}$/.test(sealHex)) return false;\n return crypto.subtle.verify(\"HMAC\", await keyPromise, fromHex(sealHex) as BufferSource, sealInput(provider, subject, quote)); // constant-time\n }\n /** The intent token: HMAC over (provider, subject, the provider's intent id), domain-separated from the seal. */\n async function intentTag(provider: string, subject: string | undefined, intentId: string): Promise<string> {\n const data = new TextEncoder().encode(JSON.stringify([INTENT_PREFIX, provider, subject ?? null, intentId]));\n return toHex(new Uint8Array(await crypto.subtle.sign(\"HMAC\", await keyPromise, data)).subarray(0, 16));\n }\n /** A swap id from the app → the provider's own id, or 403. */\n async function untoken(provider: string, subject: string | undefined, wireId: string, action: Swap2Action): Promise<string> {\n const m = TOKENED.exec(wireId);\n if (!m) {\n if (action === \"track\" && opts.acceptUntokenedTrackUntil !== undefined && now() < opts.acceptUntokenedTrackUntil) return wireId;\n throw new Swap2HandlerError(403, \"swap id carries no intent token from this server\");\n }\n if (!sameHex(await intentTag(provider, subject, m[1]!), m[2]!)) {\n throw new Swap2HandlerError(403, subject === undefined ? \"swap id carries an invalid intent token\" : \"swap id was not issued to this caller\");\n }\n return m[1]!;\n }\n /** The quote as the provider issued it: no seal, its own intent id. */\n const forProvider = (quote: Quote): Quote => ({...stripSeal(quote), intentId: TOKENED.exec(quote.intentId)?.[1] ?? quote.intentId});\n\n return async function handle(action: string, body: unknown, context: C): Promise<Swap2Response> {\n try {\n if (!ACTIONS.has(action)) return fail(404, `unknown action '${String(action).slice(0, 40)}'`);\n const b = record(body, \"body\");\n const providerName = str(b.provider, \"provider\", 40);\n const act = action as Swap2Action;\n const request = validated(act, b, opts.allowUnlistedTokens === true);\n const dry = \"params\" in request && request.params.dry === true;\n // The guard sees the provider's own intent id, as the provider will.\n const shown: Swap2Request = \"ref\" in request ? {ref: {...request.ref, intentId: TOKENED.exec(request.ref.intentId)?.[1] ?? request.ref.intentId}}\n : \"quote\" in request ? {...request, quote: forProvider(request.quote)}\n : \"intentId\" in request ? {...request, intentId: TOKENED.exec(request.intentId)?.[1] ?? request.intentId}\n : request;\n await opts.guard({action: act, provider: providerName, dry, request: shown}, context);\n const subject = opts.subject ? await opts.subject(context) : undefined;\n\n const provider = byName.get(providerName);\n if (!provider) return fail(404, `unknown provider '${providerName}'`);\n if (!provider.enabled()) return fail(403, `provider '${providerName}' is disabled`);\n\n switch (act) {\n case \"quote\": {\n const {params} = request as {params: QuoteParams};\n let refusal: string | null = null; // per request: a shared field races under parallel quotes\n const quote = await provider.getQuote({...params, onRefusal: (reason) => { refusal = reason; }});\n if (quote) {\n let normalized: Quote;\n try { normalized = normalizeQuote(quote, INTENT_MAX); } catch { return fail(502, `${providerName} returned a quote this server refuses to seal`); }\n // A seal vouches for the quote: hold it to the engine's own checks (provider, input, floor,\n // slippage, expiry) so a client that does not re-validate cannot be handed a bad one.\n if (!validQuote(normalized, params, providerName) || normalized.validUntilMs <= now()) {\n return fail(502, `${providerName} returned a quote the swap engine would reject`);\n }\n const tokened = {...normalized, intentId: `${normalized.intentId}~${await intentTag(providerName, subject, normalized.intentId)}`};\n return ok(await seal(providerName, subject, tokened));\n }\n if (refusal) return ok({noQuote: clientText(String(refusal))});\n return ok(params.dry === false ? {noQuote: `${providerName} refused without naming a reason`} : null);\n }\n case \"build\":\n case \"preflight\": {\n const {quote, tx} = request as {quote: Quote; tx?: UnsignedTx};\n if (!(await verify(providerName, subject, quote))) {\n return subject === undefined ? fail(400, \"quote integrity check failed\") : fail(403, \"quote was not sealed for this caller\");\n }\n if (quote.validUntilMs < now()) return fail(410, \"quote expired: ask for a new one\");\n if (act === \"build\") return ok(await refused(() => provider.buildDeposit(forProvider(quote))));\n if (!provider.preflightDeposit) return ok({});\n await refused(() => provider.preflightDeposit!(forProvider(quote), tx!));\n return ok({});\n }\n case \"track\":\n case \"claim\": {\n const {ref} = request as {ref: IntentRef};\n const own = {...ref, intentId: await untoken(providerName, subject, ref.intentId, act)};\n if (act === \"track\") return ok(await provider.trackIntent(own));\n if (!provider.buildClaim) return ok({});\n return ok(await refused(() => provider.buildClaim!(own)));\n }\n case \"notify\": {\n const {intentId, txHash} = request as {intentId: string; txHash: string};\n await provider.notifyDeposit?.(await untoken(providerName, subject, intentId, act), txHash);\n return ok({});\n }\n case \"register\": {\n if (!provider.registerSignedOrder) return fail(400, `provider '${providerName}' has no signed-order flow`);\n const {intentId, signature} = request as {intentId: string; signature: string};\n await provider.registerSignedOrder(await untoken(providerName, subject, intentId, act), signature);\n return ok({});\n }\n case \"supports\": {\n const {from, to} = request as {from: Asset; to: Asset};\n return ok({supported: provider.supportsRoute(from, to)});\n }\n }\n } catch (err) {\n if (err instanceof Swap2HandlerError) return fail(err.status, err.message);\n return fail(500, \"internal error\"); // never echo provider internals or upstream URLs\n }\n };\n}\n\n/** Each action's body, validated field by field before anyone (guard, provider) sees it. */\nfunction validated(action: Swap2Action, b: Record<string, unknown>, allowUnlisted: boolean): Swap2Request {\n switch (action) {\n case \"quote\": return {params: quoteParams(b.params, allowUnlisted)};\n case \"build\": return {quote: normalizeQuote(b.quote, WIRE_INTENT_MAX)};\n case \"preflight\": return {quote: normalizeQuote(b.quote, WIRE_INTENT_MAX), tx: unsignedTx(b.tx)};\n case \"track\":\n case \"claim\": return {ref: intentRef(b.ref)};\n case \"notify\": return {intentId: str(b.intentId, \"intentId\", WIRE_INTENT_MAX), txHash: str(b.txHash, \"txHash\", 200)};\n case \"register\": {\n const signature = str(b.signature, \"signature\", 302);\n if (!/^0x[0-9a-fA-F]{2,300}$/.test(signature)) throw new Swap2HandlerError(400, \"signature: not a hex signature\");\n return {intentId: str(b.intentId, \"intentId\", WIRE_INTENT_MAX), signature};\n }\n case \"supports\": return {from: asset(b.from, \"from\"), to: asset(b.to, \"to\")};\n }\n}\n\n/** Constant-time comparison of two lowercase hex strings. */\nfunction sameHex(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n let diff = 0;\n for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);\n return diff === 0;\n}\n\n// ---- refusals -------------------------------------------------------------------------------\n\n/** A provider's own refusal (\"lifi: …\") is actionable: 422 with the redacted text. Anything else is a 500. */\nasync function refused<T>(fn: () => Promise<T>): Promise<T> {\n try {\n return await fn();\n } catch (err) {\n if (err instanceof Error && /^[a-z0-9_-]+: \\S/i.test(err.message)) {\n throw new Swap2HandlerError(422, clientText(err.message));\n }\n throw err;\n }\n}\n\n/** What a client is shown of a provider's text: cut before the URL pass runs (scan 2026-10-27 modules, F34), no URLs, 300 characters. */\nconst clientText = (text: string): string => redactUrls(text.slice(0, 2_048)).slice(0, 300);\n\nconst ok = (body: unknown): Swap2Response => ({status: 200, body: body === undefined ? null : body});\nconst fail = (status: number, error: string): Swap2Response => ({status, body: {error: clientText(error)}});\n\n// ---- validation (the limits mirror a production backend's schema) --------------------------\n\nconst DIGITS = (max: number) => new RegExp(`^\\\\d{1,${max}}$`);\n\nfunction bad(field: string, why: string): never {\n throw new Swap2HandlerError(400, `${field}: ${why}`);\n}\nfunction record(v: unknown, field: string): Record<string, unknown> {\n if (!v || typeof v !== \"object\" || Array.isArray(v)) bad(field, \"expected an object\");\n return v as Record<string, unknown>;\n}\nfunction str(v: unknown, field: string, max: number): string {\n if (typeof v !== \"string\") bad(field, \"expected a string\");\n if (v.length > max) bad(field, `longer than ${max}`);\n return v;\n}\nfunction optStr(v: unknown, field: string, max: number): string | undefined {\n return v === undefined ? undefined : str(v, field, max);\n}\nfunction digits(v: unknown, field: string, max: number): string {\n const s = str(v, field, max);\n if (!DIGITS(max).test(s)) bad(field, \"expected base units (digits only)\");\n return s;\n}\nfunction num(v: unknown, field: string, min = 0, max = Number.MAX_SAFE_INTEGER): number {\n if (typeof v !== \"number\" || !Number.isFinite(v) || v < min || v > max) bad(field, \"expected a finite number in range\");\n return v;\n}\nfunction bool(v: unknown, field: string): boolean | undefined {\n if (v === undefined) return undefined;\n if (typeof v !== \"boolean\") bad(field, \"expected a boolean\");\n return v;\n}\n\nfunction quoteParams(v: unknown, allowUnlisted = false): QuoteParams {\n const p = record(v, \"params\");\n const sourceDecimals = !allowUnlisted || p.sourceDecimals === undefined ? undefined : num(p.sourceDecimals, \"params.sourceDecimals\", 0, 77);\n if (sourceDecimals !== undefined && !Number.isInteger(sourceDecimals)) bad(\"params.sourceDecimals\", \"expected an integer\");\n const slippage = p.slippageBps === undefined ? undefined : num(p.slippageBps, \"params.slippageBps\", 0, 10_000);\n if (slippage !== undefined && !Number.isInteger(slippage)) bad(\"params.slippageBps\", \"expected an integer\");\n const refundAddress = optStr(p.refundAddress, \"params.refundAddress\", 120);\n const minAmountOut = p.minAmountOut === undefined ? undefined : digits(p.minAmountOut, \"params.minAmountOut\", 60);\n const dry = bool(p.dry, \"params.dry\");\n const flex = bool(p.flex, \"params.flex\");\n return {\n sourceChain: str(p.sourceChain, \"params.sourceChain\", 64),\n sourceToken: str(p.sourceToken, \"params.sourceToken\", 64),\n ...(sourceDecimals !== undefined ? {sourceDecimals} : {}),\n destChain: str(p.destChain, \"params.destChain\", 64),\n destToken: str(p.destToken, \"params.destToken\", 120),\n amountIn: digits(p.amountIn, \"params.amountIn\", 40),\n destAddress: str(p.destAddress, \"params.destAddress\", 120),\n ...(refundAddress !== undefined ? {refundAddress} : {}),\n ...(minAmountOut !== undefined ? {minAmountOut} : {}),\n ...(slippage !== undefined ? {slippageBps: slippage} : {}),\n ...(dry !== undefined ? {dry} : {}),\n ...(flex !== undefined ? {flex} : {}),\n };\n}\n\nconst FEE_TYPES = new Set([\"network\", \"protocol\", \"affiliate\", \"other\"]);\n\n/** Keeps only the known Quote fields, each checked: what is sealed is exactly what is verified. */\nfunction normalizeQuote(v: unknown, intentMax: number): Quote {\n const q = record(v, \"quote\");\n const out: Quote = {\n provider: str(q.provider, \"quote.provider\", 40),\n intentId: str(q.intentId, \"quote.intentId\", intentMax),\n depositAddress: str(q.depositAddress, \"quote.depositAddress\", 200),\n depositMemo: q.depositMemo === null ? null : str(q.depositMemo, \"quote.depositMemo\", 400),\n sourceAmount: digits(q.sourceAmount, \"quote.sourceAmount\", 60),\n destAmount: digits(q.destAmount, \"quote.destAmount\", 60),\n minDestAmount: digits(q.minDestAmount, \"quote.minDestAmount\", 60),\n feeUsd: num(q.feeUsd, \"quote.feeUsd\"),\n etaSeconds: num(q.etaSeconds, \"quote.etaSeconds\"),\n reliability: num(q.reliability, \"quote.reliability\", 0, 1),\n validUntilMs: num(q.validUntilMs, \"quote.validUntilMs\"),\n };\n if (q.txRequest !== undefined) out.txRequest = txRequest(q.txRequest);\n if (q.fees !== undefined) {\n if (!Array.isArray(q.fees) || q.fees.length > 32) bad(\"quote.fees\", \"expected up to 32 entries\");\n out.fees = q.fees.map((f, i) => {\n const fee = record(f, `quote.fees[${i}]`);\n const type = str(fee.type, `quote.fees[${i}].type`, 20);\n if (!FEE_TYPES.has(type)) bad(`quote.fees[${i}].type`, \"unknown fee type\");\n const description = optStr(fee.description, `quote.fees[${i}].description`, 500);\n const included = bool(fee.included, `quote.fees[${i}].included`);\n if (included === undefined) bad(`quote.fees[${i}].included`, \"expected a boolean\");\n return {\n type: type as \"network\" | \"protocol\" | \"affiliate\" | \"other\",\n amount: digits(fee.amount, `quote.fees[${i}].amount`, 80),\n token: str(fee.token, `quote.fees[${i}].token`, 200),\n included,\n ...(description !== undefined ? {description} : {}),\n };\n });\n }\n if (q.providerData !== undefined) {\n const data = record(q.providerData, \"quote.providerData\");\n const keys = Object.keys(data);\n if (keys.length > 64) bad(\"quote.providerData\", \"more than 64 entries\");\n out.providerData = Object.fromEntries(keys.map((k) => [str(k, \"quote.providerData key\", 200), str(data[k], `quote.providerData.${k.slice(0, 40)}`, 10_000)]));\n }\n return out;\n}\n\nfunction txRequest(v: unknown): TxRequest {\n const t = record(v, \"quote.txRequest\");\n const out: TxRequest = {};\n if (t.to !== undefined) out.to = str(t.to, \"quote.txRequest.to\", 200);\n if (t.data !== undefined) out.data = str(t.data, \"quote.txRequest.data\", 1_000_000);\n if (t.value !== undefined) out.value = digits(t.value, \"quote.txRequest.value\", 80);\n if (t.chainId !== undefined) {\n const id = num(t.chainId, \"quote.txRequest.chainId\", 1);\n if (!Number.isInteger(id)) bad(\"quote.txRequest.chainId\", \"expected an integer\");\n out.chainId = id;\n }\n if (t.gasLimit !== undefined) out.gasLimit = digits(t.gasLimit, \"quote.txRequest.gasLimit\", 80);\n if (t.approvalAddress !== undefined) out.approvalAddress = str(t.approvalAddress, \"quote.txRequest.approvalAddress\", 200);\n if (t.serializedTx !== undefined) out.serializedTx = str(t.serializedTx, \"quote.txRequest.serializedTx\", 2_000_000);\n if (t.nativeFee !== undefined) out.nativeFee = digits(t.nativeFee, \"quote.txRequest.nativeFee\", 80);\n return out;\n}\n\n/** The transaction the client built from `build` (bigints arrive as decimal strings). */\nfunction unsignedTx(v: unknown): UnsignedTx {\n const t = record(v, \"tx\");\n const out: UnsignedTx = {chain: str(t.chain, \"tx.chain\", 64), to: str(t.to, \"tx.to\", 200), value: digits(t.value, \"tx.value\", 80)};\n const optional: [keyof UnsignedTx, number][] = [[\"data\", 1_000_000], [\"gasLimit\", 80], [\"approvalAddress\", 200], [\"serializedTx\", 2_000_000], [\"memo\", 2_000], [\"bodyBoc\", 200_000], [\"stateInitBoc\", 200_000], [\"typedData\", 200_000]];\n for (const [k, max] of optional) if (t[k] !== undefined) (out as unknown as Record<string, unknown>)[k] = k === \"gasLimit\" ? digits(t[k], `tx.${k}`, max) : str(t[k], `tx.${k}`, max);\n if (t.memoEncoding !== undefined) {\n if (t.memoEncoding !== \"utf8\" && t.memoEncoding !== \"hex\") bad(\"tx.memoEncoding\", \"expected utf8 or hex\");\n out.memoEncoding = t.memoEncoding;\n }\n if (t.chainId !== undefined) out.chainId = num(t.chainId, \"tx.chainId\", 1);\n return out;\n}\n\nfunction intentRef(v: unknown): IntentRef {\n const r = record(v, \"ref\");\n const nullable = (x: unknown, f: string) => (x === null || x === undefined ? undefined : str(x, f, 200));\n const sourceTxHash = nullable(r.sourceTxHash, \"ref.sourceTxHash\");\n const destTxHash = nullable(r.destTxHash, \"ref.destTxHash\");\n return {\n intentId: str(r.intentId, \"ref.intentId\", WIRE_INTENT_MAX),\n ...(sourceTxHash !== undefined ? {sourceTxHash} : {}),\n ...(destTxHash !== undefined ? {destTxHash} : {}),\n };\n}\n\nfunction asset(v: unknown, field: string): Asset {\n const a = record(v, field);\n return {chain: str(a.chain, `${field}.chain`, 64), token: str(a.token, `${field}.token`, 120)};\n}\n\n// ---- seal ------------------------------------------------------------------------------------\n\nfunction withoutSeal(quote: Quote): Quote {\n const {providerData, ...rest} = quote;\n if (!providerData) return rest;\n const {[SEAL_FIELD]: _seal, ...data} = providerData;\n return Object.keys(data).length ? {...rest, providerData: data} : rest;\n}\n/** The provider gets its quote back exactly as it produced it. */\nconst stripSeal = withoutSeal;\n\n/** Deterministic JSON: sorted keys, `undefined` dropped, non-finite numbers refused. */\nfunction canonical(value: unknown): string {\n if (value === null || typeof value === \"string\" || typeof value === \"boolean\") return JSON.stringify(value);\n if (typeof value === \"number\") {\n if (!Number.isFinite(value)) throw new Swap2HandlerError(400, \"quote contains a non-finite number\");\n return JSON.stringify(value);\n }\n if (Array.isArray(value)) return `[${value.map(canonical).join(\",\")}]`;\n if (typeof value === \"object\") {\n const r = value as Record<string, unknown>;\n return `{${Object.keys(r).filter((k) => r[k] !== undefined).sort().map((k) => `${JSON.stringify(k)}:${canonical(r[k])}`).join(\",\")}}`;\n }\n throw new Swap2HandlerError(400, \"quote is not JSON-safe\");\n}\n\nfunction toHex(bytes: Uint8Array): string {\n return Array.from(bytes, (b) => b.toString(16).padStart(2, \"0\")).join(\"\");\n}\nfunction fromHex(hex: string): Uint8Array {\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n","import {resolveAsset, type CcAsset} from \"@anywalletkit/swap2-assets\";\nimport type {QuoteParams} from \"./types.js\";\n/** Shared CAIP-2 helpers. Contribution packages never read deployment env. */\nexport function namespace(chain: string): string {\n return chain.split(\":\")[0] ?? \"\";\n}\n/** True for an `eip155:` chain id. */\nexport function isEvm(chain: string): boolean {\n return namespace(chain) === \"eip155\";\n}\n/** Numeric chain id of an `eip155:<id>` CAIP-2 id. */\nexport function evmRef(chain: string): number {\n return Number(chain.split(\":\")[1] ?? \"0\");\n}\n\n/**\n * `AbortSignal.timeout`, portably: Hermes (React Native) ships AbortController\n * but not the static helpers, and the unguarded call threw\n * \"AbortSignal.timeout is not a function\" on every remote execution — which\n * the auction then read as \"no route\". Falls back to a manual controller.\n */\nexport function timeoutSignal(ms: number): AbortSignal {\n const native = (AbortSignal as unknown as {timeout?: (ms: number) => AbortSignal}).timeout;\n if (typeof native === \"function\") return native.call(AbortSignal, ms);\n const ctrl = new AbortController();\n setTimeout(() => ctrl.abort(), ms);\n return ctrl.signal;\n}\n\n/** `AbortSignal.any`, portably (see timeoutSignal). */\nexport function anySignal(signals: readonly AbortSignal[]): AbortSignal {\n const native = (AbortSignal as unknown as {any?: (s: readonly AbortSignal[]) => AbortSignal}).any;\n if (typeof native === \"function\") return native.call(AbortSignal, signals);\n const ctrl = new AbortController();\n for (const s of signals) {\n if (s.aborted) {\n ctrl.abort();\n break;\n }\n s.addEventListener(\"abort\", () => ctrl.abort(), {once: true});\n }\n return ctrl.signal;\n}\n\n/** Longest URL scheme taken out (`https`, `wss`, `git+ssh`…): a letter and up to 31 of `[A-Za-z0-9+.-]`. */\nconst SCHEME_MAX = 32;\nconst isLetter = (c: number): boolean => (c >= 97 && c <= 122) || (c >= 65 && c <= 90);\nconst isSchemeChar = (c: number): boolean => isLetter(c) || (c >= 48 && c <= 57) || c === 43 || c === 45 || c === 46;\n/** What ends a URL in text: `\\s` as regexes define it, and `\"` `'` `<` `>`. */\nconst endsUrl = (c: number): boolean =>\n c === 34 || c === 39 || c === 60 || c === 62 || (c >= 9 && c <= 13) || c === 32 || c === 0xa0 || c === 0x1680 ||\n (c >= 0x2000 && c <= 0x200a) || c === 0x2028 || c === 0x2029 || c === 0x202f || c === 0x205f || c === 0x3000 || c === 0xfeff;\n\n/**\n * Remove URLs from text that may travel to a client (a refusal reason, a build error):\n * transport errors carry the endpoint — and with a keyed RPC (QuickNode, Alchemy,\n * Infura…) the key sits in its path (scan 2026-10-23 F8). What `/[a-z][a-z0-9+.-]{0,31}:\\/\\/[^\\s\"'<>]+/gi`\n * would replace, found from each `://` instead of tried at every position: linear in the length of\n * `text` whatever an API or a client puts in it (\"a.a.a…\" made the regex this replaced quadratic;\n * scan 2026-10-27 modules, F34). A URL glued to a word loses its last 32 scheme characters and the rest.\n */\nexport function redactUrls(text: string): string {\n let out = \"\";\n let done = 0; // text[0, done) is in `out`\n for (let at = text.indexOf(\"://\"); at !== -1; at = text.indexOf(\"://\", at + 3)) {\n let start = at;\n while (start > done && at - start < SCHEME_MAX && isSchemeChar(text.charCodeAt(start - 1))) start--;\n while (start < at && !isLetter(text.charCodeAt(start))) start++;\n if (start === at) continue; // no scheme: not a URL (and its tail is not scanned, so \"://://…\" stays linear)\n let end = at + 3;\n while (end < text.length && !endsUrl(text.charCodeAt(end))) end++;\n if (end === at + 3) continue; // nothing after \"://\"\n out += `${text.slice(done, start)}<url>`;\n done = end;\n at = end - 3;\n }\n return done === 0 ? text : out + text.slice(done);\n}\n\n/**\n * The source asset a provider is asked to sell: the catalog's entry for `params.sourceToken`, or —\n * only when the engine sent `sourceDecimals` (an unlisted token the user added) — an unlisted asset\n * named by its contract / mint. Providers that can trade any token use this instead of the catalog\n * lookup; the others keep refusing what the catalog does not list.\n */\nexport function sourceAssetOf(params: Pick<QuoteParams, \"sourceChain\" | \"sourceToken\" | \"sourceDecimals\">): CcAsset | undefined {\n const decimals = params.sourceDecimals;\n return resolveAsset(params.sourceChain, params.sourceToken, decimals === undefined ? {} : {allowUnlisted: true, decimals});\n}\n\nconst B64 = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n/** Standard or URL-safe base64 → bytes, without Buffer or atob (Hermes); null for anything else. */\nexport function base64Bytes(text: string): Uint8Array | null {\n const s = text.replace(/-/g, \"+\").replace(/_/g, \"/\").replace(/=+$/, \"\");\n if (!/^[A-Za-z0-9+/]*$/.test(s) || s.length % 4 === 1) return null;\n const out = new Uint8Array(Math.floor((s.length * 3) / 4));\n let bits = 0, acc = 0, n = 0;\n for (const ch of s) {\n acc = (acc << 6) | B64.indexOf(ch);\n bits += 6;\n if (bits >= 8) { bits -= 8; out[n++] = (acc >> bits) & 0xff; }\n }\n return out.subarray(0, n);\n}\n\n/** CRC16-XMODEM, the checksum of a user-friendly TON address. */\nfunction crc16(bytes: Uint8Array): number {\n let crc = 0;\n for (const b of bytes) {\n crc ^= b << 8;\n for (let i = 0; i < 8; i++) crc = crc & 0x8000 ? ((crc << 1) ^ 0x1021) & 0xffff : (crc << 1) & 0xffff;\n }\n return crc;\n}\n\n/**\n * A TON address in raw form (`<workchain>:<64 hex>`, lowercase) from a raw string, a user-friendly one (bounceable or\n * not, checksum verified), or an address object (`toRawString()` / `workChain` + `hash`); null for anything else. Two\n * spellings of one address compare equal in this form.\n */\nexport function tonRawAddress(address: unknown): string | null {\n if (address && typeof address === \"object\") {\n const o = address as {toRawString?: () => string; workChain?: unknown; hash?: unknown};\n if (typeof o.toRawString === \"function\") { try { return tonRawAddress(o.toRawString()); } catch { return null; } }\n if (typeof o.workChain === \"number\" && o.hash instanceof Uint8Array && o.hash.length === 32) {\n return `${o.workChain}:${Array.from(o.hash, (b) => b.toString(16).padStart(2, \"0\")).join(\"\")}`;\n }\n return null;\n }\n if (typeof address !== \"string\") return null;\n const raw = /^(-?[0-9]{1,3}):([0-9a-fA-F]{64})$/.exec(address.trim());\n if (raw) return `${Number(raw[1])}:${raw[2]!.toLowerCase()}`;\n if (!/^[A-Za-z0-9+/_-]{48}$/.test(address.trim())) return null;\n const bytes = base64Bytes(address.trim());\n if (!bytes || bytes.length !== 36 || crc16(bytes.subarray(0, 34)) !== ((bytes[34]! << 8) | bytes[35]!)) return null;\n const wc = bytes[1] === 0xff ? -1 : bytes[1]!;\n return `${wc}:${Array.from(bytes.subarray(2, 34), (b) => b.toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n","import type {CrossChainProvider, Quote, QuoteParams} from \"./types.js\";\n\n/** Shared quote validator used by both the aggregate and single-provider paths. */\nexport function validQuote(q: Quote, params: QuoteParams, provider: string): boolean {\n try {\n if (q.provider !== provider || q.sourceAmount !== params.amountIn) return false;\n if (typeof q.intentId !== \"string\" || !q.intentId || typeof q.depositAddress !== \"string\" || (q.depositMemo !== null && typeof q.depositMemo !== \"string\")) return false;\n if (![q.sourceAmount, q.destAmount, q.minDestAmount].every((value) => /^[0-9]+$/.test(value))) return false;\n const source = BigInt(q.sourceAmount);\n const dest = BigInt(q.destAmount);\n const min = BigInt(q.minDestAmount);\n if (source <= 0n || dest <= 0n || min <= 0n || min > dest) return false;\n if (params.minAmountOut !== undefined) {\n if (!/^[0-9]+$/.test(params.minAmountOut) || BigInt(params.minAmountOut) <= 0n || min < BigInt(params.minAmountOut)) return false;\n }\n if (params.slippageBps !== undefined) {\n if (!Number.isInteger(params.slippageBps) || params.slippageBps < 0 || params.slippageBps >= 10_000) return false;\n // Providers round the floor DOWN (1Click, LI.FI: floor(dest × (1 − s))).\n // A ceil here refused nearly every slippage-bounded quote by one base unit.\n const required = (dest * BigInt(10_000 - params.slippageBps)) / 10_000n;\n if (min < required) return false;\n }\n if (!Number.isFinite(q.etaSeconds) || q.etaSeconds < 0) return false;\n if (!Number.isFinite(q.reliability) || q.reliability < 0 || q.reliability > 1) return false;\n if (!Number.isFinite(q.feeUsd) || q.feeUsd < 0) return false;\n if (q.fees && (!Array.isArray(q.fees) || !q.fees.every((fee) =>\n [\"network\", \"protocol\", \"affiliate\", \"other\"].includes(fee.type) &&\n /^[0-9]+$/.test(fee.amount) &&\n typeof fee.token === \"string\" && fee.token.length > 0 &&\n typeof fee.included === \"boolean\" &&\n (fee.description === undefined || typeof fee.description === \"string\")\n ))) return false;\n if (q.providerData && (Array.isArray(q.providerData) || typeof q.providerData !== \"object\" || !Object.values(q.providerData).every((value) => typeof value === \"string\"))) return false;\n const tx = q.txRequest;\n if (tx) {\n if (tx.value !== undefined && !/^[0-9]+$/.test(tx.value)) return false;\n if (tx.gasLimit !== undefined && !/^[0-9]+$/.test(tx.gasLimit)) return false;\n if (tx.chainId !== undefined && (!Number.isSafeInteger(tx.chainId) || tx.chainId <= 0)) return false;\n if ([tx.to, tx.data, tx.approvalAddress, tx.serializedTx].some((value) => value !== undefined && typeof value !== \"string\")) return false;\n }\n // Execution is always re-quoted. An unknown/zero expiry cannot prove that\n // the transaction is still safe to sign, so contribution packages reject it.\n if (!Number.isFinite(q.validUntilMs) || q.validUntilMs <= Date.now()) return false;\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Why a remote provider's quote cannot execute in this app, or null: its EVM transaction calls or\n * approves an address the app did not pin (`remoteProvider` `callTargets` / `spenders`), or it pays\n * an address the backend named — a deposit address or a plain value send — on a stub the app did not\n * open to such routes (`depositAddressRoutes`, scan 2026-10-28 F9). The router and the\n * single-provider path drop such a quote with this reason, so the auction never picks a route\n * execution would refuse (scan 2026-10-27 modules, F8/F9/F16); execution checks the build again, in\n * full. Local providers are not judged here.\n */\nexport function remotePinRefusal(provider: Pick<CrossChainProvider, \"name\" | \"remote\" | \"callTargets\" | \"spenders\" | \"depositAddressRoutes\">, q: Quote, chain: string): string | null {\n if (provider.remote !== true) return null;\n const tx = q.txRequest;\n const plain = tx ? tx.data === undefined && tx.serializedTx === undefined : typeof q.depositAddress === \"string\" && q.depositAddress !== \"\";\n if (plain && provider.depositAddressRoutes !== true) {\n return `${provider.name}: the route pays ${tx?.to || q.depositAddress}, an address the backend named, and the app did not allow deposit-address routes for this provider (remoteProvider depositAddressRoutes)`;\n }\n if (!chain.startsWith(\"eip155:\")) return null;\n if (!tx || tx.serializedTx !== undefined) return null;\n const pinned = (list: readonly string[] | undefined, address: string) =>\n (list ?? []).some((a) => typeof a === \"string\" && a.toLowerCase() === address.toLowerCase());\n if (tx.approvalAddress && !pinned(provider.spenders?.(chain), tx.approvalAddress)) {\n return `${provider.name}: the route approves ${tx.approvalAddress}, which the app did not pin as a spender on ${chain} (remoteProvider spenders)`;\n }\n if (tx.data !== undefined && tx.to && !pinned(provider.callTargets?.(chain), tx.to)) {\n return `${provider.name}: the route calls ${tx.to}, which the app did not pin as a call target on ${chain} (remoteProvider callTargets)`;\n }\n return null;\n}\n"],"mappings":"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","names":[]}
|
package/dist/server.js
CHANGED
package/dist/swap2/base.cjs
CHANGED
|
@@ -769,12 +769,15 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
769
769
|
}
|
|
770
770
|
}
|
|
771
771
|
const executionModels = [unsigned.data !== void 0, unsigned.bodyBoc !== void 0, unsigned.typedData !== void 0].filter(Boolean).length;
|
|
772
|
-
if (executionModels > 1 || unsigned.memo !== void 0 && (unsigned.bodyBoc !== void 0 || unsigned.typedData !== void 0)) {
|
|
772
|
+
if (executionModels > 1 || unsigned.memo !== void 0 && (unsigned.bodyBoc !== void 0 || unsigned.typedData !== void 0) || unsigned.orderExtension !== void 0 && unsigned.typedData === void 0) {
|
|
773
773
|
throw new Error("swap2: provider returned ambiguous execution payloads");
|
|
774
774
|
}
|
|
775
775
|
if (unsigned.approvalAddress && !unsigned.data && !unsigned.typedData) {
|
|
776
776
|
throw new Error("swap2: approval is only valid for calldata or typed-order execution");
|
|
777
777
|
}
|
|
778
|
+
if (provider.remote === true && provider.depositAddressRoutes !== true && unsigned.data === void 0 && unsigned.bodyBoc === void 0 && unsigned.typedData === void 0 && !serialized) {
|
|
779
|
+
throw new Swap2ValidationError(`swap2: remote provider '${q.provider}' sends the input to ${unsigned.to}, an address its backend named, and the app did not allow deposit-address routes for it (remoteProvider depositAddressRoutes) \u2014 refused`);
|
|
780
|
+
}
|
|
778
781
|
const tronSource = this.fromChain.startsWith("tron:");
|
|
779
782
|
const nearSource = this.fromChain.startsWith("near:");
|
|
780
783
|
if (unsigned.memo && !this.fromChain.startsWith("ton:") && !tronSource && !nearSource) {
|
|
@@ -881,6 +884,9 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
881
884
|
throw new Swap2ValidationError(`swap2: remote provider '${q.provider}' asks to approve ${unsigned.approvalAddress} on ${this.fromChain}, which the app did not pin as a spender (remoteProvider spenders) \u2014 refused`);
|
|
882
885
|
}
|
|
883
886
|
if (unsigned.typedData !== void 0) {
|
|
887
|
+
if (remote && provider.verifiesBuilds !== true) {
|
|
888
|
+
throw new Swap2ValidationError(`swap2: remote provider '${q.provider}' asks to sign a typed order, which needs the app's own validator (remoteProvider verifyBuild) \u2014 refused`);
|
|
889
|
+
}
|
|
884
890
|
const chainId = this.fromChain.startsWith("eip155:") ? Number(this.fromChain.slice("eip155:".length)) : null;
|
|
885
891
|
assertTypedOrder(unsigned.typedData, unsigned.approvalAddress, { self: selfAddress, token: source.contract ?? void 0, amount: q.sourceAmount, chainId });
|
|
886
892
|
}
|