@anywalletkit/protocol-swap2-core 0.4.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/README.md +2 -1
  2. package/dist/{chunk-Y3VUEWYI.js → chunk-IGTMUTNJ.js} +106 -28
  3. package/dist/chunk-IGTMUTNJ.js.map +1 -0
  4. package/dist/{chunk-GDRKGFLH.js → chunk-JFGMF6FO.js} +2 -2
  5. package/dist/{chunk-SKV5BVXK.js → chunk-MNJTMSFN.js} +8 -2
  6. package/dist/chunk-MNJTMSFN.js.map +1 -0
  7. package/dist/{chunk-4H2JVYXT.js → chunk-ZMWK3RQ4.js} +4 -3
  8. package/dist/chunk-ZMWK3RQ4.js.map +1 -0
  9. package/dist/index.cjs +119 -37
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +2 -1
  12. package/dist/index.d.ts +2 -1
  13. package/dist/index.js +7 -5
  14. package/dist/remote.cjs +3 -1
  15. package/dist/remote.cjs.map +1 -1
  16. package/dist/remote.d.cts +6 -0
  17. package/dist/remote.d.ts +6 -0
  18. package/dist/remote.js +2 -2
  19. package/dist/server.cjs +8 -4
  20. package/dist/server.cjs.map +1 -1
  21. package/dist/server.d.cts +7 -0
  22. package/dist/server.d.ts +7 -0
  23. package/dist/server.js +8 -5
  24. package/dist/server.js.map +1 -1
  25. package/dist/swap2/base.cjs +101 -26
  26. package/dist/swap2/base.cjs.map +1 -1
  27. package/dist/swap2/base.d.cts +21 -1
  28. package/dist/swap2/base.d.ts +21 -1
  29. package/dist/swap2/base.js +1 -1
  30. package/dist/swap2/single.cjs +101 -26
  31. package/dist/swap2/single.cjs.map +1 -1
  32. package/dist/swap2/single.d.cts +1 -0
  33. package/dist/swap2/single.d.ts +1 -0
  34. package/dist/swap2/single.js +2 -2
  35. package/dist/types.cjs.map +1 -1
  36. package/dist/types.d.cts +13 -0
  37. package/dist/types.d.ts +13 -0
  38. package/dist/util.cjs +6 -0
  39. package/dist/util.cjs.map +1 -1
  40. package/dist/util.d.cts +12 -1
  41. package/dist/util.d.ts +12 -1
  42. package/dist/util.js +3 -1
  43. package/package.json +7 -7
  44. package/dist/chunk-4H2JVYXT.js.map +0 -1
  45. package/dist/chunk-SKV5BVXK.js.map +0 -1
  46. package/dist/chunk-Y3VUEWYI.js.map +0 -1
  47. /package/dist/{chunk-GDRKGFLH.js.map → chunk-JFGMF6FO.js.map} +0 -0
@@ -1,4 +1,5 @@
1
1
  // src/util.ts
2
+ import { resolveAsset } from "@anywalletkit/swap2-assets";
2
3
  function namespace(chain) {
3
4
  return chain.split(":")[0] ?? "";
4
5
  }
