@anywalletkit/protocol-swap2-core 0.3.2 → 0.4.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.
@@ -0,0 +1,100 @@
1
+ import { QuoteParams, Quote, UnsignedTx, IntentRef, Asset, CrossChainProvider } from './types.js';
2
+ import '@anywalletkit/core';
3
+
4
+ /**
5
+ * The server side of {@link remoteProvider}: runs real providers behind your backend so API
6
+ * keys, affiliate settings and non-CORS endpoints never ship in the app.
7
+ *
8
+ * Framework-free: wire `handle(action, body, context)` into any HTTP route that receives
9
+ * `POST {baseUrl}/{action}` with a JSON body, and send back `{status, body}` as JSON.
10
+ *
11
+ * What it guarantees:
12
+ * - every field of every request is validated (types, lengths, digit-only amounts) before a
13
+ * provider sees it; unknown actions, providers and malformed bodies are refused;
14
+ * - `quote` answers are sealed with HMAC-SHA256 under `sealSecret`; `build` and `preflight`
15
+ * refuse a quote that was not sealed here, was changed in transit, or has expired — so a
16
+ * client cannot make your server build a deposit for terms the provider never quoted;
17
+ * - every quote's `intentId` leaves with an intent token appended (`<id>~<tag>`): `track`,
18
+ * `claim`, `notify` and `register` refuse an id without a valid one (403), and the provider
19
+ * only ever sees its own id. With `subject`, quotes and tokens are bound to the caller, so
20
+ * another user's sealed quote or swap id is refused too; without it the token is a bearer
21
+ * capability for that one swap (share a transaction hash with support, not the swap id);
22
+ * - a provider's named refusal travels in-band (`{noQuote}`) so the app can show WHY;
23
+ * - error text crossing the boundary has URLs redacted (no upstream endpoints or keys).
24
+ *
25
+ * What it does NOT do, and you must: authenticate the caller and rate-limit per user/IP in
26
+ * `guard` (see {@link Swap2HandlerOptions.guard}), cap the raw request size before parsing
27
+ * (`serializedTx` may legitimately be ~2 MB), and log without request bodies.
28
+ *
29
+ * Server-only (WebCrypto HMAC; Node ≥ 20, Deno, Bun, edge runtimes). Never import it into an app.
30
+ */
31
+ /** One call, as `guard` sees it. `dry` quotes are display quotes: give them their own, larger bucket. */
32
+ interface Swap2Call {
33
+ action: Swap2Action;
34
+ provider: string;
35
+ dry: boolean;
36
+ /**
37
+ * The validated request as the provider will get it — amounts, recipients, the provider's own
38
+ * intent id (token removed) — for limits that depend on what is asked. Not yet checked against
39
+ * the seal or the intent token: those run after the guard.
40
+ */
41
+ request: Swap2Request;
42
+ }
43
+ /** The validated body of each action. */
44
+ type Swap2Request = {
45
+ params: QuoteParams;
46
+ } | {
47
+ quote: Quote;
48
+ tx?: UnsignedTx;
49
+ } | {
50
+ ref: IntentRef;
51
+ } | {
52
+ intentId: string;
53
+ txHash: string;
54
+ } | {
55
+ intentId: string;
56
+ signature: string;
57
+ } | {
58
+ from: Asset;
59
+ to: Asset;
60
+ };
61
+ type Swap2Action = "quote" | "build" | "preflight" | "track" | "claim" | "notify" | "register" | "supports";
62
+ interface Swap2HandlerOptions<C> {
63
+ /** The real providers, by `name` (the same names the app's `remoteProvider`s use). */
64
+ providers: readonly CrossChainProvider[];
65
+ /** HMAC key for quote seals: at least 32 bytes of randomness, kept server-side, rotated with care. */
66
+ sealSecret: string | Uint8Array;
67
+ /**
68
+ * Authentication and rate limiting — required, because this endpoint spends your provider
69
+ * quotas and builds deposits. Throw {@link Swap2HandlerError} (401 / 403 / 429) to refuse.
70
+ * `context` is whatever your route passes as the third argument (the request, a session…).
71
+ */
72
+ guard: (call: Swap2Call, context: C) => void | Promise<void>;
73
+ /**
74
+ * Who is calling: a stable id of the authenticated user — the Sign-In with Ethereum address,
75
+ * not an email — or undefined. Quotes are sealed for, and intent tokens issued to, this subject:
76
+ * another caller's quote or swap id is refused (403). Without it, the intent token in a swap
77
+ * id is a bearer capability. Runs after `guard`.
78
+ */
79
+ subject?: (context: C) => string | undefined | Promise<string | undefined>;
80
+ /**
81
+ * Moving to this handler from another backend: until this time (ms since epoch), `track`
82
+ * accepts a swap id without an intent token, so swaps started before the move can still be
83
+ * followed. Read-only: `claim`, `notify` and `register` always require the token.
84
+ */
85
+ acceptUntokenedTrackUntil?: number;
86
+ /** Clock for expiry checks (tests). */
87
+ now?: () => number;
88
+ }
89
+ interface Swap2Response {
90
+ status: number;
91
+ body: unknown;
92
+ }
93
+ /** Throw from `guard` (or anywhere) to answer with this status and message. */
94
+ declare class Swap2HandlerError extends Error {
95
+ readonly status: number;
96
+ constructor(status: number, message: string);
97
+ }
98
+ declare function createSwap2Handler<C = unknown>(opts: Swap2HandlerOptions<C>): (action: string, body: unknown, context: C) => Promise<Swap2Response>;
99
+
100
+ export { type Swap2Action, type Swap2Call, Swap2HandlerError, type Swap2HandlerOptions, type Swap2Request, type Swap2Response, createSwap2Handler };
package/dist/server.js ADDED
@@ -0,0 +1,346 @@
1
+ import {
2
+ redactUrls
3
+ } from "./chunk-SKV5BVXK.js";
4
+
5
+ // src/server.ts
6
+ var Swap2HandlerError = class extends Error {
7
+ status;
8
+ constructor(status, message) {
9
+ super(message);
10
+ this.name = "Swap2HandlerError";
11
+ this.status = status;
12
+ }
13
+ };
14
+ var ACTIONS = /* @__PURE__ */ new Set(["quote", "build", "preflight", "track", "claim", "notify", "register", "supports"]);
15
+ var SEAL_FIELD = "__remoteSeal";
16
+ var SEAL_PREFIX = "swap2-remote-quote:v1:";
17
+ var INTENT_PREFIX = "swap2-remote-intent:v1";
18
+ var TOKENED = /^([\s\S]+)~([0-9a-f]{32})$/;
19
+ var INTENT_MAX = 2e3;
20
+ var WIRE_INTENT_MAX = INTENT_MAX + 33;
21
+ function createSwap2Handler(opts) {
22
+ if (typeof opts.guard !== "function") throw new Error("swap2-server: guard is required (authentication + rate limiting)");
23
+ const secret = typeof opts.sealSecret === "string" ? new TextEncoder().encode(opts.sealSecret) : opts.sealSecret;
24
+ if (!(secret instanceof Uint8Array) || secret.length < 32) throw new Error("swap2-server: sealSecret must be at least 32 bytes");
25
+ const byName = /* @__PURE__ */ new Map();
26
+ for (const p of opts.providers) {
27
+ if (byName.has(p.name)) throw new Error(`swap2-server: duplicate provider '${p.name}'`);
28
+ byName.set(p.name, p);
29
+ }
30
+ const now = opts.now ?? Date.now;
31
+ const keyPromise = crypto.subtle.importKey("raw", secret, { name: "HMAC", hash: "SHA-256" }, false, ["sign", "verify"]);
32
+ const sealInput = (provider, subject, quote) => new TextEncoder().encode(SEAL_PREFIX + provider + ":" + (subject === void 0 ? "" : JSON.stringify(subject) + ":") + canonical(withoutSeal(quote)));
33
+ async function seal(provider, subject, quote) {
34
+ const tag = new Uint8Array(await crypto.subtle.sign("HMAC", await keyPromise, sealInput(provider, subject, quote)));
35
+ return { ...quote, providerData: { ...quote.providerData ?? {}, [SEAL_FIELD]: toHex(tag) } };
36
+ }
37
+ async function verify(provider, subject, quote) {
38
+ if (quote.provider !== provider) return false;
39
+ const sealHex = quote.providerData?.[SEAL_FIELD];
40
+ if (!sealHex || !/^[0-9a-f]{64}$/.test(sealHex)) return false;
41
+ return crypto.subtle.verify("HMAC", await keyPromise, fromHex(sealHex), sealInput(provider, subject, quote));
42
+ }
43
+ async function intentTag(provider, subject, intentId) {
44
+ const data = new TextEncoder().encode(JSON.stringify([INTENT_PREFIX, provider, subject ?? null, intentId]));
45
+ return toHex(new Uint8Array(await crypto.subtle.sign("HMAC", await keyPromise, data)).subarray(0, 16));
46
+ }
47
+ async function untoken(provider, subject, wireId, action) {
48
+ const m = TOKENED.exec(wireId);
49
+ if (!m) {
50
+ if (action === "track" && opts.acceptUntokenedTrackUntil !== void 0 && now() < opts.acceptUntokenedTrackUntil) return wireId;
51
+ throw new Swap2HandlerError(403, "swap id carries no intent token from this server");
52
+ }
53
+ if (!sameHex(await intentTag(provider, subject, m[1]), m[2])) {
54
+ throw new Swap2HandlerError(403, subject === void 0 ? "swap id carries an invalid intent token" : "swap id was not issued to this caller");
55
+ }
56
+ return m[1];
57
+ }
58
+ const forProvider = (quote) => ({ ...stripSeal(quote), intentId: TOKENED.exec(quote.intentId)?.[1] ?? quote.intentId });
59
+ return async function handle(action, body, context) {
60
+ try {
61
+ if (!ACTIONS.has(action)) return fail(404, `unknown action '${String(action).slice(0, 40)}'`);
62
+ const b = record(body, "body");
63
+ const providerName = str(b.provider, "provider", 40);
64
+ const act = action;
65
+ const request = validated(act, b);
66
+ const dry = "params" in request && request.params.dry === true;
67
+ const shown = "ref" in request ? { ref: { ...request.ref, intentId: TOKENED.exec(request.ref.intentId)?.[1] ?? request.ref.intentId } } : "quote" in request ? { ...request, quote: forProvider(request.quote) } : "intentId" in request ? { ...request, intentId: TOKENED.exec(request.intentId)?.[1] ?? request.intentId } : request;
68
+ await opts.guard({ action: act, provider: providerName, dry, request: shown }, context);
69
+ const subject = opts.subject ? await opts.subject(context) : void 0;
70
+ const provider = byName.get(providerName);
71
+ if (!provider) return fail(404, `unknown provider '${providerName}'`);
72
+ if (!provider.enabled()) return fail(403, `provider '${providerName}' is disabled`);
73
+ switch (act) {
74
+ case "quote": {
75
+ const { params } = request;
76
+ let refusal = null;
77
+ const quote = await provider.getQuote({ ...params, onRefusal: (reason) => {
78
+ refusal = reason;
79
+ } });
80
+ if (quote) {
81
+ let normalized;
82
+ try {
83
+ normalized = normalizeQuote(quote, INTENT_MAX);
84
+ } catch {
85
+ return fail(502, `${providerName} returned a quote this server refuses to seal`);
86
+ }
87
+ const tokened = { ...normalized, intentId: `${normalized.intentId}~${await intentTag(providerName, subject, normalized.intentId)}` };
88
+ return ok(await seal(providerName, subject, tokened));
89
+ }
90
+ if (refusal) return ok({ noQuote: redactUrls(String(refusal)).slice(0, 300) });
91
+ return ok(params.dry === false ? { noQuote: `${providerName} refused without naming a reason` } : null);
92
+ }
93
+ case "build":
94
+ case "preflight": {
95
+ const { quote, tx } = request;
96
+ if (!await verify(providerName, subject, quote)) {
97
+ return subject === void 0 ? fail(400, "quote integrity check failed") : fail(403, "quote was not sealed for this caller");
98
+ }
99
+ if (quote.validUntilMs < now()) return fail(410, "quote expired: ask for a new one");
100
+ if (act === "build") return ok(await refused(() => provider.buildDeposit(forProvider(quote))));
101
+ if (!provider.preflightDeposit) return ok({});
102
+ await refused(() => provider.preflightDeposit(forProvider(quote), tx));
103
+ return ok({});
104
+ }
105
+ case "track":
106
+ case "claim": {
107
+ const { ref } = request;
108
+ const own = { ...ref, intentId: await untoken(providerName, subject, ref.intentId, act) };
109
+ if (act === "track") return ok(await provider.trackIntent(own));
110
+ if (!provider.buildClaim) return ok({});
111
+ return ok(await refused(() => provider.buildClaim(own)));
112
+ }
113
+ case "notify": {
114
+ const { intentId, txHash } = request;
115
+ await provider.notifyDeposit?.(await untoken(providerName, subject, intentId, act), txHash);
116
+ return ok({});
117
+ }
118
+ case "register": {
119
+ if (!provider.registerSignedOrder) return fail(400, `provider '${providerName}' has no signed-order flow`);
120
+ const { intentId, signature } = request;
121
+ await provider.registerSignedOrder(await untoken(providerName, subject, intentId, act), signature);
122
+ return ok({});
123
+ }
124
+ case "supports": {
125
+ const { from, to } = request;
126
+ return ok({ supported: provider.supportsRoute(from, to) });
127
+ }
128
+ }
129
+ } catch (err) {
130
+ if (err instanceof Swap2HandlerError) return fail(err.status, err.message);
131
+ return fail(500, "internal error");
132
+ }
133
+ };
134
+ }
135
+ function validated(action, b) {
136
+ switch (action) {
137
+ case "quote":
138
+ return { params: quoteParams(b.params) };
139
+ case "build":
140
+ return { quote: normalizeQuote(b.quote, WIRE_INTENT_MAX) };
141
+ case "preflight":
142
+ return { quote: normalizeQuote(b.quote, WIRE_INTENT_MAX), tx: unsignedTx(b.tx) };
143
+ case "track":
144
+ case "claim":
145
+ return { ref: intentRef(b.ref) };
146
+ case "notify":
147
+ return { intentId: str(b.intentId, "intentId", WIRE_INTENT_MAX), txHash: str(b.txHash, "txHash", 200) };
148
+ case "register": {
149
+ const signature = str(b.signature, "signature", 302);
150
+ if (!/^0x[0-9a-fA-F]{2,300}$/.test(signature)) throw new Swap2HandlerError(400, "signature: not a hex signature");
151
+ return { intentId: str(b.intentId, "intentId", WIRE_INTENT_MAX), signature };
152
+ }
153
+ case "supports":
154
+ return { from: asset(b.from, "from"), to: asset(b.to, "to") };
155
+ }
156
+ }
157
+ function sameHex(a, b) {
158
+ if (a.length !== b.length) return false;
159
+ let diff = 0;
160
+ for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
161
+ return diff === 0;
162
+ }
163
+ async function refused(fn) {
164
+ try {
165
+ return await fn();
166
+ } catch (err) {
167
+ if (err instanceof Error && /^[a-z0-9_-]+: \S/i.test(err.message)) {
168
+ throw new Swap2HandlerError(422, redactUrls(err.message).slice(0, 300));
169
+ }
170
+ throw err;
171
+ }
172
+ }
173
+ var ok = (body) => ({ status: 200, body: body === void 0 ? null : body });
174
+ var fail = (status, error) => ({ status, body: { error: redactUrls(error).slice(0, 300) } });
175
+ var DIGITS = (max) => new RegExp(`^\\d{1,${max}}$`);
176
+ function bad(field, why) {
177
+ throw new Swap2HandlerError(400, `${field}: ${why}`);
178
+ }
179
+ function record(v, field) {
180
+ if (!v || typeof v !== "object" || Array.isArray(v)) bad(field, "expected an object");
181
+ return v;
182
+ }
183
+ function str(v, field, max) {
184
+ if (typeof v !== "string") bad(field, "expected a string");
185
+ if (v.length > max) bad(field, `longer than ${max}`);
186
+ return v;
187
+ }
188
+ function optStr(v, field, max) {
189
+ return v === void 0 ? void 0 : str(v, field, max);
190
+ }
191
+ function digits(v, field, max) {
192
+ const s = str(v, field, max);
193
+ if (!DIGITS(max).test(s)) bad(field, "expected base units (digits only)");
194
+ return s;
195
+ }
196
+ function num(v, field, min = 0, max = Number.MAX_SAFE_INTEGER) {
197
+ if (typeof v !== "number" || !Number.isFinite(v) || v < min || v > max) bad(field, "expected a finite number in range");
198
+ return v;
199
+ }
200
+ function bool(v, field) {
201
+ if (v === void 0) return void 0;
202
+ if (typeof v !== "boolean") bad(field, "expected a boolean");
203
+ return v;
204
+ }
205
+ function quoteParams(v) {
206
+ const p = record(v, "params");
207
+ const slippage = p.slippageBps === void 0 ? void 0 : num(p.slippageBps, "params.slippageBps", 0, 1e4);
208
+ if (slippage !== void 0 && !Number.isInteger(slippage)) bad("params.slippageBps", "expected an integer");
209
+ const refundAddress = optStr(p.refundAddress, "params.refundAddress", 120);
210
+ const minAmountOut = p.minAmountOut === void 0 ? void 0 : digits(p.minAmountOut, "params.minAmountOut", 60);
211
+ const dry = bool(p.dry, "params.dry");
212
+ const flex = bool(p.flex, "params.flex");
213
+ return {
214
+ sourceChain: str(p.sourceChain, "params.sourceChain", 64),
215
+ sourceToken: str(p.sourceToken, "params.sourceToken", 64),
216
+ destChain: str(p.destChain, "params.destChain", 64),
217
+ destToken: str(p.destToken, "params.destToken", 120),
218
+ amountIn: digits(p.amountIn, "params.amountIn", 40),
219
+ destAddress: str(p.destAddress, "params.destAddress", 120),
220
+ ...refundAddress !== void 0 ? { refundAddress } : {},
221
+ ...minAmountOut !== void 0 ? { minAmountOut } : {},
222
+ ...slippage !== void 0 ? { slippageBps: slippage } : {},
223
+ ...dry !== void 0 ? { dry } : {},
224
+ ...flex !== void 0 ? { flex } : {}
225
+ };
226
+ }
227
+ var FEE_TYPES = /* @__PURE__ */ new Set(["network", "protocol", "affiliate", "other"]);
228
+ function normalizeQuote(v, intentMax) {
229
+ const q = record(v, "quote");
230
+ const out = {
231
+ provider: str(q.provider, "quote.provider", 40),
232
+ intentId: str(q.intentId, "quote.intentId", intentMax),
233
+ depositAddress: str(q.depositAddress, "quote.depositAddress", 200),
234
+ depositMemo: q.depositMemo === null ? null : str(q.depositMemo, "quote.depositMemo", 400),
235
+ sourceAmount: digits(q.sourceAmount, "quote.sourceAmount", 60),
236
+ destAmount: digits(q.destAmount, "quote.destAmount", 60),
237
+ minDestAmount: digits(q.minDestAmount, "quote.minDestAmount", 60),
238
+ feeUsd: num(q.feeUsd, "quote.feeUsd"),
239
+ etaSeconds: num(q.etaSeconds, "quote.etaSeconds"),
240
+ reliability: num(q.reliability, "quote.reliability", 0, 1),
241
+ validUntilMs: num(q.validUntilMs, "quote.validUntilMs")
242
+ };
243
+ if (q.txRequest !== void 0) out.txRequest = txRequest(q.txRequest);
244
+ if (q.fees !== void 0) {
245
+ if (!Array.isArray(q.fees) || q.fees.length > 32) bad("quote.fees", "expected up to 32 entries");
246
+ out.fees = q.fees.map((f, i) => {
247
+ const fee = record(f, `quote.fees[${i}]`);
248
+ const type = str(fee.type, `quote.fees[${i}].type`, 20);
249
+ if (!FEE_TYPES.has(type)) bad(`quote.fees[${i}].type`, "unknown fee type");
250
+ const description = optStr(fee.description, `quote.fees[${i}].description`, 500);
251
+ const included = bool(fee.included, `quote.fees[${i}].included`);
252
+ if (included === void 0) bad(`quote.fees[${i}].included`, "expected a boolean");
253
+ return {
254
+ type,
255
+ amount: digits(fee.amount, `quote.fees[${i}].amount`, 80),
256
+ token: str(fee.token, `quote.fees[${i}].token`, 200),
257
+ included,
258
+ ...description !== void 0 ? { description } : {}
259
+ };
260
+ });
261
+ }
262
+ if (q.providerData !== void 0) {
263
+ const data = record(q.providerData, "quote.providerData");
264
+ const keys = Object.keys(data);
265
+ if (keys.length > 64) bad("quote.providerData", "more than 64 entries");
266
+ out.providerData = Object.fromEntries(keys.map((k) => [str(k, "quote.providerData key", 200), str(data[k], `quote.providerData.${k.slice(0, 40)}`, 1e4)]));
267
+ }
268
+ return out;
269
+ }
270
+ function txRequest(v) {
271
+ const t = record(v, "quote.txRequest");
272
+ const out = {};
273
+ if (t.to !== void 0) out.to = str(t.to, "quote.txRequest.to", 200);
274
+ if (t.data !== void 0) out.data = str(t.data, "quote.txRequest.data", 1e6);
275
+ if (t.value !== void 0) out.value = digits(t.value, "quote.txRequest.value", 80);
276
+ if (t.chainId !== void 0) {
277
+ const id = num(t.chainId, "quote.txRequest.chainId", 1);
278
+ if (!Number.isInteger(id)) bad("quote.txRequest.chainId", "expected an integer");
279
+ out.chainId = id;
280
+ }
281
+ if (t.gasLimit !== void 0) out.gasLimit = digits(t.gasLimit, "quote.txRequest.gasLimit", 80);
282
+ if (t.approvalAddress !== void 0) out.approvalAddress = str(t.approvalAddress, "quote.txRequest.approvalAddress", 200);
283
+ if (t.serializedTx !== void 0) out.serializedTx = str(t.serializedTx, "quote.txRequest.serializedTx", 2e6);
284
+ if (t.nativeFee !== void 0) out.nativeFee = digits(t.nativeFee, "quote.txRequest.nativeFee", 80);
285
+ return out;
286
+ }
287
+ function unsignedTx(v) {
288
+ const t = record(v, "tx");
289
+ const out = { chain: str(t.chain, "tx.chain", 64), to: str(t.to, "tx.to", 200), value: digits(t.value, "tx.value", 80) };
290
+ const optional = [["data", 1e6], ["gasLimit", 80], ["approvalAddress", 200], ["serializedTx", 2e6], ["memo", 2e3], ["bodyBoc", 2e5], ["stateInitBoc", 2e5], ["typedData", 2e5]];
291
+ for (const [k, max] of optional) if (t[k] !== void 0) out[k] = k === "gasLimit" ? digits(t[k], `tx.${k}`, max) : str(t[k], `tx.${k}`, max);
292
+ if (t.memoEncoding !== void 0) {
293
+ if (t.memoEncoding !== "utf8" && t.memoEncoding !== "hex") bad("tx.memoEncoding", "expected utf8 or hex");
294
+ out.memoEncoding = t.memoEncoding;
295
+ }
296
+ if (t.chainId !== void 0) out.chainId = num(t.chainId, "tx.chainId", 1);
297
+ return out;
298
+ }
299
+ function intentRef(v) {
300
+ const r = record(v, "ref");
301
+ const nullable = (x, f) => x === null || x === void 0 ? void 0 : str(x, f, 200);
302
+ const sourceTxHash = nullable(r.sourceTxHash, "ref.sourceTxHash");
303
+ const destTxHash = nullable(r.destTxHash, "ref.destTxHash");
304
+ return {
305
+ intentId: str(r.intentId, "ref.intentId", WIRE_INTENT_MAX),
306
+ ...sourceTxHash !== void 0 ? { sourceTxHash } : {},
307
+ ...destTxHash !== void 0 ? { destTxHash } : {}
308
+ };
309
+ }
310
+ function asset(v, field) {
311
+ const a = record(v, field);
312
+ return { chain: str(a.chain, `${field}.chain`, 64), token: str(a.token, `${field}.token`, 120) };
313
+ }
314
+ function withoutSeal(quote) {
315
+ const { providerData, ...rest } = quote;
316
+ if (!providerData) return rest;
317
+ const { [SEAL_FIELD]: _seal, ...data } = providerData;
318
+ return Object.keys(data).length ? { ...rest, providerData: data } : rest;
319
+ }
320
+ var stripSeal = withoutSeal;
321
+ function canonical(value) {
322
+ if (value === null || typeof value === "string" || typeof value === "boolean") return JSON.stringify(value);
323
+ if (typeof value === "number") {
324
+ if (!Number.isFinite(value)) throw new Swap2HandlerError(400, "quote contains a non-finite number");
325
+ return JSON.stringify(value);
326
+ }
327
+ if (Array.isArray(value)) return `[${value.map(canonical).join(",")}]`;
328
+ if (typeof value === "object") {
329
+ const r = value;
330
+ return `{${Object.keys(r).filter((k) => r[k] !== void 0).sort().map((k) => `${JSON.stringify(k)}:${canonical(r[k])}`).join(",")}}`;
331
+ }
332
+ throw new Swap2HandlerError(400, "quote is not JSON-safe");
333
+ }
334
+ function toHex(bytes) {
335
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
336
+ }
337
+ function fromHex(hex) {
338
+ const out = new Uint8Array(hex.length / 2);
339
+ for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
340
+ return out;
341
+ }
342
+ export {
343
+ Swap2HandlerError,
344
+ createSwap2Handler
345
+ };
346
+ //# sourceMappingURL=server.js.map
@@ -0,0 +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":[]}
@@ -188,14 +188,15 @@ function declaredNativeFee(q, chain) {
188
188
  if (fee > cap) throw new Error(`swap2: quoted native fee ${fee} exceeds the cap ${cap} for ${chain}`);
189
189
  return fee;
190
190
  }
