@anywalletkit/protocol-swap2-core 0.6.1 → 0.8.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-KIAQXIBG.js → chunk-5FPDKWBG.js} +21 -2
- package/dist/chunk-5FPDKWBG.js.map +1 -0
- package/dist/{chunk-ZMWK3RQ4.js → chunk-6MTZNYKM.js} +40 -5
- package/dist/chunk-6MTZNYKM.js.map +1 -0
- package/dist/{chunk-IGTMUTNJ.js → chunk-CRCDWV3L.js} +130 -7
- package/dist/chunk-CRCDWV3L.js.map +1 -0
- package/dist/{chunk-JFGMF6FO.js → chunk-FHEU7E5A.js} +9 -3
- package/dist/chunk-FHEU7E5A.js.map +1 -0
- package/dist/index.cjs +256 -10
- 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 +38 -3
- package/dist/remote.cjs.map +1 -1
- package/dist/remote.d.cts +33 -1
- package/dist/remote.d.ts +33 -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 +187 -16
- 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 +210 -16
- 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 +49 -1
- package/dist/types.d.ts +49 -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 +19 -0
- package/dist/validate.cjs.map +1 -1
- package/dist/validate.d.cts +12 -2
- package/dist/validate.d.ts +12 -2
- package/dist/validate.js +3 -1
- package/package.json +5 -5
- 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/dist/swap2/base.d.cts
CHANGED
|
@@ -79,6 +79,12 @@ interface Swap2EngineConfig extends Swap2ProtocolConfig {
|
|
|
79
79
|
* SOL juggling, priority fees. Default 20 000 000 lamports (0.02 SOL).
|
|
80
80
|
*/
|
|
81
81
|
serializedNativeAllowance?: bigint | number | string;
|
|
82
|
+
/**
|
|
83
|
+
* The most nanoTON a TON cell-body deposit may attach as gas: on top of the input for a native source, the whole
|
|
84
|
+
* attached value for a jetton source (default 1 TON; live Omniston swaps attach 0.1–0.4). A build attaching more is
|
|
85
|
+
* refused (scan 2026-10-27 modules, F7).
|
|
86
|
+
*/
|
|
87
|
+
tonGasCeiling?: bigint | number | string;
|
|
82
88
|
/**
|
|
83
89
|
* Review gate: called once the live quote is built and every payload/value
|
|
84
90
|
* check has passed, BEFORE any approval or signature. It receives a copy of
|
|
@@ -100,6 +106,17 @@ interface Swap2EngineConfig extends Swap2ProtocolConfig {
|
|
|
100
106
|
*/
|
|
101
107
|
allowUnlistedTokens?: boolean;
|
|
102
108
|
}
|
|
109
|
+
/** What a TON cell body does, as the engine read it before signing (scan 2026-10-27 modules, F13): its op, and the jetton transfer it is. */
|
|
110
|
+
interface TonBodySummary {
|
|
111
|
+
op: number | null;
|
|
112
|
+
/** TEP-74 transfer from the account's own jetton wallet: amount, where it goes, where the excess returns, the TON forwarded. */
|
|
113
|
+
jettonTransfer?: {
|
|
114
|
+
amount: bigint;
|
|
115
|
+
destination: string | null;
|
|
116
|
+
responseDestination: string | null;
|
|
117
|
+
forwardTon: bigint;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
103
120
|
/** What `swap2()` is about to sign, handed to {@link Swap2EngineConfig.reviewExecution}. */
|
|
104
121
|
interface Swap2ExecutionPlan {
|
|
105
122
|
provider: string;
|
|
@@ -138,6 +155,7 @@ interface Swap2ExecutionPlan {
|
|
|
138
155
|
memo?: string;
|
|
139
156
|
memoEncoding?: "utf8" | "hex";
|
|
140
157
|
cellBody?: boolean;
|
|
158
|
+
cellBodyDecoded?: TonBodySummary;
|
|
141
159
|
} | {
|
|
142
160
|
kind: "token-transfer";
|
|
143
161
|
token: string;
|
|
@@ -290,4 +308,4 @@ declare abstract class BaseSwap2Protocol extends Swap2Protocol {
|
|
|
290
308
|
getSupportedTokens(options?: Swap2SupportedTokensOptions): Promise<Swap2SupportedToken[]>;
|
|
291
309
|
}
|
|
292
310
|
|
|
293
|
-
export { BaseSwap2Protocol, NATIVE_DECIMALS, SWAP2_CHAIN_ALIASES, SWAP2_ID_PREFIX, type SerializedExecutionExpectation, type Swap2Account, type Swap2EngineConfig, type Swap2ExecutionPhase, type Swap2ExecutionPhaseDetail, type Swap2ExecutionPlan, nativeDecimals, nativeUnits, normalizeTypedDomain, swap2ChainId };
|
|
311
|
+
export { BaseSwap2Protocol, NATIVE_DECIMALS, SWAP2_CHAIN_ALIASES, SWAP2_ID_PREFIX, type SerializedExecutionExpectation, type Swap2Account, type Swap2EngineConfig, type Swap2ExecutionPhase, type Swap2ExecutionPhaseDetail, type Swap2ExecutionPlan, type TonBodySummary, nativeDecimals, nativeUnits, normalizeTypedDomain, swap2ChainId };
|
package/dist/swap2/base.d.ts
CHANGED
|
@@ -79,6 +79,12 @@ interface Swap2EngineConfig extends Swap2ProtocolConfig {
|
|
|
79
79
|
* SOL juggling, priority fees. Default 20 000 000 lamports (0.02 SOL).
|
|
80
80
|
*/
|
|
81
81
|
serializedNativeAllowance?: bigint | number | string;
|
|
82
|
+
/**
|
|
83
|
+
* The most nanoTON a TON cell-body deposit may attach as gas: on top of the input for a native source, the whole
|
|
84
|
+
* attached value for a jetton source (default 1 TON; live Omniston swaps attach 0.1–0.4). A build attaching more is
|
|
85
|
+
* refused (scan 2026-10-27 modules, F7).
|
|
86
|
+
*/
|
|
87
|
+
tonGasCeiling?: bigint | number | string;
|
|
82
88
|
/**
|
|
83
89
|
* Review gate: called once the live quote is built and every payload/value
|
|
84
90
|
* check has passed, BEFORE any approval or signature. It receives a copy of
|
|
@@ -100,6 +106,17 @@ interface Swap2EngineConfig extends Swap2ProtocolConfig {
|
|
|
100
106
|
*/
|
|
101
107
|
allowUnlistedTokens?: boolean;
|
|
102
108
|
}
|
|
109
|
+
/** What a TON cell body does, as the engine read it before signing (scan 2026-10-27 modules, F13): its op, and the jetton transfer it is. */
|
|
110
|
+
interface TonBodySummary {
|
|
111
|
+
op: number | null;
|
|
112
|
+
/** TEP-74 transfer from the account's own jetton wallet: amount, where it goes, where the excess returns, the TON forwarded. */
|
|
113
|
+
jettonTransfer?: {
|
|
114
|
+
amount: bigint;
|
|
115
|
+
destination: string | null;
|
|
116
|
+
responseDestination: string | null;
|
|
117
|
+
forwardTon: bigint;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
103
120
|
/** What `swap2()` is about to sign, handed to {@link Swap2EngineConfig.reviewExecution}. */
|
|
104
121
|
interface Swap2ExecutionPlan {
|
|
105
122
|
provider: string;
|
|
@@ -138,6 +155,7 @@ interface Swap2ExecutionPlan {
|
|
|
138
155
|
memo?: string;
|
|
139
156
|
memoEncoding?: "utf8" | "hex";
|
|
140
157
|
cellBody?: boolean;
|
|
158
|
+
cellBodyDecoded?: TonBodySummary;
|
|
141
159
|
} | {
|
|
142
160
|
kind: "token-transfer";
|
|
143
161
|
token: string;
|
|
@@ -290,4 +308,4 @@ declare abstract class BaseSwap2Protocol extends Swap2Protocol {
|
|
|
290
308
|
getSupportedTokens(options?: Swap2SupportedTokensOptions): Promise<Swap2SupportedToken[]>;
|
|
291
309
|
}
|
|
292
310
|
|
|
293
|
-
export { BaseSwap2Protocol, NATIVE_DECIMALS, SWAP2_CHAIN_ALIASES, SWAP2_ID_PREFIX, type SerializedExecutionExpectation, type Swap2Account, type Swap2EngineConfig, type Swap2ExecutionPhase, type Swap2ExecutionPhaseDetail, type Swap2ExecutionPlan, nativeDecimals, nativeUnits, normalizeTypedDomain, swap2ChainId };
|
|
311
|
+
export { BaseSwap2Protocol, NATIVE_DECIMALS, SWAP2_CHAIN_ALIASES, SWAP2_ID_PREFIX, type SerializedExecutionExpectation, type Swap2Account, type Swap2EngineConfig, type Swap2ExecutionPhase, type Swap2ExecutionPhaseDetail, type Swap2ExecutionPlan, type TonBodySummary, nativeDecimals, nativeUnits, normalizeTypedDomain, swap2ChainId };
|
package/dist/swap2/base.js
CHANGED
package/dist/swap2/single.cjs
CHANGED
|
@@ -28,7 +28,58 @@ module.exports = __toCommonJS(single_exports);
|
|
|
28
28
|
var import_core = require("@anywalletkit/core");
|
|
29
29
|
var import_viem = require("viem");
|
|
30
30
|
var import_tx_preview_evm = require("@anywalletkit/tx-preview-evm");
|
|
31
|
+
var import_swap2_assets2 = require("@anywalletkit/swap2-assets");
|
|
32
|
+
|
|
33
|
+
// src/util.ts
|
|
31
34
|
var import_swap2_assets = require("@anywalletkit/swap2-assets");
|
|
35
|
+
var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
36
|
+
function base64Bytes(text) {
|
|
37
|
+
const s = text.replace(/-/g, "+").replace(/_/g, "/").replace(/=+$/, "");
|
|
38
|
+
if (!/^[A-Za-z0-9+/]*$/.test(s) || s.length % 4 === 1) return null;
|
|
39
|
+
const out = new Uint8Array(Math.floor(s.length * 3 / 4));
|
|
40
|
+
let bits = 0, acc = 0, n = 0;
|
|
41
|
+
for (const ch of s) {
|
|
42
|
+
acc = acc << 6 | B64.indexOf(ch);
|
|
43
|
+
bits += 6;
|
|
44
|
+
if (bits >= 8) {
|
|
45
|
+
bits -= 8;
|
|
46
|
+
out[n++] = acc >> bits & 255;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return out.subarray(0, n);
|
|
50
|
+
}
|
|
51
|
+
function crc16(bytes) {
|
|
52
|
+
let crc = 0;
|
|
53
|
+
for (const b of bytes) {
|
|
54
|
+
crc ^= b << 8;
|
|
55
|
+
for (let i = 0; i < 8; i++) crc = crc & 32768 ? (crc << 1 ^ 4129) & 65535 : crc << 1 & 65535;
|
|
56
|
+
}
|
|
57
|
+
return crc;
|
|
58
|
+
}
|
|
59
|
+
function tonRawAddress(address) {
|
|
60
|
+
if (address && typeof address === "object") {
|
|
61
|
+
const o = address;
|
|
62
|
+
if (typeof o.toRawString === "function") {
|
|
63
|
+
try {
|
|
64
|
+
return tonRawAddress(o.toRawString());
|
|
65
|
+
} catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (typeof o.workChain === "number" && o.hash instanceof Uint8Array && o.hash.length === 32) {
|
|
70
|
+
return `${o.workChain}:${Array.from(o.hash, (b) => b.toString(16).padStart(2, "0")).join("")}`;
|
|
71
|
+
}
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
if (typeof address !== "string") return null;
|
|
75
|
+
const raw = /^(-?[0-9]{1,3}):([0-9a-fA-F]{64})$/.exec(address.trim());
|
|
76
|
+
if (raw) return `${Number(raw[1])}:${raw[2].toLowerCase()}`;
|
|
77
|
+
if (!/^[A-Za-z0-9+/_-]{48}$/.test(address.trim())) return null;
|
|
78
|
+
const bytes = base64Bytes(address.trim());
|
|
79
|
+
if (!bytes || bytes.length !== 36 || crc16(bytes.subarray(0, 34)) !== (bytes[34] << 8 | bytes[35])) return null;
|
|
80
|
+
const wc = bytes[1] === 255 ? -1 : bytes[1];
|
|
81
|
+
return `${wc}:${Array.from(bytes.subarray(2, 34), (b) => b.toString(16).padStart(2, "0")).join("")}`;
|
|
82
|
+
}
|
|
32
83
|
|
|
33
84
|
// src/errors.ts
|
|
34
85
|
var Swap2Error = class extends Error {
|
|
@@ -196,7 +247,64 @@ function normalizeTypedDomain(domain) {
|
|
|
196
247
|
else if (typeof raw === "string" && /^0x[0-9a-fA-F]+$/.test(raw)) chainId = safeChainId(BigInt(raw));
|
|
197
248
|
return chainId === void 0 || !Number.isSafeInteger(chainId) ? domain : { ...domain, chainId };
|
|
198
249
|
}
|
|
250
|
+
function assertTypedOrder(typedData, approvalAddress, e) {
|
|
251
|
+
const fail = (why) => new Swap2ValidationError(`swap2: typed order refused \u2014 ${why}`);
|
|
252
|
+
let parsed;
|
|
253
|
+
try {
|
|
254
|
+
parsed = JSON.parse(typedData);
|
|
255
|
+
} catch {
|
|
256
|
+
throw fail("the typed data is not JSON");
|
|
257
|
+
}
|
|
258
|
+
if (!parsed || typeof parsed !== "object") throw fail("the typed data is not an object");
|
|
259
|
+
const td = parsed;
|
|
260
|
+
const primary = td.primaryType;
|
|
261
|
+
if (typeof primary !== "string" || !primary) throw fail("it names no primaryType");
|
|
262
|
+
if (/^permit/i.test(primary)) throw fail(`${primary} is a token approval, not an order`);
|
|
263
|
+
if (primary !== "Order") throw fail(`primaryType ${primary} is not an order this engine signs`);
|
|
264
|
+
const fields = td.types?.Order;
|
|
265
|
+
const has = (name, type) => Array.isArray(fields) && fields.some((f) => f?.name === name && f?.type === type);
|
|
266
|
+
if (!has("maker", "address") || !has("makerAsset", "address") || !has("makingAmount", "uint256")) throw fail("the Order type has no maker / makerAsset / makingAmount");
|
|
267
|
+
const verifying = td.domain?.verifyingContract;
|
|
268
|
+
if (!approvalAddress || typeof verifying !== "string" || verifying.toLowerCase() !== approvalAddress.toLowerCase()) {
|
|
269
|
+
throw fail(`verifyingContract ${String(verifying)} is not the settlement contract the swap approves`);
|
|
270
|
+
}
|
|
271
|
+
if (e.chainId !== null && normalizeTypedDomain(td.domain)?.chainId !== e.chainId) throw fail("the domain chainId is not the source chain");
|
|
272
|
+
const m = td.message ?? {};
|
|
273
|
+
if (String(m.maker ?? "").toLowerCase() !== e.self.toLowerCase()) throw fail("the maker is not this account");
|
|
274
|
+
if (!e.token || String(m.makerAsset ?? "").toLowerCase() !== e.token.toLowerCase()) throw fail("the makerAsset is not the source token");
|
|
275
|
+
const raw = m.makingAmount;
|
|
276
|
+
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;
|
|
277
|
+
if (making === null || making !== BigInt(e.amount)) throw fail(`makingAmount ${String(raw)} is not the swap's ${e.amount}`);
|
|
278
|
+
}
|
|
199
279
|
var DEFAULT_SERIALIZED_NATIVE_ALLOWANCE = 20000000n;
|
|
280
|
+
var DEFAULT_TON_GAS_CEILING = 1000000000n;
|
|
281
|
+
var JETTON_TRANSFER_OP = 260734629;
|
|
282
|
+
function readTonBody(cell) {
|
|
283
|
+
let s;
|
|
284
|
+
try {
|
|
285
|
+
s = cell.beginParse();
|
|
286
|
+
} catch {
|
|
287
|
+
return { op: null };
|
|
288
|
+
}
|
|
289
|
+
let op;
|
|
290
|
+
try {
|
|
291
|
+
op = s.loadUint(32);
|
|
292
|
+
} catch {
|
|
293
|
+
return { op: null };
|
|
294
|
+
}
|
|
295
|
+
if (op !== JETTON_TRANSFER_OP) return { op };
|
|
296
|
+
try {
|
|
297
|
+
s.loadUintBig(64);
|
|
298
|
+
const amount = s.loadCoins();
|
|
299
|
+
const destination = tonRawAddress(s.loadAddress());
|
|
300
|
+
const response = s.loadMaybeAddress();
|
|
301
|
+
s.loadMaybeRef();
|
|
302
|
+
const forwardTon = s.loadCoins();
|
|
303
|
+
return { op, jettonTransfer: { amount, destination, responseDestination: response == null ? null : tonRawAddress(response), forwardTon } };
|
|
304
|
+
} catch {
|
|
305
|
+
return { op };
|
|
306
|
+
}
|
|
307
|
+
}
|
|
200
308
|
function reviewedIdentity(plan) {
|
|
201
309
|
const { feeUsd: _fee, etaSeconds: _eta, validUntilMs: _valid, approvals: _approvals, destAmount: _dest, requoted: _requoted, ...pinned } = plan;
|
|
202
310
|
return JSON.stringify(pinned, (_k, v) => typeof v === "bigint" ? v.toString() : v);
|
|
@@ -277,17 +385,17 @@ function nonEmptyRef(ref, side) {
|
|
|
277
385
|
}
|
|
278
386
|
function toSymbol(caip2, ref) {
|
|
279
387
|
nonEmptyRef(ref, "source");
|
|
280
|
-
const byToken = ref.startsWith("0x") || ref.length > 20 ? (0,
|
|
388
|
+
const byToken = ref.startsWith("0x") || ref.length > 20 ? (0, import_swap2_assets2.ccAssetByToken)(caip2, ref) : void 0;
|
|
281
389
|
if (byToken) return byToken.symbol;
|
|
282
|
-
if ((0,
|
|
390
|
+
if ((0, import_swap2_assets2.ccAsset)(caip2, ref)) return ref;
|
|
283
391
|
return ref;
|
|
284
392
|
}
|
|
285
393
|
function toDestAddress(caip2, ref, allowUnlisted = false) {
|
|
286
394
|
nonEmptyRef(ref, "destination");
|
|
287
395
|
if (ref.startsWith("0x")) return ref;
|
|
288
|
-
const asset = (0,
|
|
396
|
+
const asset = (0, import_swap2_assets2.ccAssetByToken)(caip2, ref) ?? (0, import_swap2_assets2.ccAsset)(caip2, ref);
|
|
289
397
|
if (!asset) {
|
|
290
|
-
if (allowUnlisted && (0,
|
|
398
|
+
if (allowUnlisted && (0, import_swap2_assets2.isTokenAddress)(caip2, ref)) return ref;
|
|
291
399
|
throw new Error(`swap2: unknown destination token '${ref}' on ${caip2}`);
|
|
292
400
|
}
|
|
293
401
|
return asset.contract ?? ZERO;
|
|
@@ -416,7 +524,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
416
524
|
/** The source asset the options name: the catalog's, or (allowUnlistedTokens) an unlisted one with its decimals. */
|
|
417
525
|
sourceAsset(options) {
|
|
418
526
|
const decimals = options.fromTokenDecimals;
|
|
419
|
-
return (0,
|
|
527
|
+
return (0, import_swap2_assets2.resolveAsset)(this.fromChain, toSymbol(this.fromChain, options.fromToken), {
|
|
420
528
|
allowUnlisted: this.unlistedAllowed(),
|
|
421
529
|
...decimals !== void 0 ? { decimals } : {}
|
|
422
530
|
});
|
|
@@ -430,7 +538,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
430
538
|
async prepareOptions(options) {
|
|
431
539
|
if (!this.unlistedAllowed()) return options;
|
|
432
540
|
const ref = toSymbol(this.fromChain, options.fromToken);
|
|
433
|
-
if ((0,
|
|
541
|
+
if ((0, import_swap2_assets2.resolveAsset)(this.fromChain, ref) || !(0, import_swap2_assets2.isTokenAddress)(this.fromChain, ref)) return options;
|
|
434
542
|
const key = ref.startsWith("0x") ? ref.toLowerCase() : ref;
|
|
435
543
|
let decimals = this.unlistedDecimals.get(key);
|
|
436
544
|
if (decimals === void 0) {
|
|
@@ -461,8 +569,8 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
461
569
|
const source = this.sourceAsset(options);
|
|
462
570
|
const params = {
|
|
463
571
|
sourceChain: this.fromChain,
|
|
464
|
-
sourceToken: (0,
|
|
465
|
-
...(0,
|
|
572
|
+
sourceToken: (0, import_swap2_assets2.isUnlisted)(source) ? source.contract : toSymbol(this.fromChain, options.fromToken),
|
|
573
|
+
...(0, import_swap2_assets2.isUnlisted)(source) ? { sourceDecimals: source.decimals } : {},
|
|
466
574
|
destChain,
|
|
467
575
|
destToken: toDestAddress(destChain, options.toToken, this.unlistedAllowed()),
|
|
468
576
|
amountIn: amountIn.toString(),
|
|
@@ -656,12 +764,15 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
656
764
|
}
|
|
657
765
|
}
|
|
658
766
|
const executionModels = [unsigned.data !== void 0, unsigned.bodyBoc !== void 0, unsigned.typedData !== void 0].filter(Boolean).length;
|
|
659
|
-
if (executionModels > 1 || unsigned.memo !== void 0 && (unsigned.bodyBoc !== void 0 || unsigned.typedData !== void 0)) {
|
|
767
|
+
if (executionModels > 1 || unsigned.memo !== void 0 && (unsigned.bodyBoc !== void 0 || unsigned.typedData !== void 0) || unsigned.orderExtension !== void 0 && unsigned.typedData === void 0) {
|
|
660
768
|
throw new Error("swap2: provider returned ambiguous execution payloads");
|
|
661
769
|
}
|
|
662
770
|
if (unsigned.approvalAddress && !unsigned.data && !unsigned.typedData) {
|
|
663
771
|
throw new Error("swap2: approval is only valid for calldata or typed-order execution");
|
|
664
772
|
}
|
|
773
|
+
if (provider.remote === true && provider.depositAddressRoutes !== true && unsigned.data === void 0 && unsigned.bodyBoc === void 0 && unsigned.typedData === void 0 && !serialized) {
|
|
774
|
+
throw new Swap2ValidationError(`swap2: remote provider '${q.provider}' sends the input to ${unsigned.to}, an address its backend named, and the app did not allow deposit-address routes for it (remoteProvider depositAddressRoutes) \u2014 refused`);
|
|
775
|
+
}
|
|
665
776
|
const tronSource = this.fromChain.startsWith("tron:");
|
|
666
777
|
const nearSource = this.fromChain.startsWith("near:");
|
|
667
778
|
if (unsigned.memo && !this.fromChain.startsWith("ton:") && !tronSource && !nearSource) {
|
|
@@ -689,10 +800,12 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
689
800
|
throw new Error(`swap2: provider transaction chain ${unsigned.chainId} does not match ${this.fromChain}`);
|
|
690
801
|
}
|
|
691
802
|
let tokenId;
|
|
803
|
+
let engineTransfer = false;
|
|
692
804
|
if (!unsigned.data && !unsigned.bodyBoc && !unsigned.typedData && !serialized) {
|
|
693
805
|
const src = this.sourceAsset(executionOptions);
|
|
694
806
|
if (src?.contract) {
|
|
695
807
|
if (this.fromChain.startsWith("eip155:")) {
|
|
808
|
+
engineTransfer = true;
|
|
696
809
|
unsigned = {
|
|
697
810
|
...unsigned,
|
|
698
811
|
to: src.contract,
|
|
@@ -731,12 +844,69 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
731
844
|
} else if (!unsigned.typedData && !unsigned.bodyBoc && executionValue !== BigInt(q.sourceAmount) + declaredNativeFee(q, this.fromChain)) {
|
|
732
845
|
throw new Error("swap2: provider execution amount does not match the validated quote");
|
|
733
846
|
}
|
|
847
|
+
let tonBody;
|
|
848
|
+
if (cellBody !== void 0) {
|
|
849
|
+
const refuse = (why) => {
|
|
850
|
+
throw new Swap2ValidationError(`swap2: TON cell-body build refused \u2014 ${why}`);
|
|
851
|
+
};
|
|
852
|
+
const raw = this._config.tonGasCeiling;
|
|
853
|
+
const ceiling = raw === void 0 ? DEFAULT_TON_GAS_CEILING : BigInt(raw);
|
|
854
|
+
if (ceiling < 0n) throw new Swap2ConfigurationError("swap2: tonGasCeiling must be non-negative");
|
|
855
|
+
if (provider.remote === true && provider.verifiesBuilds !== true) refuse("a remote provider's cell body needs the app's own validator (remoteProvider verifyBuild)");
|
|
856
|
+
tonBody = readTonBody(cellBody);
|
|
857
|
+
const input = BigInt(q.sourceAmount);
|
|
858
|
+
if (source.contract) {
|
|
859
|
+
if (executionValue > ceiling) refuse(`it attaches ${executionValue} nanoTON to a jetton transfer, more than the ${ceiling} gas ceiling`);
|
|
860
|
+
const t = tonBody.jettonTransfer;
|
|
861
|
+
if (!t) refuse("a jetton-source body must be a jetton transfer");
|
|
862
|
+
if (t.amount !== input) refuse(`the jetton transfer moves ${t.amount}, not the swap's ${input}`);
|
|
863
|
+
if (!t.responseDestination || t.responseDestination !== tonRawAddress(selfAddress)) refuse("the jetton transfer returns its excess to another wallet");
|
|
864
|
+
if (t.forwardTon >= executionValue) refuse("the jetton transfer forwards more TON than the message carries");
|
|
865
|
+
const own = typeof signer.getJettonWalletAddress === "function" ? tonRawAddress(await signer.getJettonWalletAddress(source.contract)) : null;
|
|
866
|
+
if (!own) refuse("the account cannot name its own jetton wallet (getJettonWalletAddress), so the target cannot be checked");
|
|
867
|
+
if (tonRawAddress(unsigned.to) !== own) refuse(`it is sent to ${unsigned.to}, not this account's ${source.symbol ?? "source"} jetton wallet`);
|
|
868
|
+
} else {
|
|
869
|
+
if (executionValue < input || executionValue - input > ceiling) refuse(`it attaches ${executionValue} nanoTON for a ${input} input, more than the ${ceiling} gas ceiling on top`);
|
|
870
|
+
const pinned = provider.remote === true ? [...provider.callTargets?.(this.fromChain) ?? [], ...q.depositAddress ? [q.depositAddress] : []].map(tonRawAddress).filter((a) => !!a) : [];
|
|
871
|
+
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)`);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
const remote = provider.remote === true;
|
|
875
|
+
const pins = (list) => (list ?? []).filter((a) => typeof a === "string" && /^0x[0-9a-fA-F]{40}$/.test(a)).map((a) => a.toLowerCase());
|
|
876
|
+
const pinnedTargets = remote ? pins(provider.callTargets?.(this.fromChain)) : [];
|
|
877
|
+
const pinnedSpenders = remote ? pins(provider.spenders?.(this.fromChain)) : [];
|
|
878
|
+
if (remote && unsigned.approvalAddress && !pinnedSpenders.includes(unsigned.approvalAddress.toLowerCase())) {
|
|
879
|
+
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`);
|
|
880
|
+
}
|
|
881
|
+
if (unsigned.typedData !== void 0) {
|
|
882
|
+
if (remote && provider.verifiesBuilds !== true) {
|
|
883
|
+
throw new Swap2ValidationError(`swap2: remote provider '${q.provider}' asks to sign a typed order, which needs the app's own validator (remoteProvider verifyBuild) \u2014 refused`);
|
|
884
|
+
}
|
|
885
|
+
const chainId = this.fromChain.startsWith("eip155:") ? Number(this.fromChain.slice("eip155:".length)) : null;
|
|
886
|
+
assertTypedOrder(unsigned.typedData, unsigned.approvalAddress, { self: selfAddress, token: source.contract ?? void 0, amount: q.sourceAmount, chainId });
|
|
887
|
+
}
|
|
734
888
|
if (unsigned.data !== void 0 && this.fromChain.startsWith("eip155:")) {
|
|
735
889
|
const call = (0, import_tx_preview_evm.decodeCall)({ to: unsigned.to, data: unsigned.data, value: executionValue });
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
890
|
+
if (call.undecodable) throw new Swap2ValidationError("swap2: quote calldata is not hex bytes \u2014 refused");
|
|
891
|
+
if (call.approval && call.approval.kind !== "erc20") {
|
|
892
|
+
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`);
|
|
893
|
+
}
|
|
894
|
+
if (call.transfer || call.approval) {
|
|
895
|
+
if (!source.contract || unsigned.to.toLowerCase() !== source.contract.toLowerCase()) {
|
|
896
|
+
throw new Swap2ValidationError(`swap2: quote calldata (${call.method}) moves ${(0, import_viem.getAddress)(unsigned.to)}, not the source token \u2014 refused`);
|
|
897
|
+
}
|
|
898
|
+
if (call.method === "transferFrom") throw new Swap2ValidationError("swap2: quote calldata pulls the source token with transferFrom \u2014 refused");
|
|
899
|
+
const amount = call.transfer?.amount ?? call.approval?.amount ?? null;
|
|
900
|
+
if (amount === null || amount > BigInt(q.sourceAmount)) {
|
|
901
|
+
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`);
|
|
902
|
+
}
|
|
903
|
+
const named = (call.transfer?.to ?? call.approval.spender).toLowerCase();
|
|
904
|
+
const allowed = engineTransfer ? [q.depositAddress] : remote ? [selfAddress, ...pinnedTargets, ...pinnedSpenders] : [selfAddress, q.depositAddress, unsigned.approvalAddress];
|
|
905
|
+
if (!allowed.filter((a) => !!a).map((a) => a.toLowerCase()).includes(named)) {
|
|
906
|
+
throw new Swap2ValidationError(`swap2: quote calldata (${call.method}) names ${(0, import_viem.getAddress)(named)}, which is not this account, the deposit address or ${remote ? "a spender the app pinned" : "the declared spender"} \u2014 refused`);
|
|
907
|
+
}
|
|
908
|
+
} else if (remote && !pinnedTargets.includes(unsigned.to.toLowerCase())) {
|
|
909
|
+
throw new Swap2ValidationError(`swap2: remote provider '${q.provider}' calls ${(0, import_viem.getAddress)(unsigned.to)} on ${this.fromChain}, which the app did not pin as a call target (remoteProvider callTargets) \u2014 refused`);
|
|
740
910
|
}
|
|
741
911
|
}
|
|
742
912
|
const needsApproval = !!unsigned.approvalAddress && !!source.contract;
|
|
@@ -798,7 +968,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
798
968
|
...unsigned.chainId !== void 0 ? { chainId: unsigned.chainId } : {},
|
|
799
969
|
...unsigned.gasLimit ? { gasLimit: BigInt(unsigned.gasLimit) } : {},
|
|
800
970
|
...unsigned.memo ? { memo: unsigned.memo, ...unsigned.memoEncoding ? { memoEncoding: unsigned.memoEncoding } : {} } : {},
|
|
801
|
-
...cellBody !== void 0 ? { cellBody: true } : {}
|
|
971
|
+
...cellBody !== void 0 ? { cellBody: true, ...tonBody ? { cellBodyDecoded: tonBody } : {} } : {}
|
|
802
972
|
}
|
|
803
973
|
};
|
|
804
974
|
const previous = carried?.reviewed;
|
|
@@ -1063,6 +1233,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
1063
1233
|
throw new Swap2ValidationError(`swap2: claim target ${unsigned.to} is not a contract '${name}' declares for ${unsigned.chain} \u2014 refused`);
|
|
1064
1234
|
}
|
|
1065
1235
|
const claimCall = (0, import_tx_preview_evm.decodeCall)({ to: unsigned.to, data: unsigned.data, value: 0n });
|
|
1236
|
+
if (claimCall.undecodable) throw new Swap2ValidationError("swap2: claim calldata is not hex bytes \u2014 refused");
|
|
1066
1237
|
if (claimCall.transfer || claimCall.approval) {
|
|
1067
1238
|
throw new Swap2ValidationError(`swap2: claim calldata (${claimCall.method}) moves tokens or grants an allowance \u2014 refused`);
|
|
1068
1239
|
}
|
|
@@ -1123,7 +1294,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
1123
1294
|
/** Source chains this protocol can swap from. */
|
|
1124
1295
|
async getSupportedChains() {
|
|
1125
1296
|
if (!this.protocolEnabled()) return [];
|
|
1126
|
-
return
|
|
1297
|
+
return import_swap2_assets2.CC_SOURCE_CHAINS.filter((c) => this.chainSupported(c.caip2)).map((c) => ({
|
|
1127
1298
|
id: c.caip2,
|
|
1128
1299
|
name: c.label,
|
|
1129
1300
|
type: chainType(c.caip2),
|
|
@@ -1136,7 +1307,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
1136
1307
|
const wantTo = options?.toChain != null ? toCaip2(options.toChain, "") : void 0;
|
|
1137
1308
|
if (!this.protocolEnabled() || wantFrom && wantTo && !this.pairSupported(wantFrom, wantTo)) return [];
|
|
1138
1309
|
const out = [];
|
|
1139
|
-
for (const c of
|
|
1310
|
+
for (const c of import_swap2_assets2.CC_SOURCE_CHAINS) {
|
|
1140
1311
|
if (!this.chainSupported(c.caip2)) continue;
|
|
1141
1312
|
if ((wantFrom || wantTo) && c.caip2 !== wantFrom && c.caip2 !== wantTo) continue;
|
|
1142
1313
|
for (const a of c.assets) {
|
|
@@ -1207,6 +1378,24 @@ function validQuote(q, params, provider) {
|
|
|
1207
1378
|
return false;
|
|
1208
1379
|
}
|
|
1209
1380
|
}
|
|
1381
|
+
function remotePinRefusal(provider, q, chain) {
|
|
1382
|
+
if (provider.remote !== true) return null;
|
|
1383
|
+
const tx = q.txRequest;
|
|
1384
|
+
const plain = tx ? tx.data === void 0 && tx.serializedTx === void 0 : typeof q.depositAddress === "string" && q.depositAddress !== "";
|
|
1385
|
+
if (plain && provider.depositAddressRoutes !== true) {
|
|
1386
|
+
return `${provider.name}: the route pays ${tx?.to || q.depositAddress}, an address the backend named, and the app did not allow deposit-address routes for this provider (remoteProvider depositAddressRoutes)`;
|
|
1387
|
+
}
|
|
1388
|
+
if (!chain.startsWith("eip155:")) return null;
|
|
1389
|
+
if (!tx || tx.serializedTx !== void 0) return null;
|
|
1390
|
+
const pinned = (list, address) => (list ?? []).some((a) => typeof a === "string" && a.toLowerCase() === address.toLowerCase());
|
|
1391
|
+
if (tx.approvalAddress && !pinned(provider.spenders?.(chain), tx.approvalAddress)) {
|
|
1392
|
+
return `${provider.name}: the route approves ${tx.approvalAddress}, which the app did not pin as a spender on ${chain} (remoteProvider spenders)`;
|
|
1393
|
+
}
|
|
1394
|
+
if (tx.data !== void 0 && tx.to && !pinned(provider.callTargets?.(chain), tx.to)) {
|
|
1395
|
+
return `${provider.name}: the route calls ${tx.to}, which the app did not pin as a call target on ${chain} (remoteProvider callTargets)`;
|
|
1396
|
+
}
|
|
1397
|
+
return null;
|
|
1398
|
+
}
|
|
1210
1399
|
|
|
1211
1400
|
// src/swap2/single.ts
|
|
1212
1401
|
var SingleProviderSwap2 = class extends BaseSwap2Protocol {
|
|
@@ -1234,6 +1423,11 @@ var SingleProviderSwap2 = class extends BaseSwap2Protocol {
|
|
|
1234
1423
|
this.lastRefusal ||= `${name}: rejected by validation (minDest=${quote.minDestAmount} vs floor=${snapshot.minAmountOut ?? "-"})`;
|
|
1235
1424
|
return null;
|
|
1236
1425
|
}
|
|
1426
|
+
const unpinned = remotePinRefusal(this.provider, quote, snapshot.sourceChain);
|
|
1427
|
+
if (unpinned) {
|
|
1428
|
+
this.lastRefusal ||= unpinned;
|
|
1429
|
+
return null;
|
|
1430
|
+
}
|
|
1237
1431
|
return { ...quote, provider: name };
|
|
1238
1432
|
} catch (err) {
|
|
1239
1433
|
this.lastRefusal ||= `${name}: ${(err instanceof Error ? err.message : String(err)).slice(0, 160)}`;
|