@anywalletkit/protocol-swap2-core 0.6.1 → 0.7.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 (53) hide show
  1. package/README.md +1 -1
  2. package/dist/chunk-2KC734UX.js +117 -0
  3. package/dist/chunk-2KC734UX.js.map +1 -0
  4. package/dist/{chunk-IGTMUTNJ.js → chunk-5E5FWYZQ.js} +123 -6
  5. package/dist/chunk-5E5FWYZQ.js.map +1 -0
  6. package/dist/{chunk-ZMWK3RQ4.js → chunk-CT2REW2V.js} +39 -5
  7. package/dist/chunk-CT2REW2V.js.map +1 -0
  8. package/dist/{chunk-JFGMF6FO.js → chunk-U653ZREM.js} +9 -3
  9. package/dist/chunk-U653ZREM.js.map +1 -0
  10. package/dist/{chunk-KIAQXIBG.js → chunk-WBYCNN6S.js} +16 -2
  11. package/dist/chunk-WBYCNN6S.js.map +1 -0
  12. package/dist/index.cjs +243 -9
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +4 -4
  15. package/dist/index.d.ts +4 -4
  16. package/dist/index.js +10 -6
  17. package/dist/remote.cjs +37 -3
  18. package/dist/remote.cjs.map +1 -1
  19. package/dist/remote.d.cts +26 -1
  20. package/dist/remote.d.ts +26 -1
  21. package/dist/remote.js +2 -2
  22. package/dist/server.cjs +23 -4
  23. package/dist/server.cjs.map +1 -1
  24. package/dist/server.js +6 -5
  25. package/dist/server.js.map +1 -1
  26. package/dist/swap2/base.cjs +180 -15
  27. package/dist/swap2/base.cjs.map +1 -1
  28. package/dist/swap2/base.d.cts +19 -1
  29. package/dist/swap2/base.d.ts +19 -1
  30. package/dist/swap2/base.js +2 -1
  31. package/dist/swap2/single.cjs +198 -15
  32. package/dist/swap2/single.cjs.map +1 -1
  33. package/dist/swap2/single.js +4 -3
  34. package/dist/types.cjs.map +1 -1
  35. package/dist/types.d.cts +32 -1
  36. package/dist/types.d.ts +32 -1
  37. package/dist/util.cjs +70 -2
  38. package/dist/util.cjs.map +1 -1
  39. package/dist/util.d.cts +13 -2
  40. package/dist/util.d.ts +13 -2
  41. package/dist/util.js +7 -3
  42. package/dist/validate.cjs +14 -0
  43. package/dist/validate.cjs.map +1 -1
  44. package/dist/validate.d.cts +10 -2
  45. package/dist/validate.d.ts +10 -2
  46. package/dist/validate.js +3 -1
  47. package/package.json +4 -4
  48. package/dist/chunk-IGTMUTNJ.js.map +0 -1
  49. package/dist/chunk-JFGMF6FO.js.map +0 -1
  50. package/dist/chunk-KIAQXIBG.js.map +0 -1
  51. package/dist/chunk-MNJTMSFN.js +0 -49
  52. package/dist/chunk-MNJTMSFN.js.map +0 -1
  53. package/dist/chunk-ZMWK3RQ4.js.map +0 -1