191
+ var safeChainId = (v) => v >= 0n && v <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(v.toString()) : void 0;
191
192
  function normalizeTypedDomain(domain) {
192
193
  if (!domain || typeof domain !== "object") return domain;
193
194
  const raw = domain.chainId;
194
195
  let chainId;
195
196
  if (typeof raw === "number") chainId = raw;
196
- else if (typeof raw === "bigint") chainId = Number(raw);
197
+ else if (typeof raw === "bigint") chainId = safeChainId(raw);
197
198
  else if (typeof raw === "string" && /^\d+$/.test(raw)) chainId = Number(raw);
198
- else if (typeof raw === "string" && /^0x[0-9a-fA-F]+$/.test(raw)) chainId = Number(BigInt(raw));
199
+ else if (typeof raw === "string" && /^0x[0-9a-fA-F]+$/.test(raw)) chainId = safeChainId(BigInt(raw));
199
200
  return chainId === void 0 || !Number.isSafeInteger(chainId) ? domain : { ...domain, chainId };
200
201
  }
201
202
  var DEFAULT_SERIALIZED_NATIVE_ALLOWANCE = 20000000n;
@@ -341,7 +342,7 @@ function assertQuoteConstraints(q, options) {
341
342
  }
342
343
  if (options.slippage !== void 0) {
343
344
  const scale = 1000000000n;
344
- const permitted = BigInt(floorScaled(options.slippage, Number(scale)));
345
+ const permitted = BigInt(floorScaled(options.slippage, 1e9));
345
346
  const required = nominal * (scale - permitted) / scale;
346
347
  if (guaranteed < required) {
347
348
  throw new Error(`swap2: quote exceeds caller slippage (${options.slippage})`);