@anywalletkit/protocol-swap2-core 0.4.0 → 0.5.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-4H2JVYXT.js → chunk-3Y6HV2OL.js} +2 -2
- package/dist/{chunk-W2PHJINC.js → chunk-IC3MZIMB.js} +95 -19
- package/dist/chunk-IC3MZIMB.js.map +1 -0
- package/dist/{chunk-SKV5BVXK.js → chunk-MNJTMSFN.js} +8 -2
- package/dist/chunk-MNJTMSFN.js.map +1 -0
- package/dist/{chunk-5RR76RKH.js → chunk-OWO27YBK.js} +2 -2
- package/dist/index.cjs +106 -27
- 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 +1 -0
- package/dist/remote.cjs.map +1 -1
- package/dist/remote.js +2 -2
- package/dist/server.cjs +50 -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 +14 -5
- package/dist/server.js.map +1 -1
- package/dist/swap2/base.cjs +90 -17
- package/dist/swap2/base.cjs.map +1 -1
- package/dist/swap2/base.d.cts +31 -2
- package/dist/swap2/base.d.ts +31 -2
- package/dist/swap2/base.js +1 -1
- package/dist/swap2/single.cjs +90 -17
- 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 +6 -0
- package/dist/types.d.ts +6 -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 +6 -6
- package/dist/chunk-SKV5BVXK.js.map +0 -1
- package/dist/chunk-W2PHJINC.js.map +0 -1
- /package/dist/{chunk-4H2JVYXT.js.map → chunk-3Y6HV2OL.js.map} +0 -0
- /package/dist/{chunk-5RR76RKH.js.map → chunk-OWO27YBK.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\";\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 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":"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","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
|
@@ -200,6 +200,10 @@ function normalizeTypedDomain(domain) {
|
|
|
200
200
|
return chainId === void 0 || !Number.isSafeInteger(chainId) ? domain : { ...domain, chainId };
|
|
201
201
|
}
|
|
202
202
|
var DEFAULT_SERIALIZED_NATIVE_ALLOWANCE = 20000000n;
|
|
203
|
+
function reviewedIdentity(plan) {
|
|
204
|
+
const { feeUsd: _fee, etaSeconds: _eta, validUntilMs: _valid, approvals: _approvals, destAmount: _dest, requoted: _requoted, ...pinned } = plan;
|
|
205
|
+
return JSON.stringify(pinned, (_k, v) => typeof v === "bigint" ? v.toString() : v);
|
|
206
|
+
}
|
|
203
207
|
var SWAP2_ID_PREFIX = "awk1";
|
|
204
208
|
function encodeId(provider, intentId, sourceTxHash) {
|
|
205
209
|
const enc = (v) => encodeURIComponent(v).replace(/\./g, "%2E");
|
|
@@ -274,10 +278,11 @@ function toSymbol(caip2, ref) {
|
|
|
274
278
|
if ((0, import_swap2_assets.ccAsset)(caip2, ref)) return ref;
|
|
275
279
|
return ref;
|
|
276
280
|
}
|
|
277
|
-
function toDestAddress(caip2, ref) {
|
|
281
|
+
function toDestAddress(caip2, ref, allowUnlisted = false) {
|
|
278
282
|
if (ref.startsWith("0x")) return ref;
|
|
279
283
|
const asset = (0, import_swap2_assets.ccAssetByToken)(caip2, ref) ?? (0, import_swap2_assets.ccAsset)(caip2, ref);
|
|
280
284
|
if (!asset) {
|
|
285
|
+
if (allowUnlisted && (0, import_swap2_assets.isTokenAddress)(caip2, ref)) return ref;
|
|
281
286
|
throw new Error(`swap2: unknown destination token '${ref}' on ${caip2}`);
|
|
282
287
|
}
|
|
283
288
|
return asset.contract ?? ZERO;
|
|
@@ -359,6 +364,15 @@ function mapStatus(s) {
|
|
|
359
364
|
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";
|
|
360
365
|
return s.destTxHash ? { status, transactions: [{ hash: s.destTxHash, type: "destination" }] } : { status };
|
|
361
366
|
}
|
|
367
|
+
function blankToUndefined(v) {
|
|
368
|
+
return typeof v === "string" && v.trim() === "" ? void 0 : v;
|
|
369
|
+
}
|
|
370
|
+
function withoutBlankAddresses(o) {
|
|
371
|
+
const { recipient, refundAddress, ...rest } = o;
|
|
372
|
+
const r = blankToUndefined(recipient);
|
|
373
|
+
const f = blankToUndefined(refundAddress);
|
|
374
|
+
return { ...rest, ...r !== void 0 ? { recipient: r } : {}, ...f !== void 0 ? { refundAddress: f } : {} };
|
|
375
|
+
}
|
|
362
376
|
var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
363
377
|
fromChain;
|
|
364
378
|
constructor(account, config) {
|
|
@@ -391,19 +405,61 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
391
405
|
pairSupported(fromChain, toChain) {
|
|
392
406
|
return this.chainSupported(fromChain) && this.chainSupported(toChain);
|
|
393
407
|
}
|
|
408
|
+
unlistedAllowed() {
|
|
409
|
+
return this._config.allowUnlistedTokens === true;
|
|
410
|
+
}
|
|
411
|
+
/** The source asset the options name: the catalog's, or (allowUnlistedTokens) an unlisted one with its decimals. */
|
|
412
|
+
sourceAsset(options) {
|
|
413
|
+
const decimals = options.fromTokenDecimals;
|
|
414
|
+
return (0, import_swap2_assets.resolveAsset)(this.fromChain, toSymbol(this.fromChain, options.fromToken), {
|
|
415
|
+
allowUnlisted: this.unlistedAllowed(),
|
|
416
|
+
...decimals !== void 0 ? { decimals } : {}
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
unlistedDecimals = /* @__PURE__ */ new Map();
|
|
420
|
+
/**
|
|
421
|
+
* Options ready to quote: with `allowUnlistedTokens`, an unlisted source token gets its decimals,
|
|
422
|
+
* read from the chain through the account (cached per token) — a caller's `fromTokenDecimals` that
|
|
423
|
+
* disagrees is refused. Listed tokens and every option otherwise pass through unchanged.
|
|
424
|
+
*/
|
|
425
|
+
async prepareOptions(options) {
|
|
426
|
+
if (!this.unlistedAllowed()) return options;
|
|
427
|
+
const ref = toSymbol(this.fromChain, options.fromToken);
|
|
428
|
+
if ((0, import_swap2_assets.resolveAsset)(this.fromChain, ref) || !(0, import_swap2_assets.isTokenAddress)(this.fromChain, ref)) return options;
|
|
429
|
+
const key = ref.startsWith("0x") ? ref.toLowerCase() : ref;
|
|
430
|
+
let decimals = this.unlistedDecimals.get(key);
|
|
431
|
+
if (decimals === void 0) {
|
|
432
|
+
const reader = this._account;
|
|
433
|
+
if (typeof reader?.getTokenInfo !== "function") {
|
|
434
|
+
if (options.fromTokenDecimals === void 0) {
|
|
435
|
+
throw new Swap2ValidationError(`swap2: '${ref}' is not in the catalog \u2014 pass fromTokenDecimals, or use an account that can read token info`);
|
|
436
|
+
}
|
|
437
|
+
return options;
|
|
438
|
+
}
|
|
439
|
+
decimals = (await reader.getTokenInfo(ref)).decimals;
|
|
440
|
+
this.unlistedDecimals.set(key, decimals);
|
|
441
|
+
}
|
|
442
|
+
if (options.fromTokenDecimals !== void 0 && options.fromTokenDecimals !== decimals) {
|
|
443
|
+
throw new Swap2ValidationError(`swap2: fromTokenDecimals ${options.fromTokenDecimals} does not match the token's ${decimals}`);
|
|
444
|
+
}
|
|
445
|
+
return snapshotOptions({ ...options, fromTokenDecimals: decimals });
|
|
446
|
+
}
|
|
394
447
|
/** Swap2Options → the engine's QuoteParams (exact-in only). */
|
|
395
|
-
toParams(
|
|
448
|
+
toParams(given, dry) {
|
|
449
|
+
const options = withoutBlankAddresses(given);
|
|
396
450
|
if (options.fromTokenAmount == null) {
|
|
397
451
|
throw new Swap2UnsupportedOperationError("swap2: exact-out swap2 is not supported by the engine");
|
|
398
452
|
}
|
|
399
453
|
const destChain = toCaip2(options.toChain, this.fromChain);
|
|
400
454
|
const constraints = callerConstraints(options);
|
|
401
455
|
const amountIn = exactPositiveAmount(options.fromTokenAmount, "fromTokenAmount");
|
|
456
|
+
const source = this.sourceAsset(options);
|
|
402
457
|
const params = {
|
|
403
458
|
sourceChain: this.fromChain,
|
|
404
|
-
sourceToken: toSymbol(this.fromChain, options.fromToken),
|
|
459
|
+
sourceToken: (0, import_swap2_assets.isUnlisted)(source) ? source.contract : toSymbol(this.fromChain, options.fromToken),
|
|
460
|
+
...(0, import_swap2_assets.isUnlisted)(source) ? { sourceDecimals: source.decimals } : {},
|
|
405
461
|
destChain,
|
|
406
|
-
destToken: toDestAddress(destChain, options.toToken),
|
|
462
|
+
destToken: toDestAddress(destChain, options.toToken, this.unlistedAllowed()),
|
|
407
463
|
amountIn: amountIn.toString(),
|
|
408
464
|
destAddress: options.recipient ?? "",
|
|
409
465
|
dry,
|
|
@@ -459,7 +515,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
459
515
|
}
|
|
460
516
|
}
|
|
461
517
|
assertQuoteFeeCaps(q, options, config) {
|
|
462
|
-
const source =
|
|
518
|
+
const source = this.sourceAsset(options);
|
|
463
519
|
if (!source) throw new Error(`swap2: unknown source token '${options.fromToken}' on ${this.fromChain}`);
|
|
464
520
|
const token = source.contract ?? source.assetId;
|
|
465
521
|
const fees = q.fees?.map((fee) => ({ ...fee, amount: BigInt(fee.amount) }));
|
|
@@ -504,7 +560,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
504
560
|
* gave one.
|
|
505
561
|
*/
|
|
506
562
|
async quoteSwap2(options) {
|
|
507
|
-
const snapshot = snapshotOptions(options);
|
|
563
|
+
const snapshot = await this.prepareOptions(snapshotOptions(options));
|
|
508
564
|
const q = await this.resolveQuote(this.toParams(snapshot, true));
|
|
509
565
|
if (!q) {
|
|
510
566
|
const detail = this.noRouteDetail();
|
|
@@ -546,9 +602,9 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
546
602
|
this.assertFeeConfig(configSnapshot);
|
|
547
603
|
await this.assertAccountChain(signer);
|
|
548
604
|
const selfAddress = await signer.getAddress();
|
|
549
|
-
const recipient = snapshot.recipient ?? selfAddress;
|
|
550
|
-
const refundAddress = snapshot.refundAddress ?? selfAddress;
|
|
551
|
-
const executionOptions = snapshotOptions({ ...snapshot, recipient, refundAddress });
|
|
605
|
+
const recipient = blankToUndefined(snapshot.recipient) ?? selfAddress;
|
|
606
|
+
const refundAddress = blankToUndefined(snapshot.refundAddress) ?? selfAddress;
|
|
607
|
+
const executionOptions = await this.prepareOptions(snapshotOptions({ ...snapshot, recipient, refundAddress }));
|
|
552
608
|
const params = this.toParams(executionOptions, false);
|
|
553
609
|
const resolved = await this.resolveQuote(params);
|
|
554
610
|
if (!resolved) {
|
|
@@ -629,7 +685,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
629
685
|
}
|
|
630
686
|
let tokenId;
|
|
631
687
|
if (!unsigned.data && !unsigned.bodyBoc && !unsigned.typedData && !serialized) {
|
|
632
|
-
const src =
|
|
688
|
+
const src = this.sourceAsset(executionOptions);
|
|
633
689
|
if (src?.contract) {
|
|
634
690
|
if (this.fromChain.startsWith("eip155:")) {
|
|
635
691
|
unsigned = {
|
|
@@ -655,7 +711,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
655
711
|
if (tokenId && (cellBody !== void 0 || unsigned.memo !== void 0 && !tronSource && !nearSource && !tonSource)) {
|
|
656
712
|
throw new Error("swap2: token deposit with memo/cell payload is not implemented");
|
|
657
713
|
}
|
|
658
|
-
const source =
|
|
714
|
+
const source = this.sourceAsset(executionOptions);
|
|
659
715
|
if (!source) throw new Error(`swap2: unknown source token '${executionOptions.fromToken}' on ${this.fromChain}`);
|
|
660
716
|
const executionValue = BigInt(unsigned.value);
|
|
661
717
|
if (serialized) {
|
|
@@ -711,7 +767,8 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
711
767
|
approvalAmounts = current === approvalAmount ? [] : [...usdtMainnet && current !== 0n ? [0n] : [], approvalAmount];
|
|
712
768
|
}
|
|
713
769
|
const review = this._config.reviewExecution;
|
|
714
|
-
|
|
770
|
+
let reviewed;
|
|
771
|
+
if (review && (!carried || carried.reviewed)) {
|
|
715
772
|
const chainId = unsigned.chainId !== void 0 ? { chainId: unsigned.chainId } : {};
|
|
716
773
|
const approvals = approvalAmounts.map((amount) => ({ token: source.contract, spender: unsigned.approvalAddress, amount, ...chainId }));
|
|
717
774
|
const plan = {
|
|
@@ -739,12 +796,28 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
739
796
|
...cellBody !== void 0 ? { cellBody: true } : {}
|
|
740
797
|
}
|
|
741
798
|
};
|
|
742
|
-
|
|
743
|
-
if (
|
|
744
|
-
|
|
799
|
+
const previous = carried?.reviewed;
|
|
800
|
+
if (!previous || reviewedIdentity(previous) !== reviewedIdentity(plan) || plan.destAmount < previous.destAmount) {
|
|
801
|
+
try {
|
|
802
|
+
reviewed = deepFreeze(structuredCloneSafe(previous ? { ...plan, requoted: true } : plan));
|
|
803
|
+
await review(reviewed);
|
|
804
|
+
if (q.validUntilMs > 0 && q.validUntilMs <= Date.now()) {
|
|
805
|
+
throw new Swap2QuoteError(`swap2: quote expired during review \u2014 ${previous ? "the allowance was revoked" : "nothing was signed"}`);
|
|
806
|
+
}
|
|
807
|
+
} catch (error) {
|
|
808
|
+
if (previous) await revokeApproval?.().catch(() => {
|
|
809
|
+
});
|
|
810
|
+
throw error;
|
|
811
|
+
}
|
|
745
812
|
}
|
|
746
813
|
}
|
|
747
|
-
|
|
814
|
+
try {
|
|
815
|
+
await this.assertNativeGas(signer, unsigned, approvalAmounts, source.contract ?? void 0, !!unsigned.typedData || serialized || !!tokenId);
|
|
816
|
+
} catch (error) {
|
|
817
|
+
if (carried) await revokeApproval?.().catch(() => {
|
|
818
|
+
});
|
|
819
|
+
throw error;
|
|
820
|
+
}
|
|
748
821
|
if (approvalAmounts.length) {
|
|
749
822
|
const approve = async (amount) => {
|
|
750
823
|
const sent2 = await signer.sendTransaction({
|
|
@@ -773,7 +846,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
773
846
|
if (q.validUntilMs > 0 && q.validUntilMs <= Date.now()) {
|
|
774
847
|
if (!carried && approvalAmounts.length && revokeApproval) {
|
|
775
848
|
this.emitPhase("requoting");
|
|
776
|
-
const handoff = { token: source.contract, spender: unsigned.approvalAddress, amount: approvalAmount, revoke: revokeApproval, adopted: false };
|
|
849
|
+
const handoff = { token: source.contract, spender: unsigned.approvalAddress, amount: approvalAmount, revoke: revokeApproval, adopted: false, ...reviewed ? { reviewed } : {} };
|
|
777
850
|
try {
|
|
778
851
|
return await this.executeSwap2(signer, snapshot, config, handoff);
|
|
779
852
|
} catch (error) {
|