package/README.md CHANGED
@@ -15,7 +15,7 @@ On npm: [@anywalletkit/protocol-swap2-core](https://www.npmjs.com/package/@anywa
15
15
 
16
16
  - **`BaseSwap2Protocol` / `SingleProviderSwap2`** — a `Swap2Protocol` (from `@anywalletkit/core`) over a provider. It quotes, binds the execution to the quote, runs approvals, sends, and resumes status by id (`getSwap2Status`, `claimSwap2` for two-phase routes).
17
17
  - **Safety checks.** Quote validation, a minimum-output floor, fee caps, a review gate before signing, and refusals that name their reason. Status ids look like `awk1.<provider>.<intent>[.<hash>]` (`SWAP2_ID_PREFIX`).
18
- - **`remoteProvider`** — runs a provider on your server (API keys, shared state) while the app keeps the keys and signs. A provider with a claim step (CCTP) needs `claimTargets` from the app (e.g. `cctpClaimTargets`): `claimSwap2` calls only those contracts.
18
+ - **`remoteProvider`** — runs a provider on your server (API keys, shared state) while the app keeps the keys and signs. The backend is not the authority over what the wallet signs: spread the provider package's pins into the stub (`remoteProvider({name, baseUrl, ...lifiPins})`, `fyndPins`, `uniswapPins`, `cctpPins`…). The engine then lets an EVM route call only the pinned `callTargets`, approve only the pinned `spenders`, and runs the provider's own `verifyQuote` (Fynd: `checkFyndTransaction`) on the client; a stub without pins gets no EVM calldata route at all (the router drops it with the reason), while deposit-address routes need none. Every token call in a swap's calldata, from any provider, must move the source token, at most the swap amount; operator and Permit2 approvals are never a swap's main call. A typed (gasless) order is signed only when it is the swap's own Limit Order Protocol `Order` — verifying contract = the approved settlement contract, maker = this account, the source token, exactly the input — never a `Permit*` or another typed message; a provider's own check of the build can run on the client too (`verifyBuild`). A TON cell-body deposit (`bodyBoc`) is bound too: it attaches the input plus at most `tonGasCeiling` of gas (default 1 TON; a jetton source: the ceiling alone), a jetton source's body must be a TEP-74 transfer of exactly the input from this account's own jetton wallet (`getJettonWalletAddress`) with the excess returning here, the decoded body reaches the review (`cellBodyDecoded`), and a remote stub's cell body needs the app's own validator (`verifyBuild`). An absolute `baseUrl` must be `https:` (a same-origin path is fine; `allowInsecureBaseUrl` for local development). A provider with a claim step (CCTP) also takes `claimTargets` from the app: `claimSwap2` calls only those contracts.
19
19
  - **`createSwap2Handler`** (`@anywalletkit/protocol-swap2-core/server`, server-only) — the other end of `remoteProvider`: validates every request, HMAC-seals only quotes that pass the engine's own checks (`validQuote`: provider, input, floor, slippage, expiry; else 502) and builds only sealed, unexpired ones, puts an intent token on every swap id (tracking, claims and notices without it are refused), passes refusals through with URLs redacted; you supply `guard` (auth + rate limits, it sees the validated request) and, with per-user sign-in, `subject` (quotes and swap ids bound to the user). A reference server: [`examples/server`](https://github.com/ultratestbro/anywalletkit/tree/main/examples/server).
20
20
  - **Tokens outside the catalog** — `allowUnlistedTokens: true` on the engine (and on `createSwap2Handler` for a remote engine) lets a swap name a token the catalog does not list by its EVM contract or Solana mint: the user's own tokens. Off by default. The engine reads the token's decimals with the account's `getTokenInfo` and sends them as `sourceDecimals`; providers that trade any token resolve the source with `sourceAssetOf(params)`, the rest refuse it. Approvals and value binding are the same as for catalog tokens.
21
21
  - **Errors** — `Swap2Error` and its subclasses (quote, validation, execution, fee limit, status…).
@@ -0,0 +1,117 @@
1
+ // src/util.ts
2
+ import { resolveAsset } from "@anywalletkit/swap2-assets";
3
+ function namespace(chain) {
4
+ return chain.split(":")[0] ?? "";
5
+ }
6
+ function isEvm(chain) {
7
+ return namespace(chain) === "eip155";
8
+ }
9
+ function evmRef(chain) {
10
+ return Number(chain.split(":")[1] ?? "0");
11
+ }
12
+ function timeoutSignal(ms) {
13
+ const native = AbortSignal.timeout;
14
+ if (typeof native === "function") return native.call(AbortSignal, ms);
15
+ const ctrl = new AbortController();
16
+ setTimeout(() => ctrl.abort(), ms);
17
+ return ctrl.signal;
18
+ }
19
+ function anySignal(signals) {
20
+ const native = AbortSignal.any;
21
+ if (typeof native === "function") return native.call(AbortSignal, signals);
22
+ const ctrl = new AbortController();
23
+ for (const s of signals) {
24
+ if (s.aborted) {
25
+ ctrl.abort();
26
+ break;
27
+ }
28
+ s.addEventListener("abort", () => ctrl.abort(), { once: true });
29
+ }
30
+ return ctrl.signal;
31
+ }
32
+ var SCHEME_MAX = 32;
33
+ var isLetter = (c) => c >= 97 && c <= 122 || c >= 65 && c <= 90;
34
+ var isSchemeChar = (c) => isLetter(c) || c >= 48 && c <= 57 || c === 43 || c === 45 || c === 46;
35
+ var endsUrl = (c) => c === 34 || c === 39 || c === 60 || c === 62 || c >= 9 && c <= 13 || c === 32 || c === 160 || c === 5760 || c >= 8192 && c <= 8202 || c === 8232 || c === 8233 || c === 8239 || c === 8287 || c === 12288 || c === 65279;
36
+ function redactUrls(text) {
37
+ let out = "";
38
+ let done = 0;
39
+ for (let at = text.indexOf("://"); at !== -1; at = text.indexOf("://", at + 3)) {
40
+ let start = at;
41
+ while (start > done && at - start < SCHEME_MAX && isSchemeChar(text.charCodeAt(start - 1))) start--;
42
+ while (start < at && !isLetter(text.charCodeAt(start))) start++;
43
+ if (start === at) continue;
44
+ let end = at + 3;
45
+ while (end < text.length && !endsUrl(text.charCodeAt(end))) end++;
46
+ if (end === at + 3) continue;
47
+ out += `${text.slice(done, start)}<url>`;
48
+ done = end;
49
+ at = end - 3;
50
+ }
51
+ return done === 0 ? text : out + text.slice(done);
52
+ }
53
+ function sourceAssetOf(params) {
54
+ const decimals = params.sourceDecimals;
55
+ return resolveAsset(params.sourceChain, params.sourceToken, decimals === void 0 ? {} : { allowUnlisted: true, decimals });
56
+ }
57
+ var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
58
+ function base64Bytes(text) {
59
+ const s = text.replace(/-/g, "+").replace(/_/g, "/").replace(/=+$/, "");
60
+ if (!/^[A-Za-z0-9+/]*$/.test(s) || s.length % 4 === 1) return null;
61
+ const out = new Uint8Array(Math.floor(s.length * 3 / 4));
62
+ let bits = 0, acc = 0, n = 0;
63
+ for (const ch of s) {
64
+ acc = acc << 6 | B64.indexOf(ch);
65
+ bits += 6;
66
+ if (bits >= 8) {
67
+ bits -= 8;
68
+ out[n++] = acc >> bits & 255;
69
+ }
70
+ }
71
+ return out.subarray(0, n);
72
+ }
73
+ function crc16(bytes) {
74
+ let crc = 0;
75
+ for (const b of bytes) {
76
+ crc ^= b << 8;
77
+ for (let i = 0; i < 8; i++) crc = crc & 32768 ? (crc << 1 ^ 4129) & 65535 : crc << 1 & 65535;
78
+ }
79
+ return crc;
80
+ }
81
+ function tonRawAddress(address) {
82
+ if (address && typeof address === "object") {
83
+ const o = address;
84
+ if (typeof o.toRawString === "function") {
85
+ try {
86
+ return tonRawAddress(o.toRawString());
87
+ } catch {
88
+ return null;
89
+ }
90
+ }
91
+ if (typeof o.workChain === "number" && o.hash instanceof Uint8Array && o.hash.length === 32) {
92
+ return `${o.workChain}:${Array.from(o.hash, (b) => b.toString(16).padStart(2, "0")).join("")}`;
93
+ }
94
+ return null;
95
+ }
96
+ if (typeof address !== "string") return null;
97
+ const raw = /^(-?[0-9]{1,3}):([0-9a-fA-F]{64})$/.exec(address.trim());
98
+ if (raw) return `${Number(raw[1])}:${raw[2].toLowerCase()}`;
99
+ if (!/^[A-Za-z0-9+/_-]{48}$/.test(address.trim())) return null;
100
+ const bytes = base64Bytes(address.trim());
101
+ if (!bytes || bytes.length !== 36 || crc16(bytes.subarray(0, 34)) !== (bytes[34] << 8 | bytes[35])) return null;
102
+ const wc = bytes[1] === 255 ? -1 : bytes[1];
103
+ return `${wc}:${Array.from(bytes.subarray(2, 34), (b) => b.toString(16).padStart(2, "0")).join("")}`;
104
+ }
105
+
106
+ export {
107
+ namespace,
108
+ isEvm,
109
+ evmRef,
110
+ timeoutSignal,
111
+ anySignal,
112
+ redactUrls,
113
+ sourceAssetOf,
114
+ base64Bytes,
115
+ tonRawAddress
116
+ };
117
+ //# sourceMappingURL=chunk-2KC734UX.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/** Longest URL scheme taken out (`https`, `wss`, `git+ssh`…): a letter and up to 31 of `[A-Za-z0-9+.-]`. */\nconst SCHEME_MAX = 32;\nconst isLetter = (c: number): boolean => (c >= 97 && c <= 122) || (c >= 65 && c <= 90);\nconst isSchemeChar = (c: number): boolean => isLetter(c) || (c >= 48 && c <= 57) || c === 43 || c === 45 || c === 46;\n/** What ends a URL in text: `\\s` as regexes define it, and `\"` `'` `<` `>`. */\nconst endsUrl = (c: number): boolean =>\n c === 34 || c === 39 || c === 60 || c === 62 || (c >= 9 && c <= 13) || c === 32 || c === 0xa0 || c === 0x1680 ||\n (c >= 0x2000 && c <= 0x200a) || c === 0x2028 || c === 0x2029 || c === 0x202f || c === 0x205f || c === 0x3000 || c === 0xfeff;\n\n/**\n * Remove URLs from text that may travel to a client (a refusal reason, a build error):\n * transport errors carry the endpoint — and with a keyed RPC (QuickNode, Alchemy,\n * Infura…) the key sits in its path (scan 2026-10-23 F8). What `/[a-z][a-z0-9+.-]{0,31}:\\/\\/[^\\s\"'<>]+/gi`\n * would replace, found from each `://` instead of tried at every position: linear in the length of\n * `text` whatever an API or a client puts in it (\"a.a.a…\" made the regex this replaced quadratic;\n * scan 2026-10-27 modules, F34). A URL glued to a word loses its last 32 scheme characters and the rest.\n */\nexport function redactUrls(text: string): string {\n let out = \"\";\n let done = 0; // text[0, done) is in `out`\n for (let at = text.indexOf(\"://\"); at !== -1; at = text.indexOf(\"://\", at + 3)) {\n let start = at;\n while (start > done && at - start < SCHEME_MAX && isSchemeChar(text.charCodeAt(start - 1))) start--;\n while (start < at && !isLetter(text.charCodeAt(start))) start++;\n if (start === at) continue; // no scheme: not a URL (and its tail is not scanned, so \"://://…\" stays linear)\n let end = at + 3;\n while (end < text.length && !endsUrl(text.charCodeAt(end))) end++;\n if (end === at + 3) continue; // nothing after \"://\"\n out += `${text.slice(done, start)}<url>`;\n done = end;\n at = end - 3;\n }\n return done === 0 ? text : out + text.slice(done);\n}\n\n/**\n * The source asset a provider is asked to sell: the catalog's entry for `params.sourceToken`, or —\n * only when the engine sent `sourceDecimals` (an unlisted token the user added) — an unlisted asset\n * named by its contract / mint. Providers that can trade any token use this instead of the catalog\n * lookup; the others keep refusing what the catalog does not list.\n */\nexport function sourceAssetOf(params: Pick<QuoteParams, \"sourceChain\" | \"sourceToken\" | \"sourceDecimals\">): CcAsset | undefined {\n const decimals = params.sourceDecimals;\n return resolveAsset(params.sourceChain, params.sourceToken, decimals === undefined ? {} : {allowUnlisted: true, decimals});\n}\n\nconst B64 = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n/** Standard or URL-safe base64 → bytes, without Buffer or atob (Hermes); null for anything else. */\nexport function base64Bytes(text: string): Uint8Array | null {\n const s = text.replace(/-/g, \"+\").replace(/_/g, \"/\").replace(/=+$/, \"\");\n if (!/^[A-Za-z0-9+/]*$/.test(s) || s.length % 4 === 1) return null;\n const out = new Uint8Array(Math.floor((s.length * 3) / 4));\n let bits = 0, acc = 0, n = 0;\n for (const ch of s) {\n acc = (acc << 6) | B64.indexOf(ch);\n bits += 6;\n if (bits >= 8) { bits -= 8; out[n++] = (acc >> bits) & 0xff; }\n }\n return out.subarray(0, n);\n}\n\n/** CRC16-XMODEM, the checksum of a user-friendly TON address. */\nfunction crc16(bytes: Uint8Array): number {\n let crc = 0;\n for (const b of bytes) {\n crc ^= b << 8;\n for (let i = 0; i < 8; i++) crc = crc & 0x8000 ? ((crc << 1) ^ 0x1021) & 0xffff : (crc << 1) & 0xffff;\n }\n return crc;\n}\n\n/**\n * A TON address in raw form (`<workchain>:<64 hex>`, lowercase) from a raw string, a user-friendly one (bounceable or\n * not, checksum verified), or an address object (`toRawString()` / `workChain` + `hash`); null for anything else. Two\n * spellings of one address compare equal in this form.\n */\nexport function tonRawAddress(address: unknown): string | null {\n if (address && typeof address === \"object\") {\n const o = address as {toRawString?: () => string; workChain?: unknown; hash?: unknown};\n if (typeof o.toRawString === \"function\") { try { return tonRawAddress(o.toRawString()); } catch { return null; } }\n if (typeof o.workChain === \"number\" && o.hash instanceof Uint8Array && o.hash.length === 32) {\n return `${o.workChain}:${Array.from(o.hash, (b) => b.toString(16).padStart(2, \"0\")).join(\"\")}`;\n }\n return null;\n }\n if (typeof address !== \"string\") return null;\n const raw = /^(-?[0-9]{1,3}):([0-9a-fA-F]{64})$/.exec(address.trim());\n if (raw) return `${Number(raw[1])}:${raw[2]!.toLowerCase()}`;\n if (!/^[A-Za-z0-9+/_-]{48}$/.test(address.trim())) return null;\n const bytes = base64Bytes(address.trim());\n if (!bytes || bytes.length !== 36 || crc16(bytes.subarray(0, 34)) !== ((bytes[34]! << 8) | bytes[35]!)) return null;\n const wc = bytes[1] === 0xff ? -1 : bytes[1]!;\n return `${wc}:${Array.from(bytes.subarray(2, 34), (b) => b.toString(16).padStart(2, \"0\")).join(\"\")}`;\n}\n"],"mappings":";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;AAGA,IAAM,aAAa;AACnB,IAAM,WAAW,CAAC,MAAwB,KAAK,MAAM,KAAK,OAAS,KAAK,MAAM,KAAK;AACnF,IAAM,eAAe,CAAC,MAAuB,SAAS,CAAC,KAAM,KAAK,MAAM,KAAK,MAAO,MAAM,MAAM,MAAM,MAAM,MAAM;AAElH,IAAM,UAAU,CAAC,MACf,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAO,KAAK,KAAK,KAAK,MAAO,MAAM,MAAM,MAAM,OAAQ,MAAM,QACtG,KAAK,QAAU,KAAK,QAAW,MAAM,QAAU,MAAM,QAAU,MAAM,QAAU,MAAM,QAAU,MAAM,SAAU,MAAM;AAUjH,SAAS,WAAW,MAAsB;AAC/C,MAAI,MAAM;AACV,MAAI,OAAO;AACX,WAAS,KAAK,KAAK,QAAQ,KAAK,GAAG,OAAO,IAAI,KAAK,KAAK,QAAQ,OAAO,KAAK,CAAC,GAAG;AAC9E,QAAI,QAAQ;AACZ,WAAO,QAAQ,QAAQ,KAAK,QAAQ,cAAc,aAAa,KAAK,WAAW,QAAQ,CAAC,CAAC,EAAG;AAC5F,WAAO,QAAQ,MAAM,CAAC,SAAS,KAAK,WAAW,KAAK,CAAC,EAAG;AACxD,QAAI,UAAU,GAAI;AAClB,QAAI,MAAM,KAAK;AACf,WAAO,MAAM,KAAK,UAAU,CAAC,QAAQ,KAAK,WAAW,GAAG,CAAC,EAAG;AAC5D,QAAI,QAAQ,KAAK,EAAG;AACpB,WAAO,GAAG,KAAK,MAAM,MAAM,KAAK,CAAC;AACjC,WAAO;AACP,SAAK,MAAM;AAAA,EACb;AACA,SAAO,SAAS,IAAI,OAAO,MAAM,KAAK,MAAM,IAAI;AAClD;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;AAEA,IAAM,MAAM;AAEL,SAAS,YAAY,MAAiC;AAC3D,QAAM,IAAI,KAAK,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG,EAAE,QAAQ,OAAO,EAAE;AACtE,MAAI,CAAC,mBAAmB,KAAK,CAAC,KAAK,EAAE,SAAS,MAAM,EAAG,QAAO;AAC9D,QAAM,MAAM,IAAI,WAAW,KAAK,MAAO,EAAE,SAAS,IAAK,CAAC,CAAC;AACzD,MAAI,OAAO,GAAG,MAAM,GAAG,IAAI;AAC3B,aAAW,MAAM,GAAG;AAClB,UAAO,OAAO,IAAK,IAAI,QAAQ,EAAE;AACjC,YAAQ;AACR,QAAI,QAAQ,GAAG;AAAE,cAAQ;AAAG,UAAI,GAAG,IAAK,OAAO,OAAQ;AAAA,IAAM;AAAA,EAC/D;AACA,SAAO,IAAI,SAAS,GAAG,CAAC;AAC1B;AAGA,SAAS,MAAM,OAA2B;AACxC,MAAI,MAAM;AACV,aAAW,KAAK,OAAO;AACrB,WAAO,KAAK;AACZ,aAAS,IAAI,GAAG,IAAI,GAAG,IAAK,OAAM,MAAM,SAAW,OAAO,IAAK,QAAU,QAAU,OAAO,IAAK;AAAA,EACjG;AACA,SAAO;AACT;AAOO,SAAS,cAAc,SAAiC;AAC7D,MAAI,WAAW,OAAO,YAAY,UAAU;AAC1C,UAAM,IAAI;AACV,QAAI,OAAO,EAAE,gBAAgB,YAAY;AAAE,UAAI;AAAE,eAAO,cAAc,EAAE,YAAY,CAAC;AAAA,MAAG,QAAQ;AAAE,eAAO;AAAA,MAAM;AAAA,IAAE;AACjH,QAAI,OAAO,EAAE,cAAc,YAAY,EAAE,gBAAgB,cAAc,EAAE,KAAK,WAAW,IAAI;AAC3F,aAAO,GAAG,EAAE,SAAS,IAAI,MAAM,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC;AAAA,IAC9F;AACA,WAAO;AAAA,EACT;AACA,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,QAAM,MAAM,qCAAqC,KAAK,QAAQ,KAAK,CAAC;AACpE,MAAI,IAAK,QAAO,GAAG,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,EAAG,YAAY,CAAC;AAC1D,MAAI,CAAC,wBAAwB,KAAK,QAAQ,KAAK,CAAC,EAAG,QAAO;AAC1D,QAAM,QAAQ,YAAY,QAAQ,KAAK,CAAC;AACxC,MAAI,CAAC,SAAS,MAAM,WAAW,MAAM,MAAM,MAAM,SAAS,GAAG,EAAE,CAAC,OAAQ,MAAM,EAAE,KAAM,IAAK,MAAM,EAAE,GAAK,QAAO;AAC/G,QAAM,KAAK,MAAM,CAAC,MAAM,MAAO,KAAK,MAAM,CAAC;AAC3C,SAAO,GAAG,EAAE,IAAI,MAAM,KAAK,MAAM,SAAS,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC;AACpG;","names":[]}
@@ -10,6 +10,9 @@ import {
10
10
  Swap2UnsupportedOperationError,
11
11
  Swap2ValidationError
12
12
  } from "./chunk-IVR3SREQ.js";
13
+ import {
14
+ tonRawAddress
15
+ } from "./chunk-2KC734UX.js";
13
16
 
14
17
  // src/swap2/base.ts
15
18
  import { Swap2Protocol } from "@anywalletkit/core";
@@ -130,7 +133,64 @@ function normalizeTypedDomain(domain) {
130
133
  else if (typeof raw === "string" && /^0x[0-9a-fA-F]+$/.test(raw)) chainId = safeChainId(BigInt(raw));
131
134
  return chainId === void 0 || !Number.isSafeInteger(chainId) ? domain : { ...domain, chainId };
132
135
  }
136
+ function assertTypedOrder(typedData, approvalAddress, e) {
137
+ const fail = (why) => new Swap2ValidationError(`swap2: typed order refused \u2014 ${why}`);
138
+ let parsed;
139
+ try {
140
+ parsed = JSON.parse(typedData);
141
+ } catch {
142
+ throw fail("the typed data is not JSON");
143
+ }
144
+ if (!parsed || typeof parsed !== "object") throw fail("the typed data is not an object");
145
+ const td = parsed;
146
+ const primary = td.primaryType;
147
+ if (typeof primary !== "string" || !primary) throw fail("it names no primaryType");
148
+ if (/^permit/i.test(primary)) throw fail(`${primary} is a token approval, not an order`);
149
+ if (primary !== "Order") throw fail(`primaryType ${primary} is not an order this engine signs`);
150
+ const fields = td.types?.Order;
151
+ const has = (name, type) => Array.isArray(fields) && fields.some((f) => f?.name === name && f?.type === type);
152
+ if (!has("maker", "address") || !has("makerAsset", "address") || !has("makingAmount", "uint256")) throw fail("the Order type has no maker / makerAsset / makingAmount");
153
+ const verifying = td.domain?.verifyingContract;
154
+ if (!approvalAddress || typeof verifying !== "string" || verifying.toLowerCase() !== approvalAddress.toLowerCase()) {
155
+ throw fail(`verifyingContract ${String(verifying)} is not the settlement contract the swap approves`);
156
+ }
157
+ if (e.chainId !== null && normalizeTypedDomain(td.domain)?.chainId !== e.chainId) throw fail("the domain chainId is not the source chain");
158
+ const m = td.message ?? {};
159
+ if (String(m.maker ?? "").toLowerCase() !== e.self.toLowerCase()) throw fail("the maker is not this account");
160
+ if (!e.token || String(m.makerAsset ?? "").toLowerCase() !== e.token.toLowerCase()) throw fail("the makerAsset is not the source token");
161
+ const raw = m.makingAmount;
162
+ const making = typeof raw === "string" && /^(?:0x[0-9a-fA-F]{1,64}|[0-9]{1,78})$/.test(raw) ? BigInt(raw) : typeof raw === "number" && Number.isSafeInteger(raw) ? BigInt(raw) : null;
163
+ if (making === null || making !== BigInt(e.amount)) throw fail(`makingAmount ${String(raw)} is not the swap's ${e.amount}`);
164
+ }
133
165
  var DEFAULT_SERIALIZED_NATIVE_ALLOWANCE = 20000000n;
166
+ var DEFAULT_TON_GAS_CEILING = 1000000000n;
167
+ var JETTON_TRANSFER_OP = 260734629;
168
+ function readTonBody(cell) {
169
+ let s;
170
+ try {
171
+ s = cell.beginParse();
172
+ } catch {
173
+ return { op: null };
174
+ }
175
+ let op;
176
+ try {
177
+ op = s.loadUint(32);
178
+ } catch {
179
+ return { op: null };
180
+ }
181
+ if (op !== JETTON_TRANSFER_OP) return { op };
182
+ try {
183
+ s.loadUintBig(64);
184
+ const amount = s.loadCoins();
185
+ const destination = tonRawAddress(s.loadAddress());
186
+ const response = s.loadMaybeAddress();
187
+ s.loadMaybeRef();
188
+ const forwardTon = s.loadCoins();
189
+ return { op, jettonTransfer: { amount, destination, responseDestination: response == null ? null : tonRawAddress(response), forwardTon } };
190
+ } catch {
191
+ return { op };
192
+ }
193
+ }
134
194
  function reviewedIdentity(plan) {
135
195
  const { feeUsd: _fee, etaSeconds: _eta, validUntilMs: _valid, approvals: _approvals, destAmount: _dest, requoted: _requoted, ...pinned } = plan;
136
196
  return JSON.stringify(pinned, (_k, v) => typeof v === "bigint" ? v.toString() : v);
@@ -623,10 +683,12 @@ var BaseSwap2Protocol = class extends Swap2Protocol {
623
683
  throw new Error(`swap2: provider transaction chain ${unsigned.chainId} does not match ${this.fromChain}`);
624
684
  }
625
685
  let tokenId;
686
+ let engineTransfer = false;
626
687
  if (!unsigned.data && !unsigned.bodyBoc && !unsigned.typedData && !serialized) {
627
688
  const src = this.sourceAsset(executionOptions);
628
689
  if (src?.contract) {
629
690
  if (this.fromChain.startsWith("eip155:")) {
691
+ engineTransfer = true;
630
692
  unsigned = {
631
693
  ...unsigned,
632
694
  to: src.contract,
@@ -665,12 +727,66 @@ var BaseSwap2Protocol = class extends Swap2Protocol {
665
727
  } else if (!unsigned.typedData && !unsigned.bodyBoc && executionValue !== BigInt(q.sourceAmount) + declaredNativeFee(q, this.fromChain)) {
666
728
  throw new Error("swap2: provider execution amount does not match the validated quote");
667
729
  }
730
+ let tonBody;
731
+ if (cellBody !== void 0) {
732
+ const refuse = (why) => {
733
+ throw new Swap2ValidationError(`swap2: TON cell-body build refused \u2014 ${why}`);
734
+ };
735
+ const raw = this._config.tonGasCeiling;
736
+ const ceiling = raw === void 0 ? DEFAULT_TON_GAS_CEILING : BigInt(raw);
737
+ if (ceiling < 0n) throw new Swap2ConfigurationError("swap2: tonGasCeiling must be non-negative");
738
+ if (provider.remote === true && provider.verifiesBuilds !== true) refuse("a remote provider's cell body needs the app's own validator (remoteProvider verifyBuild)");
739
+ tonBody = readTonBody(cellBody);
740
+ const input = BigInt(q.sourceAmount);
741
+ if (source.contract) {
742
+ if (executionValue > ceiling) refuse(`it attaches ${executionValue} nanoTON to a jetton transfer, more than the ${ceiling} gas ceiling`);
743
+ const t = tonBody.jettonTransfer;
744
+ if (!t) refuse("a jetton-source body must be a jetton transfer");
745
+ if (t.amount !== input) refuse(`the jetton transfer moves ${t.amount}, not the swap's ${input}`);
746
+ if (!t.responseDestination || t.responseDestination !== tonRawAddress(selfAddress)) refuse("the jetton transfer returns its excess to another wallet");
747
+ if (t.forwardTon >= executionValue) refuse("the jetton transfer forwards more TON than the message carries");
748
+ const own = typeof signer.getJettonWalletAddress === "function" ? tonRawAddress(await signer.getJettonWalletAddress(source.contract)) : null;
749
+ if (!own) refuse("the account cannot name its own jetton wallet (getJettonWalletAddress), so the target cannot be checked");
750
+ if (tonRawAddress(unsigned.to) !== own) refuse(`it is sent to ${unsigned.to}, not this account's ${source.symbol ?? "source"} jetton wallet`);
751
+ } else {
752
+ if (executionValue < input || executionValue - input > ceiling) refuse(`it attaches ${executionValue} nanoTON for a ${input} input, more than the ${ceiling} gas ceiling on top`);
753
+ const pinned = provider.remote === true ? [...provider.callTargets?.(this.fromChain) ?? [], ...q.depositAddress ? [q.depositAddress] : []].map(tonRawAddress).filter((a) => !!a) : [];
754
+ if (pinned.length && !pinned.includes(tonRawAddress(unsigned.to) ?? "")) refuse(`remote provider '${q.provider}' sends it to ${unsigned.to}, which the app did not pin (remoteProvider callTargets)`);
755
+ }
756
+ }
757
+ const remote = provider.remote === true;
758
+ const pins = (list) => (list ?? []).filter((a) => typeof a === "string" && /^0x[0-9a-fA-F]{40}$/.test(a)).map((a) => a.toLowerCase());
759
+ const pinnedTargets = remote ? pins(provider.callTargets?.(this.fromChain)) : [];
760
+ const pinnedSpenders = remote ? pins(provider.spenders?.(this.fromChain)) : [];
761
+ if (remote && unsigned.approvalAddress && !pinnedSpenders.includes(unsigned.approvalAddress.toLowerCase())) {
762
+ throw new Swap2ValidationError(`swap2: remote provider '${q.provider}' asks to approve ${unsigned.approvalAddress} on ${this.fromChain}, which the app did not pin as a spender (remoteProvider spenders) \u2014 refused`);
763
+ }
764
+ if (unsigned.typedData !== void 0) {
765
+ const chainId = this.fromChain.startsWith("eip155:") ? Number(this.fromChain.slice("eip155:".length)) : null;
766
+ assertTypedOrder(unsigned.typedData, unsigned.approvalAddress, { self: selfAddress, token: source.contract ?? void 0, amount: q.sourceAmount, chainId });
767
+ }
668
768
  if (unsigned.data !== void 0 && this.fromChain.startsWith("eip155:")) {
669
769
  const call = decodeCall({ to: unsigned.to, data: unsigned.data, value: executionValue });
670
- const named = call.transfer?.to ?? call.approval?.spender;
671
- const allowed = [selfAddress, q.depositAddress, unsigned.approvalAddress, unsigned.to].filter((a) => !!a).map((a) => a.toLowerCase());
672
- if (named && !allowed.includes(named.toLowerCase())) {
673
- throw new Swap2ValidationError(`swap2: quote calldata (${call.method}) names ${getAddress(named)}, which is not this account, the deposit address or the declared spender \u2014 refused`);
770
+ if (call.undecodable) throw new Swap2ValidationError("swap2: quote calldata is not hex bytes \u2014 refused");
771
+ if (call.approval && call.approval.kind !== "erc20") {
772
+ throw new Swap2ValidationError(`swap2: quote calldata is ${call.approval.kind === "operator" ? "an operator approval (setApprovalForAll)" : "a Permit2 approval"} \u2014 never a swap's main call; refused`);
773
+ }
774
+ if (call.transfer || call.approval) {
775
+ if (!source.contract || unsigned.to.toLowerCase() !== source.contract.toLowerCase()) {
776
+ throw new Swap2ValidationError(`swap2: quote calldata (${call.method}) moves ${getAddress(unsigned.to)}, not the source token \u2014 refused`);
777
+ }
778
+ if (call.method === "transferFrom") throw new Swap2ValidationError("swap2: quote calldata pulls the source token with transferFrom \u2014 refused");
779
+ const amount = call.transfer?.amount ?? call.approval?.amount ?? null;
780
+ if (amount === null || amount > BigInt(q.sourceAmount)) {
781
+ throw new Swap2ValidationError(`swap2: quote calldata (${call.method}) covers ${amount === null ? "an unlimited amount" : amount} of the source token, more than the swap's ${q.sourceAmount} \u2014 refused`);
782
+ }
783
+ const named = (call.transfer?.to ?? call.approval.spender).toLowerCase();
784
+ const allowed = engineTransfer ? [q.depositAddress] : remote ? [selfAddress, ...pinnedTargets, ...pinnedSpenders] : [selfAddress, q.depositAddress, unsigned.approvalAddress];
785
+ if (!allowed.filter((a) => !!a).map((a) => a.toLowerCase()).includes(named)) {
786
+ throw new Swap2ValidationError(`swap2: quote calldata (${call.method}) names ${getAddress(named)}, which is not this account, the deposit address or ${remote ? "a spender the app pinned" : "the declared spender"} \u2014 refused`);
787
+ }
788
+ } else if (remote && !pinnedTargets.includes(unsigned.to.toLowerCase())) {
789
+ throw new Swap2ValidationError(`swap2: remote provider '${q.provider}' calls ${getAddress(unsigned.to)} on ${this.fromChain}, which the app did not pin as a call target (remoteProvider callTargets) \u2014 refused`);
674
790
  }
675
791
  }
676
792
  const needsApproval = !!unsigned.approvalAddress && !!source.contract;
@@ -732,7 +848,7 @@ var BaseSwap2Protocol = class extends Swap2Protocol {
732
848
  ...unsigned.chainId !== void 0 ? { chainId: unsigned.chainId } : {},
733
849
  ...unsigned.gasLimit ? { gasLimit: BigInt(unsigned.gasLimit) } : {},
734
850
  ...unsigned.memo ? { memo: unsigned.memo, ...unsigned.memoEncoding ? { memoEncoding: unsigned.memoEncoding } : {} } : {},
735
- ...cellBody !== void 0 ? { cellBody: true } : {}
851
+ ...cellBody !== void 0 ? { cellBody: true, ...tonBody ? { cellBodyDecoded: tonBody } : {} } : {}
736
852
  }
737
853
  };
738
854
  const previous = carried?.reviewed;
@@ -997,6 +1113,7 @@ var BaseSwap2Protocol = class extends Swap2Protocol {
997
1113
  throw new Swap2ValidationError(`swap2: claim target ${unsigned.to} is not a contract '${name}' declares for ${unsigned.chain} \u2014 refused`);
998
1114
  }
999
1115
  const claimCall = decodeCall({ to: unsigned.to, data: unsigned.data, value: 0n });
1116
+ if (claimCall.undecodable) throw new Swap2ValidationError("swap2: claim calldata is not hex bytes \u2014 refused");
1000
1117
  if (claimCall.transfer || claimCall.approval) {
1001
1118
  throw new Swap2ValidationError(`swap2: claim calldata (${claimCall.method}) moves tokens or grants an allowance \u2014 refused`);
1002
1119
  }
@@ -1113,4 +1230,4 @@ export {
1113
1230
  swap2ChainId,
1114
1231
  BaseSwap2Protocol
1115
1232
  };
1116
- //# sourceMappingURL=chunk-IGTMUTNJ.js.map
1233
+ //# sourceMappingURL=chunk-5E5FWYZQ.js.map