@anywalletkit/protocol-swap2-core 0.4.1 → 0.6.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 +2 -1
- package/dist/{chunk-Y3VUEWYI.js → chunk-IGTMUTNJ.js} +106 -28
- package/dist/chunk-IGTMUTNJ.js.map +1 -0
- package/dist/{chunk-GDRKGFLH.js → chunk-JFGMF6FO.js} +2 -2
- package/dist/{chunk-SKV5BVXK.js → chunk-MNJTMSFN.js} +8 -2
- package/dist/chunk-MNJTMSFN.js.map +1 -0
- package/dist/{chunk-4H2JVYXT.js → chunk-ZMWK3RQ4.js} +4 -3
- package/dist/chunk-ZMWK3RQ4.js.map +1 -0
- package/dist/index.cjs +119 -37
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +7 -5
- package/dist/remote.cjs +3 -1
- package/dist/remote.cjs.map +1 -1
- package/dist/remote.d.cts +6 -0
- package/dist/remote.d.ts +6 -0
- package/dist/remote.js +2 -2
- package/dist/server.cjs +8 -4
- package/dist/server.cjs.map +1 -1
- package/dist/server.d.cts +7 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.js +8 -5
- package/dist/server.js.map +1 -1
- package/dist/swap2/base.cjs +101 -26
- package/dist/swap2/base.cjs.map +1 -1
- package/dist/swap2/base.d.cts +21 -1
- package/dist/swap2/base.d.ts +21 -1
- package/dist/swap2/base.js +1 -1
- package/dist/swap2/single.cjs +101 -26
- package/dist/swap2/single.cjs.map +1 -1
- package/dist/swap2/single.d.cts +1 -0
- package/dist/swap2/single.d.ts +1 -0
- package/dist/swap2/single.js +2 -2
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +13 -0
- package/dist/types.d.ts +13 -0
- package/dist/util.cjs +6 -0
- package/dist/util.cjs.map +1 -1
- package/dist/util.d.cts +12 -1
- package/dist/util.d.ts +12 -1
- package/dist/util.js +3 -1
- package/package.json +7 -7
- package/dist/chunk-4H2JVYXT.js.map +0 -1
- package/dist/chunk-SKV5BVXK.js.map +0 -1
- package/dist/chunk-Y3VUEWYI.js.map +0 -1
- /package/dist/{chunk-GDRKGFLH.js.map → chunk-JFGMF6FO.js.map} +0 -0
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/server.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 /** 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);\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: redactUrls(String(refusal)).slice(0, 300)});\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>): Swap2Request {\n switch (action) {\n case \"quote\": return {params: quoteParams(b.params)};\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, redactUrls(err.message).slice(0, 300));\n }\n throw err;\n }\n}\n\nconst ok = (body: unknown): Swap2Response => ({status: 200, body: body === undefined ? null : body});\nconst fail = (status: number, error: string): Swap2Response => ({status, body: {error: redactUrls(error).slice(0, 300)}});\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): QuoteParams {\n const p = record(v, \"params\");\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 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"],"mappings":";;;;;;;;AA0FO,IAAM,oBAAN,cAAgC,MAAM;AAAA,EAClC;AAAA,EACT,YAAY,QAAgB,SAAiB;AAC3C,UAAM,OAAO;AACb,SAAK,OAAO;AACZ,SAAK,SAAS;AAAA,EAChB;AACF;AAEA,IAAM,UAA+B,oBAAI,IAAI,CAAC,SAAS,SAAS,aAAa,SAAS,SAAS,UAAU,YAAY,UAAU,CAAC;AAChI,IAAM,aAAa;AACnB,IAAM,cAAc;AACpB,IAAM,gBAAgB;AAEtB,IAAM,UAAU;AAChB,IAAM,aAAa;AACnB,IAAM,kBAAkB,aAAa;AAE9B,SAAS,mBAAgC,MAAqG;AACnJ,MAAI,OAAO,KAAK,UAAU,WAAY,OAAM,IAAI,MAAM,kEAAkE;AACxH,QAAM,SAAS,OAAO,KAAK,eAAe,WAAW,IAAI,YAAY,EAAE,OAAO,KAAK,UAAU,IAAI,KAAK;AACtG,MAAI,EAAE,kBAAkB,eAAe,OAAO,SAAS,GAAI,OAAM,IAAI,MAAM,oDAAoD;AAC/H,QAAM,SAAS,oBAAI,IAAgC;AACnD,aAAW,KAAK,KAAK,WAAW;AAC9B,QAAI,OAAO,IAAI,EAAE,IAAI,EAAG,OAAM,IAAI,MAAM,qCAAqC,EAAE,IAAI,GAAG;AACtF,WAAO,IAAI,EAAE,MAAM,CAAC;AAAA,EACtB;AACA,QAAM,MAAM,KAAK,OAAO,KAAK;AAC7B,QAAM,aAAa,OAAO,OAAO,UAAU,OAAO,QAAwB,EAAC,MAAM,QAAQ,MAAM,UAAS,GAAG,OAAO,CAAC,QAAQ,QAAQ,CAAC;AAGpI,QAAM,YAAY,CAAC,UAAkB,SAA6B,UAChE,IAAI,YAAY,EAAE,OAAO,cAAc,WAAW,OAAO,YAAY,SAAY,KAAK,KAAK,UAAU,OAAO,IAAI,OAAO,UAAU,YAAY,KAAK,CAAC,CAAC;AACtJ,iBAAe,KAAK,UAAkB,SAA6B,OAA8B;AAC/F,UAAM,MAAM,IAAI,WAAW,MAAM,OAAO,OAAO,KAAK,QAAQ,MAAM,YAAY,UAAU,UAAU,SAAS,KAAK,CAAC,CAAC;AAClH,WAAO,EAAC,GAAG,OAAO,cAAc,EAAC,GAAI,MAAM,gBAAgB,CAAC,GAAI,CAAC,UAAU,GAAG,MAAM,GAAG,EAAC,EAAC;AAAA,EAC3F;AACA,iBAAe,OAAO,UAAkB,SAA6B,OAAgC;AACnG,QAAI,MAAM,aAAa,SAAU,QAAO;AACxC,UAAM,UAAU,MAAM,eAAe,UAAU;AAC/C,QAAI,CAAC,WAAW,CAAC,iBAAiB,KAAK,OAAO,EAAG,QAAO;AACxD,WAAO,OAAO,OAAO,OAAO,QAAQ,MAAM,YAAY,QAAQ,OAAO,GAAmB,UAAU,UAAU,SAAS,KAAK,CAAC;AAAA,EAC7H;AAEA,iBAAe,UAAU,UAAkB,SAA6B,UAAmC;AACzG,UAAM,OAAO,IAAI,YAAY,EAAE,OAAO,KAAK,UAAU,CAAC,eAAe,UAAU,WAAW,MAAM,QAAQ,CAAC,CAAC;AAC1G,WAAO,MAAM,IAAI,WAAW,MAAM,OAAO,OAAO,KAAK,QAAQ,MAAM,YAAY,IAAI,CAAC,EAAE,SAAS,GAAG,EAAE,CAAC;AAAA,EACvG;AAEA,iBAAe,QAAQ,UAAkB,SAA6B,QAAgB,QAAsC;AAC1H,UAAM,IAAI,QAAQ,KAAK,MAAM;AAC7B,QAAI,CAAC,GAAG;AACN,UAAI,WAAW,WAAW,KAAK,8BAA8B,UAAa,IAAI,IAAI,KAAK,0BAA2B,QAAO;AACzH,YAAM,IAAI,kBAAkB,KAAK,kDAAkD;AAAA,IACrF;AACA,QAAI,CAAC,QAAQ,MAAM,UAAU,UAAU,SAAS,EAAE,CAAC,CAAE,GAAG,EAAE,CAAC,CAAE,GAAG;AAC9D,YAAM,IAAI,kBAAkB,KAAK,YAAY,SAAY,4CAA4C,uCAAuC;AAAA,IAC9I;AACA,WAAO,EAAE,CAAC;AAAA,EACZ;AAEA,QAAM,cAAc,CAAC,WAAyB,EAAC,GAAG,UAAU,KAAK,GAAG,UAAU,QAAQ,KAAK,MAAM,QAAQ,IAAI,CAAC,KAAK,MAAM,SAAQ;AAEjI,SAAO,eAAe,OAAO,QAAgB,MAAe,SAAoC;AAC9F,QAAI;AACF,UAAI,CAAC,QAAQ,IAAI,MAAM,EAAG,QAAO,KAAK,KAAK,mBAAmB,OAAO,MAAM,EAAE,MAAM,GAAG,EAAE,CAAC,GAAG;AAC5F,YAAM,IAAI,OAAO,MAAM,MAAM;AAC7B,YAAM,eAAe,IAAI,EAAE,UAAU,YAAY,EAAE;AACnD,YAAM,MAAM;AACZ,YAAM,UAAU,UAAU,KAAK,CAAC;AAChC,YAAM,MAAM,YAAY,WAAW,QAAQ,OAAO,QAAQ;AAE1D,YAAM,QAAsB,SAAS,UAAU,EAAC,KAAK,EAAC,GAAG,QAAQ,KAAK,UAAU,QAAQ,KAAK,QAAQ,IAAI,QAAQ,IAAI,CAAC,KAAK,QAAQ,IAAI,SAAQ,EAAC,IAC5I,WAAW,UAAU,EAAC,GAAG,SAAS,OAAO,YAAY,QAAQ,KAAK,EAAC,IACnE,cAAc,UAAU,EAAC,GAAG,SAAS,UAAU,QAAQ,KAAK,QAAQ,QAAQ,IAAI,CAAC,KAAK,QAAQ,SAAQ,IACtG;AACJ,YAAM,KAAK,MAAM,EAAC,QAAQ,KAAK,UAAU,cAAc,KAAK,SAAS,MAAK,GAAG,OAAO;AACpF,YAAM,UAAU,KAAK,UAAU,MAAM,KAAK,QAAQ,OAAO,IAAI;AAE7D,YAAM,WAAW,OAAO,IAAI,YAAY;AACxC,UAAI,CAAC,SAAU,QAAO,KAAK,KAAK,qBAAqB,YAAY,GAAG;AACpE,UAAI,CAAC,SAAS,QAAQ,EAAG,QAAO,KAAK,KAAK,aAAa,YAAY,eAAe;AAElF,cAAQ,KAAK;AAAA,QACX,KAAK,SAAS;AACZ,gBAAM,EAAC,OAAM,IAAI;AACjB,cAAI,UAAyB;AAC7B,gBAAM,QAAQ,MAAM,SAAS,SAAS,EAAC,GAAG,QAAQ,WAAW,CAAC,WAAW;AAAE,sBAAU;AAAA,UAAQ,EAAC,CAAC;AAC/F,cAAI,OAAO;AACT,gBAAI;AACJ,gBAAI;AAAE,2BAAa,eAAe,OAAO,UAAU;AAAA,YAAG,QAAQ;AAAE,qBAAO,KAAK,KAAK,GAAG,YAAY,+CAA+C;AAAA,YAAG;AAGlJ,gBAAI,CAAC,WAAW,YAAY,QAAQ,YAAY,KAAK,WAAW,gBAAgB,IAAI,GAAG;AACrF,qBAAO,KAAK,KAAK,GAAG,YAAY,gDAAgD;AAAA,YAClF;AACA,kBAAM,UAAU,EAAC,GAAG,YAAY,UAAU,GAAG,WAAW,QAAQ,IAAI,MAAM,UAAU,cAAc,SAAS,WAAW,QAAQ,CAAC,GAAE;AACjI,mBAAO,GAAG,MAAM,KAAK,cAAc,SAAS,OAAO,CAAC;AAAA,UACtD;AACA,cAAI,QAAS,QAAO,GAAG,EAAC,SAAS,WAAW,OAAO,OAAO,CAAC,EAAE,MAAM,GAAG,GAAG,EAAC,CAAC;AAC3E,iBAAO,GAAG,OAAO,QAAQ,QAAQ,EAAC,SAAS,GAAG,YAAY,mCAAkC,IAAI,IAAI;AAAA,QACtG;AAAA,QACA,KAAK;AAAA,QACL,KAAK,aAAa;AAChB,gBAAM,EAAC,OAAO,GAAE,IAAI;AACpB,cAAI,CAAE,MAAM,OAAO,cAAc,SAAS,KAAK,GAAI;AACjD,mBAAO,YAAY,SAAY,KAAK,KAAK,8BAA8B,IAAI,KAAK,KAAK,sCAAsC;AAAA,UAC7H;AACA,cAAI,MAAM,eAAe,IAAI,EAAG,QAAO,KAAK,KAAK,kCAAkC;AACnF,cAAI,QAAQ,QAAS,QAAO,GAAG,MAAM,QAAQ,MAAM,SAAS,aAAa,YAAY,KAAK,CAAC,CAAC,CAAC;AAC7F,cAAI,CAAC,SAAS,iBAAkB,QAAO,GAAG,CAAC,CAAC;AAC5C,gBAAM,QAAQ,MAAM,SAAS,iBAAkB,YAAY,KAAK,GAAG,EAAG,CAAC;AACvE,iBAAO,GAAG,CAAC,CAAC;AAAA,QACd;AAAA,QACA,KAAK;AAAA,QACL,KAAK,SAAS;AACZ,gBAAM,EAAC,IAAG,IAAI;AACd,gBAAM,MAAM,EAAC,GAAG,KAAK,UAAU,MAAM,QAAQ,cAAc,SAAS,IAAI,UAAU,GAAG,EAAC;AACtF,cAAI,QAAQ,QAAS,QAAO,GAAG,MAAM,SAAS,YAAY,GAAG,CAAC;AAC9D,cAAI,CAAC,SAAS,WAAY,QAAO,GAAG,CAAC,CAAC;AACtC,iBAAO,GAAG,MAAM,QAAQ,MAAM,SAAS,WAAY,GAAG,CAAC,CAAC;AAAA,QAC1D;AAAA,QACA,KAAK,UAAU;AACb,gBAAM,EAAC,UAAU,OAAM,IAAI;AAC3B,gBAAM,SAAS,gBAAgB,MAAM,QAAQ,cAAc,SAAS,UAAU,GAAG,GAAG,MAAM;AAC1F,iBAAO,GAAG,CAAC,CAAC;AAAA,QACd;AAAA,QACA,KAAK,YAAY;AACf,cAAI,CAAC,SAAS,oBAAqB,QAAO,KAAK,KAAK,aAAa,YAAY,4BAA4B;AACzG,gBAAM,EAAC,UAAU,UAAS,IAAI;AAC9B,gBAAM,SAAS,oBAAoB,MAAM,QAAQ,cAAc,SAAS,UAAU,GAAG,GAAG,SAAS;AACjG,iBAAO,GAAG,CAAC,CAAC;AAAA,QACd;AAAA,QACA,KAAK,YAAY;AACf,gBAAM,EAAC,MAAM,GAAE,IAAI;AACnB,iBAAO,GAAG,EAAC,WAAW,SAAS,cAAc,MAAM,EAAE,EAAC,CAAC;AAAA,QACzD;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,UAAI,eAAe,kBAAmB,QAAO,KAAK,IAAI,QAAQ,IAAI,OAAO;AACzE,aAAO,KAAK,KAAK,gBAAgB;AAAA,IACnC;AAAA,EACF;AACF;AAGA,SAAS,UAAU,QAAqB,GAA0C;AAChF,UAAQ,QAAQ;AAAA,IACd,KAAK;AAAS,aAAO,EAAC,QAAQ,YAAY,EAAE,MAAM,EAAC;AAAA,IACnD,KAAK;AAAS,aAAO,EAAC,OAAO,eAAe,EAAE,OAAO,eAAe,EAAC;AAAA,IACrE,KAAK;AAAa,aAAO,EAAC,OAAO,eAAe,EAAE,OAAO,eAAe,GAAG,IAAI,WAAW,EAAE,EAAE,EAAC;AAAA,IAC/F,KAAK;AAAA,IACL,KAAK;AAAS,aAAO,EAAC,KAAK,UAAU,EAAE,GAAG,EAAC;AAAA,IAC3C,KAAK;AAAU,aAAO,EAAC,UAAU,IAAI,EAAE,UAAU,YAAY,eAAe,GAAG,QAAQ,IAAI,EAAE,QAAQ,UAAU,GAAG,EAAC;AAAA,IACnH,KAAK,YAAY;AACf,YAAM,YAAY,IAAI,EAAE,WAAW,aAAa,GAAG;AACnD,UAAI,CAAC,yBAAyB,KAAK,SAAS,EAAG,OAAM,IAAI,kBAAkB,KAAK,gCAAgC;AAChH,aAAO,EAAC,UAAU,IAAI,EAAE,UAAU,YAAY,eAAe,GAAG,UAAS;AAAA,IAC3E;AAAA,IACA,KAAK;AAAY,aAAO,EAAC,MAAM,MAAM,EAAE,MAAM,MAAM,GAAG,IAAI,MAAM,EAAE,IAAI,IAAI,EAAC;AAAA,EAC7E;AACF;AAGA,SAAS,QAAQ,GAAW,GAAoB;AAC9C,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO;AAClC,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,IAAK,SAAQ,EAAE,WAAW,CAAC,IAAI,EAAE,WAAW,CAAC;AAC3E,SAAO,SAAS;AAClB;AAKA,eAAe,QAAW,IAAkC;AAC1D,MAAI;AACF,WAAO,MAAM,GAAG;AAAA,EAClB,SAAS,KAAK;AACZ,QAAI,eAAe,SAAS,oBAAoB,KAAK,IAAI,OAAO,GAAG;AACjE,YAAM,IAAI,kBAAkB,KAAK,WAAW,IAAI,OAAO,EAAE,MAAM,GAAG,GAAG,CAAC;AAAA,IACxE;AACA,UAAM;AAAA,EACR;AACF;AAEA,IAAM,KAAK,CAAC,UAAkC,EAAC,QAAQ,KAAK,MAAM,SAAS,SAAY,OAAO,KAAI;AAClG,IAAM,OAAO,CAAC,QAAgB,WAAkC,EAAC,QAAQ,MAAM,EAAC,OAAO,WAAW,KAAK,EAAE,MAAM,GAAG,GAAG,EAAC,EAAC;AAIvH,IAAM,SAAS,CAAC,QAAgB,IAAI,OAAO,UAAU,GAAG,IAAI;AAE5D,SAAS,IAAI,OAAe,KAAoB;AAC9C,QAAM,IAAI,kBAAkB,KAAK,GAAG,KAAK,KAAK,GAAG,EAAE;AACrD;AACA,SAAS,OAAO,GAAY,OAAwC;AAClE,MAAI,CAAC,KAAK,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,EAAG,KAAI,OAAO,oBAAoB;AACpF,SAAO;AACT;AACA,SAAS,IAAI,GAAY,OAAe,KAAqB;AAC3D,MAAI,OAAO,MAAM,SAAU,KAAI,OAAO,mBAAmB;AACzD,MAAI,EAAE,SAAS,IAAK,KAAI,OAAO,eAAe,GAAG,EAAE;AACnD,SAAO;AACT;AACA,SAAS,OAAO,GAAY,OAAe,KAAiC;AAC1E,SAAO,MAAM,SAAY,SAAY,IAAI,GAAG,OAAO,GAAG;AACxD;AACA,SAAS,OAAO,GAAY,OAAe,KAAqB;AAC9D,QAAM,IAAI,IAAI,GAAG,OAAO,GAAG;AAC3B,MAAI,CAAC,OAAO,GAAG,EAAE,KAAK,CAAC,EAAG,KAAI,OAAO,mCAAmC;AACxE,SAAO;AACT;AACA,SAAS,IAAI,GAAY,OAAe,MAAM,GAAG,MAAM,OAAO,kBAA0B;AACtF,MAAI,OAAO,MAAM,YAAY,CAAC,OAAO,SAAS,CAAC,KAAK,IAAI,OAAO,IAAI,IAAK,KAAI,OAAO,mCAAmC;AACtH,SAAO;AACT;AACA,SAAS,KAAK,GAAY,OAAoC;AAC5D,MAAI,MAAM,OAAW,QAAO;AAC5B,MAAI,OAAO,MAAM,UAAW,KAAI,OAAO,oBAAoB;AAC3D,SAAO;AACT;AAEA,SAAS,YAAY,GAAyB;AAC5C,QAAM,IAAI,OAAO,GAAG,QAAQ;AAC5B,QAAM,WAAW,EAAE,gBAAgB,SAAY,SAAY,IAAI,EAAE,aAAa,sBAAsB,GAAG,GAAM;AAC7G,MAAI,aAAa,UAAa,CAAC,OAAO,UAAU,QAAQ,EAAG,KAAI,sBAAsB,qBAAqB;AAC1G,QAAM,gBAAgB,OAAO,EAAE,eAAe,wBAAwB,GAAG;AACzE,QAAM,eAAe,EAAE,iBAAiB,SAAY,SAAY,OAAO,EAAE,cAAc,uBAAuB,EAAE;AAChH,QAAM,MAAM,KAAK,EAAE,KAAK,YAAY;AACpC,QAAM,OAAO,KAAK,EAAE,MAAM,aAAa;AACvC,SAAO;AAAA,IACL,aAAa,IAAI,EAAE,aAAa,sBAAsB,EAAE;AAAA,IACxD,aAAa,IAAI,EAAE,aAAa,sBAAsB,EAAE;AAAA,IACxD,WAAW,IAAI,EAAE,WAAW,oBAAoB,EAAE;AAAA,IAClD,WAAW,IAAI,EAAE,WAAW,oBAAoB,GAAG;AAAA,IACnD,UAAU,OAAO,EAAE,UAAU,mBAAmB,EAAE;AAAA,IAClD,aAAa,IAAI,EAAE,aAAa,sBAAsB,GAAG;AAAA,IACzD,GAAI,kBAAkB,SAAY,EAAC,cAAa,IAAI,CAAC;AAAA,IACrD,GAAI,iBAAiB,SAAY,EAAC,aAAY,IAAI,CAAC;AAAA,IACnD,GAAI,aAAa,SAAY,EAAC,aAAa,SAAQ,IAAI,CAAC;AAAA,IACxD,GAAI,QAAQ,SAAY,EAAC,IAAG,IAAI,CAAC;AAAA,IACjC,GAAI,SAAS,SAAY,EAAC,KAAI,IAAI,CAAC;AAAA,EACrC;AACF;AAEA,IAAM,YAAY,oBAAI,IAAI,CAAC,WAAW,YAAY,aAAa,OAAO,CAAC;AAGvE,SAAS,eAAe,GAAY,WAA0B;AAC5D,QAAM,IAAI,OAAO,GAAG,OAAO;AAC3B,QAAM,MAAa;AAAA,IACjB,UAAU,IAAI,EAAE,UAAU,kBAAkB,EAAE;AAAA,IAC9C,UAAU,IAAI,EAAE,UAAU,kBAAkB,SAAS;AAAA,IACrD,gBAAgB,IAAI,EAAE,gBAAgB,wBAAwB,GAAG;AAAA,IACjE,aAAa,EAAE,gBAAgB,OAAO,OAAO,IAAI,EAAE,aAAa,qBAAqB,GAAG;AAAA,IACxF,cAAc,OAAO,EAAE,cAAc,sBAAsB,EAAE;AAAA,IAC7D,YAAY,OAAO,EAAE,YAAY,oBAAoB,EAAE;AAAA,IACvD,eAAe,OAAO,EAAE,eAAe,uBAAuB,EAAE;AAAA,IAChE,QAAQ,IAAI,EAAE,QAAQ,cAAc;AAAA,IACpC,YAAY,IAAI,EAAE,YAAY,kBAAkB;AAAA,IAChD,aAAa,IAAI,EAAE,aAAa,qBAAqB,GAAG,CAAC;AAAA,IACzD,cAAc,IAAI,EAAE,cAAc,oBAAoB;AAAA,EACxD;AACA,MAAI,EAAE,cAAc,OAAW,KAAI,YAAY,UAAU,EAAE,SAAS;AACpE,MAAI,EAAE,SAAS,QAAW;AACxB,QAAI,CAAC,MAAM,QAAQ,EAAE,IAAI,KAAK,EAAE,KAAK,SAAS,GAAI,KAAI,cAAc,2BAA2B;AAC/F,QAAI,OAAO,EAAE,KAAK,IAAI,CAAC,GAAG,MAAM;AAC9B,YAAM,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG;AACxC,YAAM,OAAO,IAAI,IAAI,MAAM,cAAc,CAAC,UAAU,EAAE;AACtD,UAAI,CAAC,UAAU,IAAI,IAAI,EAAG,KAAI,cAAc,CAAC,UAAU,kBAAkB;AACzE,YAAM,cAAc,OAAO,IAAI,aAAa,cAAc,CAAC,iBAAiB,GAAG;AAC/E,YAAM,WAAW,KAAK,IAAI,UAAU,cAAc,CAAC,YAAY;AAC/D,UAAI,aAAa,OAAW,KAAI,cAAc,CAAC,cAAc,oBAAoB;AACjF,aAAO;AAAA,QACL;AAAA,QACA,QAAQ,OAAO,IAAI,QAAQ,cAAc,CAAC,YAAY,EAAE;AAAA,QACxD,OAAO,IAAI,IAAI,OAAO,cAAc,CAAC,WAAW,GAAG;AAAA,QACnD;AAAA,QACA,GAAI,gBAAgB,SAAY,EAAC,YAAW,IAAI,CAAC;AAAA,MACnD;AAAA,IACF,CAAC;AAAA,EACH;AACA,MAAI,EAAE,iBAAiB,QAAW;AAChC,UAAM,OAAO,OAAO,EAAE,cAAc,oBAAoB;AACxD,UAAM,OAAO,OAAO,KAAK,IAAI;AAC7B,QAAI,KAAK,SAAS,GAAI,KAAI,sBAAsB,sBAAsB;AACtE,QAAI,eAAe,OAAO,YAAY,KAAK,IAAI,CAAC,MAAM,CAAC,IAAI,GAAG,0BAA0B,GAAG,GAAG,IAAI,KAAK,CAAC,GAAG,sBAAsB,EAAE,MAAM,GAAG,EAAE,CAAC,IAAI,GAAM,CAAC,CAAC,CAAC;AAAA,EAC9J;AACA,SAAO;AACT;AAEA,SAAS,UAAU,GAAuB;AACxC,QAAM,IAAI,OAAO,GAAG,iBAAiB;AACrC,QAAM,MAAiB,CAAC;AACxB,MAAI,EAAE,OAAO,OAAW,KAAI,KAAK,IAAI,EAAE,IAAI,sBAAsB,GAAG;AACpE,MAAI,EAAE,SAAS,OAAW,KAAI,OAAO,IAAI,EAAE,MAAM,wBAAwB,GAAS;AAClF,MAAI,EAAE,UAAU,OAAW,KAAI,QAAQ,OAAO,EAAE,OAAO,yBAAyB,EAAE;AAClF,MAAI,EAAE,YAAY,QAAW;AAC3B,UAAM,KAAK,IAAI,EAAE,SAAS,2BAA2B,CAAC;AACtD,QAAI,CAAC,OAAO,UAAU,EAAE,EAAG,KAAI,2BAA2B,qBAAqB;AAC/E,QAAI,UAAU;AAAA,EAChB;AACA,MAAI,EAAE,aAAa,OAAW,KAAI,WAAW,OAAO,EAAE,UAAU,4BAA4B,EAAE;AAC9F,MAAI,EAAE,oBAAoB,OAAW,KAAI,kBAAkB,IAAI,EAAE,iBAAiB,mCAAmC,GAAG;AACxH,MAAI,EAAE,iBAAiB,OAAW,KAAI,eAAe,IAAI,EAAE,cAAc,gCAAgC,GAAS;AAClH,MAAI,EAAE,cAAc,OAAW,KAAI,YAAY,OAAO,EAAE,WAAW,6BAA6B,EAAE;AAClG,SAAO;AACT;AAGA,SAAS,WAAW,GAAwB;AAC1C,QAAM,IAAI,OAAO,GAAG,IAAI;AACxB,QAAM,MAAkB,EAAC,OAAO,IAAI,EAAE,OAAO,YAAY,EAAE,GAAG,IAAI,IAAI,EAAE,IAAI,SAAS,GAAG,GAAG,OAAO,OAAO,EAAE,OAAO,YAAY,EAAE,EAAC;AACjI,QAAM,WAAyC,CAAC,CAAC,QAAQ,GAAS,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,mBAAmB,GAAG,GAAG,CAAC,gBAAgB,GAAS,GAAG,CAAC,QAAQ,GAAK,GAAG,CAAC,WAAW,GAAO,GAAG,CAAC,gBAAgB,GAAO,GAAG,CAAC,aAAa,GAAO,CAAC;AACtO,aAAW,CAAC,GAAG,GAAG,KAAK,SAAU,KAAI,EAAE,CAAC,MAAM,OAAW,CAAC,IAA2C,CAAC,IAAI,MAAM,aAAa,OAAO,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,GAAG;AACpL,MAAI,EAAE,iBAAiB,QAAW;AAChC,QAAI,EAAE,iBAAiB,UAAU,EAAE,iBAAiB,MAAO,KAAI,mBAAmB,sBAAsB;AACxG,QAAI,eAAe,EAAE;AAAA,EACvB;AACA,MAAI,EAAE,YAAY,OAAW,KAAI,UAAU,IAAI,EAAE,SAAS,cAAc,CAAC;AACzE,SAAO;AACT;AAEA,SAAS,UAAU,GAAuB;AACxC,QAAM,IAAI,OAAO,GAAG,KAAK;AACzB,QAAM,WAAW,CAAC,GAAY,MAAe,MAAM,QAAQ,MAAM,SAAY,SAAY,IAAI,GAAG,GAAG,GAAG;AACtG,QAAM,eAAe,SAAS,EAAE,cAAc,kBAAkB;AAChE,QAAM,aAAa,SAAS,EAAE,YAAY,gBAAgB;AAC1D,SAAO;AAAA,IACL,UAAU,IAAI,EAAE,UAAU,gBAAgB,eAAe;AAAA,IACzD,GAAI,iBAAiB,SAAY,EAAC,aAAY,IAAI,CAAC;AAAA,IACnD,GAAI,eAAe,SAAY,EAAC,WAAU,IAAI,CAAC;AAAA,EACjD;AACF;AAEA,SAAS,MAAM,GAAY,OAAsB;AAC/C,QAAM,IAAI,OAAO,GAAG,KAAK;AACzB,SAAO,EAAC,OAAO,IAAI,EAAE,OAAO,GAAG,KAAK,UAAU,EAAE,GAAG,OAAO,IAAI,EAAE,OAAO,GAAG,KAAK,UAAU,GAAG,EAAC;AAC/F;AAIA,SAAS,YAAY,OAAqB;AACxC,QAAM,EAAC,cAAc,GAAG,KAAI,IAAI;AAChC,MAAI,CAAC,aAAc,QAAO;AAC1B,QAAM,EAAC,CAAC,UAAU,GAAG,OAAO,GAAG,KAAI,IAAI;AACvC,SAAO,OAAO,KAAK,IAAI,EAAE,SAAS,EAAC,GAAG,MAAM,cAAc,KAAI,IAAI;AACpE;AAEA,IAAM,YAAY;AAGlB,SAAS,UAAU,OAAwB;AACzC,MAAI,UAAU,QAAQ,OAAO,UAAU,YAAY,OAAO,UAAU,UAAW,QAAO,KAAK,UAAU,KAAK;AAC1G,MAAI,OAAO,UAAU,UAAU;AAC7B,QAAI,CAAC,OAAO,SAAS,KAAK,EAAG,OAAM,IAAI,kBAAkB,KAAK,oCAAoC;AAClG,WAAO,KAAK,UAAU,KAAK;AAAA,EAC7B;AACA,MAAI,MAAM,QAAQ,KAAK,EAAG,QAAO,IAAI,MAAM,IAAI,SAAS,EAAE,KAAK,GAAG,CAAC;AACnE,MAAI,OAAO,UAAU,UAAU;AAC7B,UAAM,IAAI;AACV,WAAO,IAAI,OAAO,KAAK,CAAC,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,MAAM,MAAS,EAAE,KAAK,EAAE,IAAI,CAAC,MAAM,GAAG,KAAK,UAAU,CAAC,CAAC,IAAI,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,KAAK,GAAG,CAAC;AAAA,EACpI;AACA,QAAM,IAAI,kBAAkB,KAAK,wBAAwB;AAC3D;AAEA,SAAS,MAAM,OAA2B;AACxC,SAAO,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAC1E;AACA,SAAS,QAAQ,KAAyB;AACxC,QAAM,MAAM,IAAI,WAAW,IAAI,SAAS,CAAC;AACzC,WAAS,IAAI,GAAG,IAAI,IAAI,QAAQ,IAAK,KAAI,CAAC,IAAI,SAAS,IAAI,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE;AACtF,SAAO;AACT;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/server.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: redactUrls(String(refusal)).slice(0, 300)});\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, redactUrls(err.message).slice(0, 300));\n }\n throw err;\n }\n}\n\nconst ok = (body: unknown): Swap2Response => ({status: 200, body: body === undefined ? null : body});\nconst fail = (status: number, error: string): Swap2Response => ({status, body: {error: redactUrls(error).slice(0, 300)}});\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"],"mappings":"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","names":[]}
|
package/dist/swap2/base.cjs
CHANGED
|
@@ -116,12 +116,14 @@ var inFlightKey = (fromChain, o) => JSON.stringify(
|
|
|
116
116
|
[fromChain, o.fromToken, o.toToken, o.toChain, o.recipient, o.refundAddress, o.fromTokenAmount, o.toTokenAmount, o.minAmountOut, o.slippage],
|
|
117
117
|
(_k, v) => typeof v === "bigint" ? v.toString() : v
|
|
118
118
|
);
|
|
119
|
+
var LOST_ANSWER = /socket hang up|ECONNRESET|ECONNABORTED|ETIMEDOUT|EPIPE|ENETUNREACH|EHOSTUNREACH|EAI_AGAIN|UND_ERR_[A-Z_]+|timed? ?out|TimeoutError|request aborted|AbortError|connection (?:closed|reset)|fetch failed|failed to fetch|network request failed|other side closed/i;
|
|
119
120
|
function maybeSent(error) {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
121
|
+
const chain = [];
|
|
122
|
+
for (let e = error, i = 0; e && typeof e === "object" && i < 6; e = e.cause, i++) chain.push(e);
|
|
123
|
+
for (const e of chain) if (e.maybeSent === true) return typeof e.hash === "string" && e.hash ? { hash: e.hash } : {};
|
|
124
|
+
if (chain.some((e) => e.answered === true)) return null;
|
|
125
|
+
const texts = chain.length ? chain.map((e) => [e.message, e.code, e.name].filter((x) => typeof x === "string").join(" ")) : [String(error)];
|
|
126
|
+
return texts.some((t) => LOST_ANSWER.test(t)) ? {} : null;
|
|
125
127
|
}
|
|
126
128
|
var NATIVE_DECIMALS = Object.freeze({
|
|
127
129
|
"eip155:5042": 18
|
|
@@ -205,22 +207,24 @@ function reviewedIdentity(plan) {
|
|
|
205
207
|
return JSON.stringify(pinned, (_k, v) => typeof v === "bigint" ? v.toString() : v);
|
|
206
208
|
}
|
|
207
209
|
var SWAP2_ID_PREFIX = "awk1";
|
|
208
|
-
function encodeId(provider, intentId, sourceTxHash) {
|
|
210
|
+
function encodeId(provider, intentId, sourceTxHash, destTxHash) {
|
|
209
211
|
const enc = (v) => encodeURIComponent(v).replace(/\./g, "%2E");
|
|
210
|
-
|
|
212
|
+
const parts = [provider, intentId, ...sourceTxHash != null ? [sourceTxHash, ...destTxHash != null ? [destTxHash] : []] : []];
|
|
213
|
+
return `${SWAP2_ID_PREFIX}.${parts.map(enc).join(".")}`;
|
|
211
214
|
}
|
|
212
215
|
function decodeId(id) {
|
|
213
216
|
try {
|
|
214
217
|
if (!id.startsWith(`${SWAP2_ID_PREFIX}.`)) throw new Error("unsupported id version");
|
|
215
218
|
const parts = id.slice(SWAP2_ID_PREFIX.length + 1).split(".");
|
|
216
|
-
if (parts.length < 2 || parts.length >
|
|
219
|
+
if (parts.length < 2 || parts.length > 4 || !parts[0] || !parts[1]) throw new Error("invalid id shape");
|
|
217
220
|
const provider = decodeURIComponent(parts[0]);
|
|
218
221
|
const intentId = decodeURIComponent(parts[1]);
|
|
219
222
|
const sourceTxHash = parts[2] ? decodeURIComponent(parts[2]) : void 0;
|
|
220
|
-
|
|
223
|
+
const destTxHash = parts[3] ? decodeURIComponent(parts[3]) : void 0;
|
|
224
|
+
if (!/^[A-Za-z0-9_-]+$/.test(provider) || !intentId || parts[2] !== void 0 && !sourceTxHash || parts[3] !== void 0 && !destTxHash) {
|
|
221
225
|
throw new Error("invalid id contents");
|
|
222
226
|
}
|
|
223
|
-
return { provider, intentId, ...sourceTxHash ? { sourceTxHash } : {} };
|
|
227
|
+
return { provider, intentId, ...sourceTxHash ? { sourceTxHash } : {}, ...destTxHash ? { destTxHash } : {} };
|
|
224
228
|
} catch {
|
|
225
229
|
throw new Swap2ValidationError("swap2: malformed swap2 status id");
|
|
226
230
|
}
|
|
@@ -272,16 +276,23 @@ function toCaip2(chain, fallback) {
|
|
|
272
276
|
}
|
|
273
277
|
return swap2ChainId(chain);
|
|
274
278
|
}
|
|
279
|
+
function nonEmptyRef(ref, side) {
|
|
280
|
+
if (typeof ref !== "string" || ref.trim() === "") throw new Swap2ValidationError(`swap2: ${side} token must not be empty (pass the symbol or the contract address)`);
|
|
281
|
+
return ref;
|
|
282
|
+
}
|
|
275
283
|
function toSymbol(caip2, ref) {
|
|
284
|
+
nonEmptyRef(ref, "source");
|
|
276
285
|
const byToken = ref.startsWith("0x") || ref.length > 20 ? (0, import_swap2_assets.ccAssetByToken)(caip2, ref) : void 0;
|
|
277
286
|
if (byToken) return byToken.symbol;
|
|
278
287
|
if ((0, import_swap2_assets.ccAsset)(caip2, ref)) return ref;
|
|
279
288
|
return ref;
|
|
280
289
|
}
|
|
281
|
-
function toDestAddress(caip2, ref) {
|
|
290
|
+
function toDestAddress(caip2, ref, allowUnlisted = false) {
|
|
291
|
+
nonEmptyRef(ref, "destination");
|
|
282
292
|
if (ref.startsWith("0x")) return ref;
|
|
283
293
|
const asset = (0, import_swap2_assets.ccAssetByToken)(caip2, ref) ?? (0, import_swap2_assets.ccAsset)(caip2, ref);
|
|
284
294
|
if (!asset) {
|
|
295
|
+
if (allowUnlisted && (0, import_swap2_assets.isTokenAddress)(caip2, ref)) return ref;
|
|
285
296
|
throw new Error(`swap2: unknown destination token '${ref}' on ${caip2}`);
|
|
286
297
|
}
|
|
287
298
|
return asset.contract ?? ZERO;
|
|
@@ -363,6 +374,15 @@ function mapStatus(s) {
|
|
|
363
374
|
const status = s.state === "completed" ? "completed" : s.state === "action-required" ? "action-required" : s.state === "refund-pending" ? "refund-pending" : s.state === "partial" ? "partial" : s.state === "failed" ? "failed" : s.state === "refunded" ? "refunded" : s.state === "cancelled" ? "cancelled" : s.state === "expired" ? "expired" : "pending";
|
|
364
375
|
return s.destTxHash ? { status, transactions: [{ hash: s.destTxHash, type: "destination" }] } : { status };
|
|
365
376
|
}
|
|
377
|
+
function blankToUndefined(v) {
|
|
378
|
+
return typeof v === "string" && v.trim() === "" ? void 0 : v;
|
|
379
|
+
}
|
|
380
|
+
function withoutBlankAddresses(o) {
|
|
381
|
+
const { recipient, refundAddress, ...rest } = o;
|
|
382
|
+
const r = blankToUndefined(recipient);
|
|
383
|
+
const f = blankToUndefined(refundAddress);
|
|
384
|
+
return { ...rest, ...r !== void 0 ? { recipient: r } : {}, ...f !== void 0 ? { refundAddress: f } : {} };
|
|
385
|
+
}
|
|
366
386
|
var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
367
387
|
fromChain;
|
|
368
388
|
constructor(account, config) {
|
|
@@ -395,19 +415,61 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
395
415
|
pairSupported(fromChain, toChain) {
|
|
396
416
|
return this.chainSupported(fromChain) && this.chainSupported(toChain);
|
|
397
417
|
}
|
|
418
|
+
unlistedAllowed() {
|
|
419
|
+
return this._config.allowUnlistedTokens === true;
|
|
420
|
+
}
|
|
421
|
+
/** The source asset the options name: the catalog's, or (allowUnlistedTokens) an unlisted one with its decimals. */
|
|
422
|
+
sourceAsset(options) {
|
|
423
|
+
const decimals = options.fromTokenDecimals;
|
|
424
|
+
return (0, import_swap2_assets.resolveAsset)(this.fromChain, toSymbol(this.fromChain, options.fromToken), {
|
|
425
|
+
allowUnlisted: this.unlistedAllowed(),
|
|
426
|
+
...decimals !== void 0 ? { decimals } : {}
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
unlistedDecimals = /* @__PURE__ */ new Map();
|
|
430
|
+
/**
|
|
431
|
+
* Options ready to quote: with `allowUnlistedTokens`, an unlisted source token gets its decimals,
|
|
432
|
+
* read from the chain through the account (cached per token) — a caller's `fromTokenDecimals` that
|
|
433
|
+
* disagrees is refused. Listed tokens and every option otherwise pass through unchanged.
|
|
434
|
+
*/
|
|
435
|
+
async prepareOptions(options) {
|
|
436
|
+
if (!this.unlistedAllowed()) return options;
|
|
437
|
+
const ref = toSymbol(this.fromChain, options.fromToken);
|
|
438
|
+
if ((0, import_swap2_assets.resolveAsset)(this.fromChain, ref) || !(0, import_swap2_assets.isTokenAddress)(this.fromChain, ref)) return options;
|
|
439
|
+
const key = ref.startsWith("0x") ? ref.toLowerCase() : ref;
|
|
440
|
+
let decimals = this.unlistedDecimals.get(key);
|
|
441
|
+
if (decimals === void 0) {
|
|
442
|
+
const reader = this._account;
|
|
443
|
+
if (typeof reader?.getTokenInfo !== "function") {
|
|
444
|
+
if (options.fromTokenDecimals === void 0) {
|
|
445
|
+
throw new Swap2ValidationError(`swap2: '${ref}' is not in the catalog \u2014 pass fromTokenDecimals, or use an account that can read token info`);
|
|
446
|
+
}
|
|
447
|
+
return options;
|
|
448
|
+
}
|
|
449
|
+
decimals = (await reader.getTokenInfo(ref)).decimals;
|
|
450
|
+
this.unlistedDecimals.set(key, decimals);
|
|
451
|
+
}
|
|
452
|
+
if (options.fromTokenDecimals !== void 0 && options.fromTokenDecimals !== decimals) {
|
|
453
|
+
throw new Swap2ValidationError(`swap2: fromTokenDecimals ${options.fromTokenDecimals} does not match the token's ${decimals}`);
|
|
454
|
+
}
|
|
455
|
+
return snapshotOptions({ ...options, fromTokenDecimals: decimals });
|
|
456
|
+
}
|
|
398
457
|
/** Swap2Options → the engine's QuoteParams (exact-in only). */
|
|
399
|
-
toParams(
|
|
458
|
+
toParams(given, dry) {
|
|
459
|
+
const options = withoutBlankAddresses(given);
|
|
400
460
|
if (options.fromTokenAmount == null) {
|
|
401
461
|
throw new Swap2UnsupportedOperationError("swap2: exact-out swap2 is not supported by the engine");
|
|
402
462
|
}
|
|
403
463
|
const destChain = toCaip2(options.toChain, this.fromChain);
|
|
404
464
|
const constraints = callerConstraints(options);
|
|
405
465
|
const amountIn = exactPositiveAmount(options.fromTokenAmount, "fromTokenAmount");
|
|
466
|
+
const source = this.sourceAsset(options);
|
|
406
467
|
const params = {
|
|
407
468
|
sourceChain: this.fromChain,
|
|
408
|
-
sourceToken: toSymbol(this.fromChain, options.fromToken),
|
|
469
|
+
sourceToken: (0, import_swap2_assets.isUnlisted)(source) ? source.contract : toSymbol(this.fromChain, options.fromToken),
|
|
470
|
+
...(0, import_swap2_assets.isUnlisted)(source) ? { sourceDecimals: source.decimals } : {},
|
|
409
471
|
destChain,
|
|
410
|
-
destToken: toDestAddress(destChain, options.toToken),
|
|
472
|
+
destToken: toDestAddress(destChain, options.toToken, this.unlistedAllowed()),
|
|
411
473
|
amountIn: amountIn.toString(),
|
|
412
474
|
destAddress: options.recipient ?? "",
|
|
413
475
|
dry,
|
|
@@ -463,7 +525,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
463
525
|
}
|
|
464
526
|
}
|
|
465
527
|
assertQuoteFeeCaps(q, options, config) {
|
|
466
|
-
const source =
|
|
528
|
+
const source = this.sourceAsset(options);
|
|
467
529
|
if (!source) throw new Error(`swap2: unknown source token '${options.fromToken}' on ${this.fromChain}`);
|
|
468
530
|
const token = source.contract ?? source.assetId;
|
|
469
531
|
const fees = q.fees?.map((fee) => ({ ...fee, amount: BigInt(fee.amount) }));
|
|
@@ -508,7 +570,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
508
570
|
* gave one.
|
|
509
571
|
*/
|
|
510
572
|
async quoteSwap2(options) {
|
|
511
|
-
const snapshot = snapshotOptions(options);
|
|
573
|
+
const snapshot = await this.prepareOptions(snapshotOptions(options));
|
|
512
574
|
const q = await this.resolveQuote(this.toParams(snapshot, true));
|
|
513
575
|
if (!q) {
|
|
514
576
|
const detail = this.noRouteDetail();
|
|
@@ -550,9 +612,9 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
550
612
|
this.assertFeeConfig(configSnapshot);
|
|
551
613
|
await this.assertAccountChain(signer);
|
|
552
614
|
const selfAddress = await signer.getAddress();
|
|
553
|
-
const recipient = snapshot.recipient ?? selfAddress;
|
|
554
|
-
const refundAddress = snapshot.refundAddress ?? selfAddress;
|
|
555
|
-
const executionOptions = snapshotOptions({ ...snapshot, recipient, refundAddress });
|
|
615
|
+
const recipient = blankToUndefined(snapshot.recipient) ?? selfAddress;
|
|
616
|
+
const refundAddress = blankToUndefined(snapshot.refundAddress) ?? selfAddress;
|
|
617
|
+
const executionOptions = await this.prepareOptions(snapshotOptions({ ...snapshot, recipient, refundAddress }));
|
|
556
618
|
const params = this.toParams(executionOptions, false);
|
|
557
619
|
const resolved = await this.resolveQuote(params);
|
|
558
620
|
if (!resolved) {
|
|
@@ -594,7 +656,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
594
656
|
throw new Error("swap2: provider build does not match the validated quote transaction");
|
|
595
657
|
}
|
|
596
658
|
} else if (!unsigned.bodyBoc && !unsigned.typedData) {
|
|
597
|
-
if (!q.depositAddress || unsigned.to !== q.depositAddress || (unsigned.memo ?? null) !== q.depositMemo) {
|
|
659
|
+
if (!q.depositAddress || unsigned.to !== q.depositAddress || (unsigned.memo ?? null) !== q.depositMemo || unsigned.data !== void 0 || unsigned.approvalAddress !== void 0 || unsigned.serializedTx !== void 0) {
|
|
598
660
|
throw new Error("swap2: provider build does not match the validated quote deposit");
|
|
599
661
|
}
|
|
600
662
|
}
|
|
@@ -633,7 +695,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
633
695
|
}
|
|
634
696
|
let tokenId;
|
|
635
697
|
if (!unsigned.data && !unsigned.bodyBoc && !unsigned.typedData && !serialized) {
|
|
636
|
-
const src =
|
|
698
|
+
const src = this.sourceAsset(executionOptions);
|
|
637
699
|
if (src?.contract) {
|
|
638
700
|
if (this.fromChain.startsWith("eip155:")) {
|
|
639
701
|
unsigned = {
|
|
@@ -659,7 +721,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
659
721
|
if (tokenId && (cellBody !== void 0 || unsigned.memo !== void 0 && !tronSource && !nearSource && !tonSource)) {
|
|
660
722
|
throw new Error("swap2: token deposit with memo/cell payload is not implemented");
|
|
661
723
|
}
|
|
662
|
-
const source =
|
|
724
|
+
const source = this.sourceAsset(executionOptions);
|
|
663
725
|
if (!source) throw new Error(`swap2: unknown source token '${executionOptions.fromToken}' on ${this.fromChain}`);
|
|
664
726
|
const executionValue = BigInt(unsigned.value);
|
|
665
727
|
if (serialized) {
|
|
@@ -949,10 +1011,10 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
949
1011
|
}
|
|
950
1012
|
/** Status of a swap by the id `swap2()` returned, asked from the provider that executed it. */
|
|
951
1013
|
async getSwap2Status(id, _options) {
|
|
952
|
-
const { provider: name, intentId, sourceTxHash } = decodeId(id);
|
|
1014
|
+
const { provider: name, intentId, sourceTxHash, destTxHash } = decodeId(id);
|
|
953
1015
|
const provider = this.providerFor(name);
|
|
954
1016
|
if (!provider) throw new Error(`swap2: no swap2 found for id '${id}'`);
|
|
955
|
-
return mapStatus(await provider.trackIntent(sourceTxHash
|
|
1017
|
+
return mapStatus(await provider.trackIntent({ intentId, ...sourceTxHash != null ? { sourceTxHash } : {}, ...destTxHash != null ? { destTxHash } : {} }));
|
|
956
1018
|
}
|
|
957
1019
|
/**
|
|
958
1020
|
* Two-phase routes (CCTP-style burn-and-mint): once `getSwap2Status` reports
|
|
@@ -998,6 +1060,17 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
998
1060
|
if (unsigned.chainId !== void 0 && String(unsigned.chainId) !== expectedId) {
|
|
999
1061
|
throw new Error(`swap2: claim chainId ${unsigned.chainId} does not match ${unsigned.chain}`);
|
|
1000
1062
|
}
|
|
1063
|
+
if (typeof provider.claimTargets !== "function") {
|
|
1064
|
+
throw new Swap2UnsupportedOperationError(`swap2: provider '${name}' declares no claimTargets \u2014 a claim may only call contracts the provider names for its chain`);
|
|
1065
|
+
}
|
|
1066
|
+
const targets = provider.claimTargets(unsigned.chain).map((a) => a.toLowerCase());
|
|
1067
|
+
if (!targets.includes(unsigned.to.toLowerCase())) {
|
|
1068
|
+
throw new Swap2ValidationError(`swap2: claim target ${unsigned.to} is not a contract '${name}' declares for ${unsigned.chain} \u2014 refused`);
|
|
1069
|
+
}
|
|
1070
|
+
const claimCall = (0, import_tx_preview_evm.decodeCall)({ to: unsigned.to, data: unsigned.data, value: 0n });
|
|
1071
|
+
if (claimCall.transfer || claimCall.approval) {
|
|
1072
|
+
throw new Swap2ValidationError(`swap2: claim calldata (${claimCall.method}) moves tokens or grants an allowance \u2014 refused`);
|
|
1073
|
+
}
|
|
1001
1074
|
const actual = await readAccountChain(signer);
|
|
1002
1075
|
if (actual == null) throw new Error(`swap2: destination account chain identity is unavailable for ${unsigned.chain}`);
|
|
1003
1076
|
const actualText = typeof actual === "string" ? actual : String(actual);
|
|
@@ -1013,7 +1086,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
1013
1086
|
});
|
|
1014
1087
|
if (!sent.hash) throw new Swap2ExecutionError("swap2: account did not return a claim transaction hash");
|
|
1015
1088
|
this.emitPhase("broadcast", { hash: sent.hash });
|
|
1016
|
-
return { hash: sent.hash, chain: unsigned.chain, id };
|
|
1089
|
+
return { hash: sent.hash, chain: unsigned.chain, id: encodeId(name, intentId, sourceTxHash, sent.hash) };
|
|
1017
1090
|
}
|
|
1018
1091
|
/** The inherited bridge() has no floor field: quote first, then bind execution
|
|
1019
1092
|
* to that quote's minimum so the inherited convenience API stays safe. */
|
|
@@ -1073,7 +1146,9 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
1073
1146
|
if ((wantFrom || wantTo) && c.caip2 !== wantFrom && c.caip2 !== wantTo) continue;
|
|
1074
1147
|
for (const a of c.assets) {
|
|
1075
1148
|
out.push({
|
|
1076
|
-
|
|
1149
|
+
// What quoteSwap2 takes back: the contract, or the symbol of the native coin — never the
|
|
1150
|
+
// provider-specific assetId, which is "" for most catalog entries (round 3, M25).
|
|
1151
|
+
token: a.contract ?? a.symbol,
|
|
1077
1152
|
chain: c.caip2,
|
|
1078
1153
|
symbol: a.symbol,
|
|
1079
1154
|
decimals: a.decimals,
|