@@ -31,6 +32,10 @@ function anySignal(signals) {
31
32
  function redactUrls(text) {
32
33
  return text.replace(/\b[a-z][a-z0-9+.-]*:\/\/[^\s"'<>]+/gi, "<url>");
33
34
  }
35
+ function sourceAssetOf(params) {
36
+ const decimals = params.sourceDecimals;
37
+ return resolveAsset(params.sourceChain, params.sourceToken, decimals === void 0 ? {} : { allowUnlisted: true, decimals });
38
+ }
34
39
 
35
40
  export {
36
41
  namespace,
@@ -38,6 +43,7 @@ export {
38
43
  evmRef,
39
44
  timeoutSignal,
40
45
  anySignal,
41
- redactUrls
46
+ redactUrls,
47
+ sourceAssetOf
42
48
  };
43
- //# sourceMappingURL=chunk-SKV5BVXK.js.map
49
+ //# sourceMappingURL=chunk-MNJTMSFN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/util.ts"],"sourcesContent":["import {resolveAsset, type CcAsset} from \"@anywalletkit/swap2-assets\";\nimport type {QuoteParams} from \"./types.js\";\n/** Shared CAIP-2 helpers. Contribution packages never read deployment env. */\nexport function namespace(chain: string): string {\n return chain.split(\":\")[0] ?? \"\";\n}\n/** True for an `eip155:` chain id. */\nexport function isEvm(chain: string): boolean {\n return namespace(chain) === \"eip155\";\n}\n/** Numeric chain id of an `eip155:<id>` CAIP-2 id. */\nexport function evmRef(chain: string): number {\n return Number(chain.split(\":\")[1] ?? \"0\");\n}\n\n/**\n * `AbortSignal.timeout`, portably: Hermes (React Native) ships AbortController\n * but not the static helpers, and the unguarded call threw\n * \"AbortSignal.timeout is not a function\" on every remote execution — which\n * the auction then read as \"no route\". Falls back to a manual controller.\n */\nexport function timeoutSignal(ms: number): AbortSignal {\n const native = (AbortSignal as unknown as {timeout?: (ms: number) => AbortSignal}).timeout;\n if (typeof native === \"function\") return native.call(AbortSignal, ms);\n const ctrl = new AbortController();\n setTimeout(() => ctrl.abort(), ms);\n return ctrl.signal;\n}\n\n/** `AbortSignal.any`, portably (see timeoutSignal). */\nexport function anySignal(signals: readonly AbortSignal[]): AbortSignal {\n const native = (AbortSignal as unknown as {any?: (s: readonly AbortSignal[]) => AbortSignal}).any;\n if (typeof native === \"function\") return native.call(AbortSignal, signals);\n const ctrl = new AbortController();\n for (const s of signals) {\n if (s.aborted) {\n ctrl.abort();\n break;\n }\n s.addEventListener(\"abort\", () => ctrl.abort(), {once: true});\n }\n return ctrl.signal;\n}\n\n/**\n * Remove URLs from text that may travel to a client (a refusal reason, a build error):\n * transport errors carry the endpoint — and with a keyed RPC (QuickNode, Alchemy,\n * Infura…) the key sits in its path (scan 2026-10-23 F8).\n */\nexport function redactUrls(text: string): string {\n return text.replace(/\\b[a-z][a-z0-9+.-]*:\\/\\/[^\\s\"'<>]+/gi, \"<url>\");\n}\n\n/**\n * The source asset a provider is asked to sell: the catalog's entry for `params.sourceToken`, or —\n * only when the engine sent `sourceDecimals` (an unlisted token the user added) — an unlisted asset\n * named by its contract / mint. Providers that can trade any token use this instead of the catalog\n * lookup; the others keep refusing what the catalog does not list.\n */\nexport function sourceAssetOf(params: Pick<QuoteParams, \"sourceChain\" | \"sourceToken\" | \"sourceDecimals\">): CcAsset | undefined {\n const decimals = params.sourceDecimals;\n return resolveAsset(params.sourceChain, params.sourceToken, decimals === undefined ? {} : {allowUnlisted: true, decimals});\n}\n"],"mappings":";AAAA,SAAQ,oBAAiC;AAGlC,SAAS,UAAU,OAAuB;AAC/C,SAAO,MAAM,MAAM,GAAG,EAAE,CAAC,KAAK;AAChC;AAEO,SAAS,MAAM,OAAwB;AAC5C,SAAO,UAAU,KAAK,MAAM;AAC9B;AAEO,SAAS,OAAO,OAAuB;AAC5C,SAAO,OAAO,MAAM,MAAM,GAAG,EAAE,CAAC,KAAK,GAAG;AAC1C;AAQO,SAAS,cAAc,IAAyB;AACrD,QAAM,SAAU,YAAmE;AACnF,MAAI,OAAO,WAAW,WAAY,QAAO,OAAO,KAAK,aAAa,EAAE;AACpE,QAAM,OAAO,IAAI,gBAAgB;AACjC,aAAW,MAAM,KAAK,MAAM,GAAG,EAAE;AACjC,SAAO,KAAK;AACd;AAGO,SAAS,UAAU,SAA8C;AACtE,QAAM,SAAU,YAA8E;AAC9F,MAAI,OAAO,WAAW,WAAY,QAAO,OAAO,KAAK,aAAa,OAAO;AACzE,QAAM,OAAO,IAAI,gBAAgB;AACjC,aAAW,KAAK,SAAS;AACvB,QAAI,EAAE,SAAS;AACb,WAAK,MAAM;AACX;AAAA,IACF;AACA,MAAE,iBAAiB,SAAS,MAAM,KAAK,MAAM,GAAG,EAAC,MAAM,KAAI,CAAC;AAAA,EAC9D;AACA,SAAO,KAAK;AACd;AAOO,SAAS,WAAW,MAAsB;AAC/C,SAAO,KAAK,QAAQ,wCAAwC,OAAO;AACrE;AAQO,SAAS,cAAc,QAAkG;AAC9H,QAAM,WAAW,OAAO;AACxB,SAAO,aAAa,OAAO,aAAa,OAAO,aAAa,aAAa,SAAY,CAAC,IAAI,EAAC,eAAe,MAAM,SAAQ,CAAC;AAC3H;","names":[]}
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  anySignal,
3
3
  timeoutSignal
4
- } from "./chunk-SKV5BVXK.js";
4
+ } from "./chunk-MNJTMSFN.js";
5
5
 
6
6
  // src/remote.ts
7
7
  function remoteProvider(opts) {
@@ -72,11 +72,12 @@ function remoteProvider(opts) {
72
72
  },
73
73
  ...opts.supportsChain ? { supportsChain: opts.supportsChain.bind(opts) } : {},
74
74
  ...opts.supportsChainPair ? { supportsChainPair: opts.supportsChainPair.bind(opts) } : {},
75
- ...opts.supportsSameChain !== void 0 ? { supportsSameChain: opts.supportsSameChain } : {}
75
+ ...opts.supportsSameChain !== void 0 ? { supportsSameChain: opts.supportsSameChain } : {},
76
+ ...opts.claimTargets ? { claimTargets: opts.claimTargets } : {}
76
77
  };
77
78
  }
78
79
 
79
80
  export {
80
81
  remoteProvider
81
82
  };
82
- //# sourceMappingURL=chunk-4H2JVYXT.js.map
83
+ //# sourceMappingURL=chunk-ZMWK3RQ4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/remote.ts"],"sourcesContent":["import type {Asset, CrossChainProvider, IntentRef, IntentStatus, Quote, QuoteParams, UnsignedTx} from \"./types.js\";\nimport {anySignal, timeoutSignal} from \"./util.js\";\n\n/**\n * HTTP-proxied `CrossChainProvider`: forwards every provider call to a backend\n * that runs the real provider. This is how a BROWSER context (e.g. a wallet's\n * signing worker) executes the full swap2 layer — account binding,\n * approvals, cell-body guards, id/status lifecycle — while provider secrets\n * (API JWTs, affiliate config) and non-CORS REST endpoints stay server-side.\n *\n * Wire protocol: POST {baseUrl}/{action} with a JSON body, one action per\n * provider method:\n * quote {provider, params: QuoteParams} → Quote | null\n * build {provider, quote: Quote} → UnsignedTx\n * preflight {provider, quote: Quote, tx: UnsignedTx} → {}\n * track {provider, ref: IntentRef} → IntentStatus\n * notify {provider, intentId, txHash} → {}\n * register {provider, intentId, signature} → {}\n * claim {provider, ref: IntentRef} → UnsignedTx | null\n * supports {provider, from, to} → {supported: boolean}\n *\n * `supportsRoute`/`enabled` need a SYNC answer, so they are config: pass the\n * routes the backend advertises via `supports`, or leave the default\n * allow-and-let-quote-decide behavior (a null quote is a clean miss).\n */\nexport interface RemoteProviderOptions {\n /** Provider name the backend multiplexes on (e.g. \"near-intents\"). */\n name: string;\n /** Endpoint prefix, e.g. \"/api/v2/swap2\" (same-origin) or absolute URL. */\n baseUrl: string;\n /** Sync capability gate; defaults to true (the backend quote decides). */\n supportsRoute?: (from: Asset, to: Asset) => boolean;\n /** Optional synchronous dark flag for accurate client-side discovery. */\n enabled?: () => boolean;\n /** Optional chain-level discovery hooks mirrored from the backend. */\n supportsChain?: (caip2: string) => boolean;\n supportsChainPair?: (fromChain: string, toChain: string) => boolean;\n /** Explicit opt-in when the backend can execute same-chain swaps. */\n supportsSameChain?: boolean;\n /**\n * The contracts this provider's claims may call, per chain (e.g. `cctpClaimTargets` from\n * `@anywalletkit/protocol-swap2-cctp`). The app's list, not the server's: without it the engine\n * refuses claims from this stub (round 3, M24).\n */\n claimTargets?: (chain: string) => readonly string[];\n /** Defaults to globalThis.fetch — injectable for tests. */\n fetchImpl?: typeof fetch;\n /** Extra headers (e.g. auth) attached to every call. */\n headers?: Record<string, string>;\n /** Per-request fail-closed timeout. Defaults to 12 seconds. */\n timeoutMs?: number;\n}\n\n/**\n * A provider that runs on your server: quotes, deposits and tracking go to `baseUrl`, so API keys\n * and affiliate settings never ship in the app. The engine still validates every quote and\n * transaction locally before signing.\n */\nexport function remoteProvider(opts: RemoteProviderOptions): CrossChainProvider {\n const name = opts.name;\n if (!/^[A-Za-z0-9_-]+$/.test(name)) throw new Error(\"swap2-remote: invalid provider name\");\n const baseUrl = opts.baseUrl.replace(/\\/+$/, \"\");\n if (!baseUrl) throw new Error(\"swap2-remote: baseUrl is required\");\n const supportsRoute = opts.supportsRoute;\n const enabled = opts.enabled;\n const headers = Object.freeze({...opts.headers});\n const doFetch = opts.fetchImpl ?? fetch;\n const timeoutMs = opts.timeoutMs ?? 12_000;\n if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 60_000) {\n throw new Error(\"swap2-remote: timeoutMs must be an integer between 1 and 60000\");\n }\n\n async function call<T>(action: string, body: Record<string, unknown>, signal?: AbortSignal): Promise<T> {\n const timeout = timeoutSignal(timeoutMs);\n const requestSignal = signal ? anySignal([signal, timeout]) : timeout;\n const res = await doFetch(`${baseUrl}/${action}`, {\n method: \"POST\",\n headers: {...headers, \"content-type\": \"application/json\"},\n // UnsignedTx carries bigint fields (value, gasLimit) — JSON.stringify\n // throws on them (\"Do not know how to serialize a BigInt\", seen\n // 2026-10-10 on a Solana → Base preflight). Wire them as decimal strings.\n body: JSON.stringify({provider: name, ...body}, (_k, v) => (typeof v === \"bigint\" ? v.toString() : v)),\n signal: requestSignal,\n });\n if (!res.ok) {\n // Surface the backend's message — provider errors are actionable\n // (\"quote expired\", \"route unsupported\"), not just status codes.\n const text = await res.text().catch(() => \"\");\n throw new Error(`swap2-remote ${name}/${action}: ${res.status} ${text.slice(0, 300)}`);\n }\n return (await res.json()) as T;\n }\n\n return {\n name,\n // The browser side has no dark-flag env; the backend enforces its own\n // flags — a disabled provider simply quotes null / rejects build.\n enabled: () => enabled ? enabled() : true,\n supportsRoute(from: Asset, to: Asset): boolean {\n return supportsRoute ? supportsRoute(from, to) : true;\n },\n async getQuote(params: QuoteParams, signal?: AbortSignal): Promise<Quote | null> {\n const out = await call<Quote | {noQuote: string} | null>(\"quote\", {params}, signal);\n if (out && typeof out === \"object\" && \"noQuote\" in out) {\n // The backend forwarded the venue's own refusal — throw it so the\n // router records a REASON instead of an anonymous null.\n throw new Error(String((out as {noQuote: unknown}).noQuote).slice(0, 300));\n }\n return out;\n },\n async buildDeposit(quote: Quote): Promise<UnsignedTx> {\n return call<UnsignedTx>(\"build\", {quote});\n },\n async preflightDeposit(quote: Quote, tx: UnsignedTx): Promise<void> {\n await call<Record<string, never>>(\"preflight\", {quote, tx});\n },\n async trackIntent(ref: IntentRef): Promise<IntentStatus> {\n return call<IntentStatus>(\"track\", {ref});\n },\n async notifyDeposit(intentId: string, txHash: string): Promise<void> {\n await call<Record<string, never>>(\"notify\", {intentId, txHash});\n },\n async registerSignedOrder(intentId: string, signature: string): Promise<void> {\n await call<Record<string, never>>(\"register\", {intentId, signature});\n },\n async buildClaim(ref: IntentRef): Promise<UnsignedTx | null> {\n const out = await call<UnsignedTx | null | Record<string, never>>(\"claim\", {ref});\n // A backend without the action (older deploy) answers {} — treat as \"not ready\".\n if (!out || typeof out !== \"object\" || !(\"to\" in out)) return null;\n return out as UnsignedTx;\n },\n ...(opts.supportsChain ? {supportsChain: opts.supportsChain.bind(opts)} : {}),\n ...(opts.supportsChainPair ? {supportsChainPair: opts.supportsChainPair.bind(opts)} : {}),\n ...(opts.supportsSameChain !== undefined ? {supportsSameChain: opts.supportsSameChain} : {}),\n ...(opts.claimTargets ? {claimTargets: opts.claimTargets} : {}),\n };\n}\n"],"mappings":"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","names":[]}
package/dist/index.cjs CHANGED
@@ -54,6 +54,7 @@ __export(src_exports, {
54
54
  remoteProvider: () => remoteProvider,
55
55
  shortAddress: () => shortAddress,
56
56
  slippageClassOf: () => slippageClassOf,
57
+ sourceAssetOf: () => sourceAssetOf,
57
58
  suggestSlippage: () => suggestSlippage,
58
59
  swap2ChainId: () => swap2ChainId,
59
60
  validQuote: () => validQuote
@@ -158,6 +159,7 @@ function validQuote(q, params, provider) {
158
159
  }
159
160
 
160
161
  // src/util.ts
162
+ var import_swap2_assets = require("@anywalletkit/swap2-assets");
161
163
  function namespace(chain) {
162
164
  return chain.split(":")[0] ?? "";
163
165
  }
@@ -190,6 +192,10 @@ function anySignal(signals) {
190
192
  function redactUrls(text) {
191
193
  return text.replace(/\b[a-z][a-z0-9+.-]*:\/\/[^\s"'<>]+/gi, "<url>");
192
194
  }
195
+ function sourceAssetOf(params) {
196
+ const decimals = params.sourceDecimals;
197
+ return (0, import_swap2_assets.resolveAsset)(params.sourceChain, params.sourceToken, decimals === void 0 ? {} : { allowUnlisted: true, decimals });
198
+ }
193
199
 
194
200
  // src/remote.ts
195
201
  function remoteProvider(opts) {
@@ -260,7 +266,8 @@ function remoteProvider(opts) {
260
266
  },
261
267
  ...opts.supportsChain ? { supportsChain: opts.supportsChain.bind(opts) } : {},
262
268
  ...opts.supportsChainPair ? { supportsChainPair: opts.supportsChainPair.bind(opts) } : {},
263
- ...opts.supportsSameChain !== void 0 ? { supportsSameChain: opts.supportsSameChain } : {}
269
+ ...opts.supportsSameChain !== void 0 ? { supportsSameChain: opts.supportsSameChain } : {},
270
+ ...opts.claimTargets ? { claimTargets: opts.claimTargets } : {}
264
271
  };
265
272
  }
266
273
 
@@ -268,7 +275,7 @@ function remoteProvider(opts) {
268
275
  var import_core = require("@anywalletkit/core");
269
276
  var import_viem = require("viem");
270
277
  var import_tx_preview_evm = require("@anywalletkit/tx-preview-evm");
271
- var import_swap2_assets = require("@anywalletkit/swap2-assets");
278
+ var import_swap2_assets2 = require("@anywalletkit/swap2-assets");
272
279
  var ZERO = "0x0000000000000000000000000000000000000000";
273
280
  var ERC20_TRANSFER_ABI = [
274
281
  {
@@ -291,12 +298,14 @@ var inFlightKey = (fromChain, o) => JSON.stringify(
291
298
  [fromChain, o.fromToken, o.toToken, o.toChain, o.recipient, o.refundAddress, o.fromTokenAmount, o.toTokenAmount, o.minAmountOut, o.slippage],
292
299
  (_k, v) => typeof v === "bigint" ? v.toString() : v
293
300
  );
301
+ var LOST_ANSWER = /socket hang up|ECONNRESET|ECONNABORTED|ETIMEDOUT|EPIPE|ENETUNREACH|EHOSTUNREACH|EAI_AGAIN|UND_ERR_[A-Z_]+|timed? ?out|TimeoutError|request aborted|AbortError|connection (?:closed|reset)|fetch failed|failed to fetch|network request failed|other side closed/i;
294
302
  function maybeSent(error) {
295
- for (let e = error, i = 0; e && typeof e === "object" && i < 5; e = e.cause, i++) {
296
- if (e.maybeSent === true) return typeof e.hash === "string" && e.hash ? { hash: e.hash } : {};
297
- }
298
- const text = error instanceof Error ? error.message : String(error);
299
- return /socket hang up|ECONNRESET|ETIMEDOUT|EPIPE|timed? ?out|request aborted|connection (?:closed|reset)/i.test(text) ? {} : null;
303
+ const chain = [];
304
+ for (let e = error, i = 0; e && typeof e === "object" && i < 6; e = e.cause, i++) chain.push(e);
305
+ for (const e of chain) if (e.maybeSent === true) return typeof e.hash === "string" && e.hash ? { hash: e.hash } : {};
306
+ if (chain.some((e) => e.answered === true)) return null;
307
+ const texts = chain.length ? chain.map((e) => [e.message, e.code, e.name].filter((x) => typeof x === "string").join(" ")) : [String(error)];
308
+ return texts.some((t) => LOST_ANSWER.test(t)) ? {} : null;
300
309
  }
301
310
  var NATIVE_DECIMALS = Object.freeze({
302
311
  "eip155:5042": 18
@@ -380,22 +389,24 @@ function reviewedIdentity(plan) {
380
389
  return JSON.stringify(pinned, (_k, v) => typeof v === "bigint" ? v.toString() : v);
381
390
  }
382
391
  var SWAP2_ID_PREFIX = "awk1";
383
- function encodeId(provider, intentId, sourceTxHash) {
392
+ function encodeId(provider, intentId, sourceTxHash, destTxHash) {
384
393
  const enc = (v) => encodeURIComponent(v).replace(/\./g, "%2E");
385
- return `${SWAP2_ID_PREFIX}.${[provider, intentId, sourceTxHash].filter((v) => v != null).map(enc).join(".")}`;
394
+ const parts = [provider, intentId, ...sourceTxHash != null ? [sourceTxHash, ...destTxHash != null ? [destTxHash] : []] : []];
395
+ return `${SWAP2_ID_PREFIX}.${parts.map(enc).join(".")}`;
386
396
  }
387
397
  function decodeId(id) {
388
398
  try {
389
399
  if (!id.startsWith(`${SWAP2_ID_PREFIX}.`)) throw new Error("unsupported id version");
390
400
  const parts = id.slice(SWAP2_ID_PREFIX.length + 1).split(".");
391
- if (parts.length < 2 || parts.length > 3 || !parts[0] || !parts[1]) throw new Error("invalid id shape");
401
+ if (parts.length < 2 || parts.length > 4 || !parts[0] || !parts[1]) throw new Error("invalid id shape");
392
402
  const provider = decodeURIComponent(parts[0]);
393
403
  const intentId = decodeURIComponent(parts[1]);
394
404
  const sourceTxHash = parts[2] ? decodeURIComponent(parts[2]) : void 0;
395
- if (!/^[A-Za-z0-9_-]+$/.test(provider) || !intentId || parts[2] !== void 0 && !sourceTxHash) {
405
+ const destTxHash = parts[3] ? decodeURIComponent(parts[3]) : void 0;
406
+ if (!/^[A-Za-z0-9_-]+$/.test(provider) || !intentId || parts[2] !== void 0 && !sourceTxHash || parts[3] !== void 0 && !destTxHash) {
396
407
  throw new Error("invalid id contents");
397
408
  }
398
- return { provider, intentId, ...sourceTxHash ? { sourceTxHash } : {} };
409
+ return { provider, intentId, ...sourceTxHash ? { sourceTxHash } : {}, ...destTxHash ? { destTxHash } : {} };
399
410
  } catch {
400
411
  throw new Swap2ValidationError("swap2: malformed swap2 status id");
401
412
  }
@@ -447,16 +458,23 @@ function toCaip2(chain, fallback) {
447
458
  }
448
459
  return swap2ChainId(chain);
449
460
  }
461
+ function nonEmptyRef(ref, side) {
462
+ if (typeof ref !== "string" || ref.trim() === "") throw new Swap2ValidationError(`swap2: ${side} token must not be empty (pass the symbol or the contract address)`);
463
+ return ref;
464
+ }
450
465
  function toSymbol(caip2, ref) {
451
- const byToken = ref.startsWith("0x") || ref.length > 20 ? (0, import_swap2_assets.ccAssetByToken)(caip2, ref) : void 0;
466
+ nonEmptyRef(ref, "source");
467
+ const byToken = ref.startsWith("0x") || ref.length > 20 ? (0, import_swap2_assets2.ccAssetByToken)(caip2, ref) : void 0;
452
468
  if (byToken) return byToken.symbol;
453
- if ((0, import_swap2_assets.ccAsset)(caip2, ref)) return ref;
469
+ if ((0, import_swap2_assets2.ccAsset)(caip2, ref)) return ref;
454
470
  return ref;
455
471
  }
456
- function toDestAddress(caip2, ref) {
472
+ function toDestAddress(caip2, ref, allowUnlisted = false) {
473
+ nonEmptyRef(ref, "destination");
457
474
  if (ref.startsWith("0x")) return ref;
458
- const asset = (0, import_swap2_assets.ccAssetByToken)(caip2, ref) ?? (0, import_swap2_assets.ccAsset)(caip2, ref);
475
+ const asset = (0, import_swap2_assets2.ccAssetByToken)(caip2, ref) ?? (0, import_swap2_assets2.ccAsset)(caip2, ref);
459
476
  if (!asset) {
477
+ if (allowUnlisted && (0, import_swap2_assets2.isTokenAddress)(caip2, ref)) return ref;
460
478
  throw new Error(`swap2: unknown destination token '${ref}' on ${caip2}`);
461
479
  }
462
480
  return asset.contract ?? ZERO;
@@ -538,6 +556,15 @@ function mapStatus(s) {
538
556
  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";
539
557
  return s.destTxHash ? { status, transactions: [{ hash: s.destTxHash, type: "destination" }] } : { status };
540
558
  }
559
+ function blankToUndefined(v) {
560
+ return typeof v === "string" && v.trim() === "" ? void 0 : v;
561
+ }
562
+ function withoutBlankAddresses(o) {
563
+ const { recipient, refundAddress, ...rest } = o;
564
+ const r = blankToUndefined(recipient);
565
+ const f = blankToUndefined(refundAddress);
566
+ return { ...rest, ...r !== void 0 ? { recipient: r } : {}, ...f !== void 0 ? { refundAddress: f } : {} };
567
+ }
541
568
  var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
542
569
  fromChain;
543
570
  constructor(account, config) {
@@ -570,19 +597,61 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
570
597
  pairSupported(fromChain, toChain) {
571
598
  return this.chainSupported(fromChain) && this.chainSupported(toChain);
572
599
  }
600
+ unlistedAllowed() {
601
+ return this._config.allowUnlistedTokens === true;
602
+ }
603
+ /** The source asset the options name: the catalog's, or (allowUnlistedTokens) an unlisted one with its decimals. */
604
+ sourceAsset(options) {
605
+ const decimals = options.fromTokenDecimals;
606
+ return (0, import_swap2_assets2.resolveAsset)(this.fromChain, toSymbol(this.fromChain, options.fromToken), {
607
+ allowUnlisted: this.unlistedAllowed(),
608
+ ...decimals !== void 0 ? { decimals } : {}
609
+ });
610
+ }
611
+ unlistedDecimals = /* @__PURE__ */ new Map();
612
+ /**
613
+ * Options ready to quote: with `allowUnlistedTokens`, an unlisted source token gets its decimals,
614
+ * read from the chain through the account (cached per token) — a caller's `fromTokenDecimals` that
615
+ * disagrees is refused. Listed tokens and every option otherwise pass through unchanged.
616
+ */
617
+ async prepareOptions(options) {
618
+ if (!this.unlistedAllowed()) return options;
619
+ const ref = toSymbol(this.fromChain, options.fromToken);
620
+ if ((0, import_swap2_assets2.resolveAsset)(this.fromChain, ref) || !(0, import_swap2_assets2.isTokenAddress)(this.fromChain, ref)) return options;
621
+ const key = ref.startsWith("0x") ? ref.toLowerCase() : ref;
622
+ let decimals = this.unlistedDecimals.get(key);
623
+ if (decimals === void 0) {
624
+ const reader = this._account;
625
+ if (typeof reader?.getTokenInfo !== "function") {
626
+ if (options.fromTokenDecimals === void 0) {
627
+ throw new Swap2ValidationError(`swap2: '${ref}' is not in the catalog \u2014 pass fromTokenDecimals, or use an account that can read token info`);
628
+ }
629
+ return options;
630
+ }
631
+ decimals = (await reader.getTokenInfo(ref)).decimals;
632
+ this.unlistedDecimals.set(key, decimals);
633
+ }
634
+ if (options.fromTokenDecimals !== void 0 && options.fromTokenDecimals !== decimals) {
635
+ throw new Swap2ValidationError(`swap2: fromTokenDecimals ${options.fromTokenDecimals} does not match the token's ${decimals}`);
636
+ }
637
+ return snapshotOptions({ ...options, fromTokenDecimals: decimals });
638
+ }
573
639
  /** Swap2Options → the engine's QuoteParams (exact-in only). */
574
- toParams(options, dry) {
640
+ toParams(given, dry) {
641
+ const options = withoutBlankAddresses(given);
575
642
  if (options.fromTokenAmount == null) {
576
643
  throw new Swap2UnsupportedOperationError("swap2: exact-out swap2 is not supported by the engine");
577
644
  }
578
645
  const destChain = toCaip2(options.toChain, this.fromChain);
579
646
  const constraints = callerConstraints(options);
580
647
  const amountIn = exactPositiveAmount(options.fromTokenAmount, "fromTokenAmount");
648
+ const source = this.sourceAsset(options);
581
649
  const params = {
582
650
  sourceChain: this.fromChain,
583
- sourceToken: toSymbol(this.fromChain, options.fromToken),
651
+ sourceToken: (0, import_swap2_assets2.isUnlisted)(source) ? source.contract : toSymbol(this.fromChain, options.fromToken),
652
+ ...(0, import_swap2_assets2.isUnlisted)(source) ? { sourceDecimals: source.decimals } : {},
584
653
  destChain,
585
- destToken: toDestAddress(destChain, options.toToken),
654
+ destToken: toDestAddress(destChain, options.toToken, this.unlistedAllowed()),
586
655
  amountIn: amountIn.toString(),
587
656
  destAddress: options.recipient ?? "",
588
657
  dry,
@@ -638,7 +707,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
638
707
  }
639
708
  }
640
709
  assertQuoteFeeCaps(q, options, config) {
641
- const source = (0, import_swap2_assets.ccAsset)(this.fromChain, toSymbol(this.fromChain, options.fromToken));
710
+ const source = this.sourceAsset(options);
642
711
  if (!source) throw new Error(`swap2: unknown source token '${options.fromToken}' on ${this.fromChain}`);
643
712
  const token = source.contract ?? source.assetId;
644
713
  const fees = q.fees?.map((fee) => ({ ...fee, amount: BigInt(fee.amount) }));
@@ -683,7 +752,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
683
752
  * gave one.
684
753
  */
685
754
  async quoteSwap2(options) {
686
- const snapshot = snapshotOptions(options);
755
+ const snapshot = await this.prepareOptions(snapshotOptions(options));
687
756
  const q = await this.resolveQuote(this.toParams(snapshot, true));
688
757
  if (!q) {
689
758
  const detail = this.noRouteDetail();
@@ -725,9 +794,9 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
725
794
  this.assertFeeConfig(configSnapshot);
726
795
  await this.assertAccountChain(signer);
727
796
  const selfAddress = await signer.getAddress();
728
- const recipient = snapshot.recipient ?? selfAddress;
729
- const refundAddress = snapshot.refundAddress ?? selfAddress;
730
- const executionOptions = snapshotOptions({ ...snapshot, recipient, refundAddress });
797
+ const recipient = blankToUndefined(snapshot.recipient) ?? selfAddress;
798
+ const refundAddress = blankToUndefined(snapshot.refundAddress) ?? selfAddress;
799
+ const executionOptions = await this.prepareOptions(snapshotOptions({ ...snapshot, recipient, refundAddress }));
731
800
  const params = this.toParams(executionOptions, false);
732
801
  const resolved = await this.resolveQuote(params);
733
802
  if (!resolved) {
@@ -769,7 +838,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
769
838
  throw new Error("swap2: provider build does not match the validated quote transaction");
770
839
  }
771
840
  } else if (!unsigned.bodyBoc && !unsigned.typedData) {
772
- if (!q.depositAddress || unsigned.to !== q.depositAddress || (unsigned.memo ?? null) !== q.depositMemo) {
841
+ if (!q.depositAddress || unsigned.to !== q.depositAddress || (unsigned.memo ?? null) !== q.depositMemo || unsigned.data !== void 0 || unsigned.approvalAddress !== void 0 || unsigned.serializedTx !== void 0) {
773
842
  throw new Error("swap2: provider build does not match the validated quote deposit");
774
843
  }
775
844
  }
@@ -808,7 +877,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
808
877
  }
809
878
  let tokenId;
810
879
  if (!unsigned.data && !unsigned.bodyBoc && !unsigned.typedData && !serialized) {
811
- const src = (0, import_swap2_assets.ccAsset)(this.fromChain, toSymbol(this.fromChain, executionOptions.fromToken));
880
+ const src = this.sourceAsset(executionOptions);
812
881
  if (src?.contract) {
813
882
  if (this.fromChain.startsWith("eip155:")) {
814
883
  unsigned = {
@@ -834,7 +903,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
834
903
  if (tokenId && (cellBody !== void 0 || unsigned.memo !== void 0 && !tronSource && !nearSource && !tonSource)) {
835
904
  throw new Error("swap2: token deposit with memo/cell payload is not implemented");
836
905
  }
837
- const source = (0, import_swap2_assets.ccAsset)(this.fromChain, toSymbol(this.fromChain, executionOptions.fromToken));
906
+ const source = this.sourceAsset(executionOptions);
838
907
  if (!source) throw new Error(`swap2: unknown source token '${executionOptions.fromToken}' on ${this.fromChain}`);
839
908
  const executionValue = BigInt(unsigned.value);
840
909
  if (serialized) {
@@ -1124,10 +1193,10 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
1124
1193
  }
1125
1194
  /** Status of a swap by the id `swap2()` returned, asked from the provider that executed it. */
1126
1195
  async getSwap2Status(id, _options) {
1127
- const { provider: name, intentId, sourceTxHash } = decodeId(id);
1196
+ const { provider: name, intentId, sourceTxHash, destTxHash } = decodeId(id);
1128
1197
  const provider = this.providerFor(name);
1129
1198
  if (!provider) throw new Error(`swap2: no swap2 found for id '${id}'`);
1130
- return mapStatus(await provider.trackIntent(sourceTxHash == null ? { intentId } : { intentId, sourceTxHash }));
1199
+ return mapStatus(await provider.trackIntent({ intentId, ...sourceTxHash != null ? { sourceTxHash } : {}, ...destTxHash != null ? { destTxHash } : {} }));
1131
1200
  }
1132
1201
  /**
1133
1202
  * Two-phase routes (CCTP-style burn-and-mint): once `getSwap2Status` reports
@@ -1173,6 +1242,17 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
1173
1242
  if (unsigned.chainId !== void 0 && String(unsigned.chainId) !== expectedId) {
1174
1243
  throw new Error(`swap2: claim chainId ${unsigned.chainId} does not match ${unsigned.chain}`);
1175
1244
  }
1245
+ if (typeof provider.claimTargets !== "function") {
1246
+ throw new Swap2UnsupportedOperationError(`swap2: provider '${name}' declares no claimTargets \u2014 a claim may only call contracts the provider names for its chain`);
1247
+ }
1248
+ const targets = provider.claimTargets(unsigned.chain).map((a) => a.toLowerCase());
1249
+ if (!targets.includes(unsigned.to.toLowerCase())) {
1250
+ throw new Swap2ValidationError(`swap2: claim target ${unsigned.to} is not a contract '${name}' declares for ${unsigned.chain} \u2014 refused`);
1251
+ }
1252
+ const claimCall = (0, import_tx_preview_evm.decodeCall)({ to: unsigned.to, data: unsigned.data, value: 0n });
1253
+ if (claimCall.transfer || claimCall.approval) {
1254
+ throw new Swap2ValidationError(`swap2: claim calldata (${claimCall.method}) moves tokens or grants an allowance \u2014 refused`);
1255
+ }
1176
1256
  const actual = await readAccountChain(signer);
1177
1257
  if (actual == null) throw new Error(`swap2: destination account chain identity is unavailable for ${unsigned.chain}`);
1178
1258
  const actualText = typeof actual === "string" ? actual : String(actual);
@@ -1188,7 +1268,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
1188
1268
  });
1189
1269
  if (!sent.hash) throw new Swap2ExecutionError("swap2: account did not return a claim transaction hash");
1190
1270
  this.emitPhase("broadcast", { hash: sent.hash });
1191
- return { hash: sent.hash, chain: unsigned.chain, id };
1271
+ return { hash: sent.hash, chain: unsigned.chain, id: encodeId(name, intentId, sourceTxHash, sent.hash) };
1192
1272
  }
1193
1273
  /** The inherited bridge() has no floor field: quote first, then bind execution
1194
1274
  * to that quote's minimum so the inherited convenience API stays safe. */
@@ -1230,7 +1310,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
1230
1310
  /** Source chains this protocol can swap from. */
1231
1311
  async getSupportedChains() {
1232
1312
  if (!this.protocolEnabled()) return [];
1233
- return import_swap2_assets.CC_SOURCE_CHAINS.filter((c) => this.chainSupported(c.caip2)).map((c) => ({
1313
+ return import_swap2_assets2.CC_SOURCE_CHAINS.filter((c) => this.chainSupported(c.caip2)).map((c) => ({
1234
1314
  id: c.caip2,
1235
1315
  name: c.label,
1236
1316
  type: chainType(c.caip2),
@@ -1243,12 +1323,14 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
1243
1323
  const wantTo = options?.toChain != null ? toCaip2(options.toChain, "") : void 0;
1244
1324
  if (!this.protocolEnabled() || wantFrom && wantTo && !this.pairSupported(wantFrom, wantTo)) return [];
1245
1325
  const out = [];
1246
- for (const c of import_swap2_assets.CC_SOURCE_CHAINS) {
1326
+ for (const c of import_swap2_assets2.CC_SOURCE_CHAINS) {
1247
1327
  if (!this.chainSupported(c.caip2)) continue;
1248
1328
  if ((wantFrom || wantTo) && c.caip2 !== wantFrom && c.caip2 !== wantTo) continue;
1249
1329
  for (const a of c.assets) {
1250
1330
  out.push({
1251
- token: a.assetId,
1331
+ // What quoteSwap2 takes back: the contract, or the symbol of the native coin — never the
1332
+ // provider-specific assetId, which is "" for most catalog entries (round 3, M25).
1333
+ token: a.contract ?? a.symbol,
1252
1334
  chain: c.caip2,
1253
1335
  symbol: a.symbol,
1254
1336
  decimals: a.decimals,
@@ -1330,7 +1412,7 @@ var SingleProviderSwap2 = class extends BaseSwap2Protocol {
1330
1412
 
1331
1413
  // src/activity.ts
1332
1414
  var import_core2 = require("@anywalletkit/core");
1333
- var import_swap2_assets2 = require("@anywalletkit/swap2-assets");
1415
+ var import_swap2_assets3 = require("@anywalletkit/swap2-assets");
1334
1416
  function calledContract(tx) {
1335
1417
  const to = tx.call?.to ?? tx.to;
1336
1418
  if (!to) return null;
@@ -1367,7 +1449,7 @@ function matchesCall(tx, contracts, selectors) {
1367
1449
  return sel !== void 0 && want.has(sel);
1368
1450
  }
1369
1451
  function chainLabel(caip2) {
1370
- return (0, import_swap2_assets2.ccChain)(caip2)?.label ?? caip2;
1452
+ return (0, import_swap2_assets3.ccChain)(caip2)?.label ?? caip2;
1371
1453
  }
1372
1454
  function shortAddress(a) {
1373
1455
  return a.length > 14 ? `${a.slice(0, 6)}\u2026${a.slice(-4)}` : a;
@@ -1386,7 +1468,7 @@ function assetMetaFor(tx) {
1386
1468
  const key = token === null ? null : token.startsWith("0x") ? token.toLowerCase() : token;
1387
1469
  const known = fromTransfers.get(key);
1388
1470
  if (known?.symbol !== void 0 && known.decimals !== void 0) return known;
1389
- const cat = (0, import_swap2_assets2.ccAssetByToken)(tx.chain, token ?? "");
1471
+ const cat = (0, import_swap2_assets3.ccAssetByToken)(tx.chain, token ?? "");
1390
1472
  if (!cat) return known;
1391
1473
  const meta = { symbol: known?.symbol ?? cat.symbol, decimals: known?.decimals ?? cat.decimals };
1392
1474
  return meta;
@@ -1394,7 +1476,7 @@ function assetMetaFor(tx) {
1394
1476
  }
1395
1477
  function assetLabel(tx, a) {
1396
1478
  if (a.symbol) return a.symbol;
1397
- if (a.token === null) return (0, import_swap2_assets2.ccAssetByToken)(tx.chain, "")?.symbol ?? "native";
1479
+ if (a.token === null) return (0, import_swap2_assets3.ccAssetByToken)(tx.chain, "")?.symbol ?? "native";
1398
1480
  return shortAddress(a.token);
1399
1481
  }
1400
1482
  function ownerAssets(tx, ctx) {