@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.
- package/README.md +1 -1
- package/dist/chunk-2KC734UX.js +117 -0
- package/dist/chunk-2KC734UX.js.map +1 -0
- package/dist/{chunk-IGTMUTNJ.js → chunk-5E5FWYZQ.js} +123 -6
- package/dist/chunk-5E5FWYZQ.js.map +1 -0
- package/dist/{chunk-ZMWK3RQ4.js → chunk-CT2REW2V.js} +39 -5
- package/dist/chunk-CT2REW2V.js.map +1 -0
- package/dist/{chunk-JFGMF6FO.js → chunk-U653ZREM.js} +9 -3
- package/dist/chunk-U653ZREM.js.map +1 -0
- package/dist/{chunk-KIAQXIBG.js → chunk-WBYCNN6S.js} +16 -2
- package/dist/chunk-WBYCNN6S.js.map +1 -0
- package/dist/index.cjs +243 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +10 -6
- package/dist/remote.cjs +37 -3
- package/dist/remote.cjs.map +1 -1
- package/dist/remote.d.cts +26 -1
- package/dist/remote.d.ts +26 -1
- package/dist/remote.js +2 -2
- package/dist/server.cjs +23 -4
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +6 -5
- package/dist/server.js.map +1 -1
- package/dist/swap2/base.cjs +180 -15
- package/dist/swap2/base.cjs.map +1 -1
- package/dist/swap2/base.d.cts +19 -1
- package/dist/swap2/base.d.ts +19 -1
- package/dist/swap2/base.js +2 -1
- package/dist/swap2/single.cjs +198 -15
- package/dist/swap2/single.cjs.map +1 -1
- package/dist/swap2/single.js +4 -3
- package/dist/types.cjs.map +1 -1
- package/dist/types.d.cts +32 -1
- package/dist/types.d.ts +32 -1
- package/dist/util.cjs +70 -2
- package/dist/util.cjs.map +1 -1
- package/dist/util.d.cts +13 -2
- package/dist/util.d.ts +13 -2
- package/dist/util.js +7 -3
- package/dist/validate.cjs +14 -0
- package/dist/validate.cjs.map +1 -1
- package/dist/validate.d.cts +10 -2
- package/dist/validate.d.ts +10 -2
- package/dist/validate.js +3 -1
- package/package.json +4 -4
- package/dist/chunk-IGTMUTNJ.js.map +0 -1
- package/dist/chunk-JFGMF6FO.js.map +0 -1
- package/dist/chunk-KIAQXIBG.js.map +0 -1
- package/dist/chunk-MNJTMSFN.js +0 -49
- package/dist/chunk-MNJTMSFN.js.map +0 -1
- 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.
|
|
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":"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","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
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
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-
|
|
1233
|
+
//# sourceMappingURL=chunk-5E5FWYZQ.js.map
|