@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/index.cjs
CHANGED
|
@@ -51,12 +51,14 @@ __export(src_exports, {
|
|
|
51
51
|
nativeUnits: () => nativeUnits,
|
|
52
52
|
ownerAssets: () => ownerAssets,
|
|
53
53
|
redactUrls: () => redactUrls,
|
|
54
|
+
remotePinRefusal: () => remotePinRefusal,
|
|
54
55
|
remoteProvider: () => remoteProvider,
|
|
55
56
|
shortAddress: () => shortAddress,
|
|
56
57
|
slippageClassOf: () => slippageClassOf,
|
|
57
58
|
sourceAssetOf: () => sourceAssetOf,
|
|
58
59
|
suggestSlippage: () => suggestSlippage,
|
|
59
60
|
swap2ChainId: () => swap2ChainId,
|
|
61
|
+
tonRawAddress: () => tonRawAddress,
|
|
60
62
|
validQuote: () => validQuote
|
|
61
63
|
});
|
|
62
64
|
module.exports = __toCommonJS(src_exports);
|
|
@@ -157,6 +159,24 @@ function validQuote(q, params, provider) {
|
|
|
157
159
|
return false;
|
|
158
160
|
}
|
|
159
161
|
}
|
|
162
|
+
function remotePinRefusal(provider, q, chain) {
|
|
163
|
+
if (provider.remote !== true) return null;
|
|
164
|
+
const tx = q.txRequest;
|
|
165
|
+
const plain = tx ? tx.data === void 0 && tx.serializedTx === void 0 : typeof q.depositAddress === "string" && q.depositAddress !== "";
|
|
166
|
+
if (plain && provider.depositAddressRoutes !== true) {
|
|
167
|
+
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)`;
|
|
168
|
+
}
|
|
169
|
+
if (!chain.startsWith("eip155:")) return null;
|
|
170
|
+
if (!tx || tx.serializedTx !== void 0) return null;
|
|
171
|
+
const pinned = (list, address) => (list ?? []).some((a) => typeof a === "string" && a.toLowerCase() === address.toLowerCase());
|
|
172
|
+
if (tx.approvalAddress && !pinned(provider.spenders?.(chain), tx.approvalAddress)) {
|
|
173
|
+
return `${provider.name}: the route approves ${tx.approvalAddress}, which the app did not pin as a spender on ${chain} (remoteProvider spenders)`;
|
|
174
|
+
}
|
|
175
|
+
if (tx.data !== void 0 && tx.to && !pinned(provider.callTargets?.(chain), tx.to)) {
|
|
176
|
+
return `${provider.name}: the route calls ${tx.to}, which the app did not pin as a call target on ${chain} (remoteProvider callTargets)`;
|
|
177
|
+
}
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
160
180
|
|
|
161
181
|
// src/util.ts
|
|
162
182
|
var import_swap2_assets = require("@anywalletkit/swap2-assets");
|
|
@@ -189,20 +209,90 @@ function anySignal(signals) {
|
|
|
189
209
|
}
|
|
190
210
|
return ctrl.signal;
|
|
191
211
|
}
|
|
212
|
+
var SCHEME_MAX = 32;
|
|
213
|
+
var isLetter = (c) => c >= 97 && c <= 122 || c >= 65 && c <= 90;
|
|
214
|
+
var isSchemeChar = (c) => isLetter(c) || c >= 48 && c <= 57 || c === 43 || c === 45 || c === 46;
|
|
215
|
+
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;
|
|
192
216
|
function redactUrls(text) {
|
|
193
|
-
|
|
217
|
+
let out = "";
|
|
218
|
+
let done = 0;
|
|
219
|
+
for (let at = text.indexOf("://"); at !== -1; at = text.indexOf("://", at + 3)) {
|
|
220
|
+
let start = at;
|
|
221
|
+
while (start > done && at - start < SCHEME_MAX && isSchemeChar(text.charCodeAt(start - 1))) start--;
|
|
222
|
+
while (start < at && !isLetter(text.charCodeAt(start))) start++;
|
|
223
|
+
if (start === at) continue;
|
|
224
|
+
let end = at + 3;
|
|
225
|
+
while (end < text.length && !endsUrl(text.charCodeAt(end))) end++;
|
|
226
|
+
if (end === at + 3) continue;
|
|
227
|
+
out += `${text.slice(done, start)}<url>`;
|
|
228
|
+
done = end;
|
|
229
|
+
at = end - 3;
|
|
230
|
+
}
|
|
231
|
+
return done === 0 ? text : out + text.slice(done);
|
|
194
232
|
}
|
|
195
233
|
function sourceAssetOf(params) {
|
|
196
234
|
const decimals = params.sourceDecimals;
|
|
197
235
|
return (0, import_swap2_assets.resolveAsset)(params.sourceChain, params.sourceToken, decimals === void 0 ? {} : { allowUnlisted: true, decimals });
|
|
198
236
|
}
|
|
237
|
+
var B64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
238
|
+
function base64Bytes(text) {
|
|
239
|
+
const s = text.replace(/-/g, "+").replace(/_/g, "/").replace(/=+$/, "");
|
|
240
|
+
if (!/^[A-Za-z0-9+/]*$/.test(s) || s.length % 4 === 1) return null;
|
|
241
|
+
const out = new Uint8Array(Math.floor(s.length * 3 / 4));
|
|
242
|
+
let bits = 0, acc = 0, n = 0;
|
|
243
|
+
for (const ch of s) {
|
|
244
|
+
acc = acc << 6 | B64.indexOf(ch);
|
|
245
|
+
bits += 6;
|
|
246
|
+
if (bits >= 8) {
|
|
247
|
+
bits -= 8;
|
|
248
|
+
out[n++] = acc >> bits & 255;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return out.subarray(0, n);
|
|
252
|
+
}
|
|
253
|
+
function crc16(bytes) {
|
|
254
|
+
let crc = 0;
|
|
255
|
+
for (const b of bytes) {
|
|
256
|
+
crc ^= b << 8;
|
|
257
|
+
for (let i = 0; i < 8; i++) crc = crc & 32768 ? (crc << 1 ^ 4129) & 65535 : crc << 1 & 65535;
|
|
258
|
+
}
|
|
259
|
+
return crc;
|
|
260
|
+
}
|
|
261
|
+
function tonRawAddress(address) {
|
|
262
|
+
if (address && typeof address === "object") {
|
|
263
|
+
const o = address;
|
|
264
|
+
if (typeof o.toRawString === "function") {
|
|
265
|
+
try {
|
|
266
|
+
return tonRawAddress(o.toRawString());
|
|
267
|
+
} catch {
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
if (typeof o.workChain === "number" && o.hash instanceof Uint8Array && o.hash.length === 32) {
|
|
272
|
+
return `${o.workChain}:${Array.from(o.hash, (b) => b.toString(16).padStart(2, "0")).join("")}`;
|
|
273
|
+
}
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
if (typeof address !== "string") return null;
|
|
277
|
+
const raw = /^(-?[0-9]{1,3}):([0-9a-fA-F]{64})$/.exec(address.trim());
|
|
278
|
+
if (raw) return `${Number(raw[1])}:${raw[2].toLowerCase()}`;
|
|
279
|
+
if (!/^[A-Za-z0-9+/_-]{48}$/.test(address.trim())) return null;
|
|
280
|
+
const bytes = base64Bytes(address.trim());
|
|
281
|
+
if (!bytes || bytes.length !== 36 || crc16(bytes.subarray(0, 34)) !== (bytes[34] << 8 | bytes[35])) return null;
|
|
282
|
+
const wc = bytes[1] === 255 ? -1 : bytes[1];
|
|
283
|
+
return `${wc}:${Array.from(bytes.subarray(2, 34), (b) => b.toString(16).padStart(2, "0")).join("")}`;
|
|
284
|
+
}
|
|
199
285
|
|
|
200
286
|
// src/remote.ts
|
|
201
287
|
function remoteProvider(opts) {
|
|
202
288
|
const name = opts.name;
|
|
203
289
|
if (!/^[A-Za-z0-9_-]+$/.test(name)) throw new Error("swap2-remote: invalid provider name");
|
|
204
|
-
const baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
|
290
|
+
const baseUrl = opts.baseUrl.trim().replace(/\/+$/, "");
|
|
205
291
|
if (!baseUrl) throw new Error("swap2-remote: baseUrl is required");
|
|
292
|
+
const insecure = /^[a-z][a-z0-9+.-]*:/i.test(baseUrl) ? !/^https:\/\//i.test(baseUrl) : /^[\\/]{2}/.test(baseUrl);
|
|
293
|
+
if (insecure && opts.allowInsecureBaseUrl !== true) {
|
|
294
|
+
throw new Error("swap2-remote: baseUrl must be https:// or a same-origin path (allowInsecureBaseUrl for local development)");
|
|
295
|
+
}
|
|
206
296
|
const supportsRoute = opts.supportsRoute;
|
|
207
297
|
const enabled = opts.enabled;
|
|
208
298
|
const headers = Object.freeze({ ...opts.headers });
|
|
@@ -211,6 +301,12 @@ function remoteProvider(opts) {
|
|
|
211
301
|
if (!Number.isInteger(timeoutMs) || timeoutMs <= 0 || timeoutMs > 6e4) {
|
|
212
302
|
throw new Error("swap2-remote: timeoutMs must be an integer between 1 and 60000");
|
|
213
303
|
}
|
|
304
|
+
const requests = /* @__PURE__ */ new Map();
|
|
305
|
+
const remember = (intentId, params) => {
|
|
306
|
+
requests.delete(intentId);
|
|
307
|
+
requests.set(intentId, params);
|
|
308
|
+
if (requests.size > 64) requests.delete(requests.keys().next().value);
|
|
309
|
+
};
|
|
214
310
|
async function call(action, body, signal) {
|
|
215
311
|
const timeout = timeoutSignal(timeoutMs);
|
|
216
312
|
const requestSignal = signal ? anySignal([signal, timeout]) : timeout;
|
|
@@ -231,6 +327,9 @@ function remoteProvider(opts) {
|
|
|
231
327
|
}
|
|
232
328
|
return {
|
|
233
329
|
name,
|
|
330
|
+
remote: true,
|
|
331
|
+
...opts.verifyBuild ? { verifiesBuilds: true } : {},
|
|
332
|
+
...opts.depositAddressRoutes === true ? { depositAddressRoutes: true } : {},
|
|
234
333
|
// The browser side has no dark-flag env; the backend enforces its own
|
|
235
334
|
// flags — a disabled provider simply quotes null / rejects build.
|
|
236
335
|
enabled: () => enabled ? enabled() : true,
|
|
@@ -242,10 +341,30 @@ function remoteProvider(opts) {
|
|
|
242
341
|
if (out && typeof out === "object" && "noQuote" in out) {
|
|
243
342
|
throw new Error(String(out.noQuote).slice(0, 300));
|
|
244
343
|
}
|
|
344
|
+
if (out && typeof out === "object" && typeof out.intentId === "string") remember(out.intentId, params);
|
|
345
|
+
if (out && opts.verifyQuote) {
|
|
346
|
+
let refusal;
|
|
347
|
+
try {
|
|
348
|
+
refusal = opts.verifyQuote(params, out);
|
|
349
|
+
} catch (e) {
|
|
350
|
+
refusal = e instanceof Error ? e.message : String(e);
|
|
351
|
+
}
|
|
352
|
+
if (refusal) throw new Error(`${name}: the quote fails this app's own check \u2014 ${refusal.slice(0, 240)}`);
|
|
353
|
+
}
|
|
245
354
|
return out;
|
|
246
355
|
},
|
|
247
356
|
async buildDeposit(quote) {
|
|
248
|
-
|
|
357
|
+
const tx = await call("build", { quote });
|
|
358
|
+
if (opts.verifyBuild) {
|
|
359
|
+
let refusal;
|
|
360
|
+
try {
|
|
361
|
+
refusal = await opts.verifyBuild(quote, tx, requests.get(quote.intentId));
|
|
362
|
+
} catch (e) {
|
|
363
|
+
refusal = e instanceof Error ? e.message : String(e);
|
|
364
|
+
}
|
|
365
|
+
if (refusal) throw new Error(`${name}: the build fails this app's own check \u2014 ${refusal.slice(0, 240)}`);
|
|
366
|
+
}
|
|
367
|
+
return tx;
|
|
249
368
|
},
|
|
250
369
|
async preflightDeposit(quote, tx) {
|
|
251
370
|
await call("preflight", { quote, tx });
|
|
@@ -267,7 +386,9 @@ function remoteProvider(opts) {
|
|
|
267
386
|
...opts.supportsChain ? { supportsChain: opts.supportsChain.bind(opts) } : {},
|
|
268
387
|
...opts.supportsChainPair ? { supportsChainPair: opts.supportsChainPair.bind(opts) } : {},
|
|
269
388
|
...opts.supportsSameChain !== void 0 ? { supportsSameChain: opts.supportsSameChain } : {},
|
|
270
|
-
...opts.claimTargets ? { claimTargets: opts.claimTargets } : {}
|
|
389
|
+
...opts.claimTargets ? { claimTargets: opts.claimTargets } : {},
|
|
390
|
+
...opts.callTargets ? { callTargets: opts.callTargets } : {},
|
|
391
|
+
...opts.spenders ? { spenders: opts.spenders } : {}
|
|
271
392
|
};
|
|
272
393
|
}
|
|
273
394
|
|
|
@@ -383,7 +504,64 @@ function normalizeTypedDomain(domain) {
|
|
|
383
504
|
else if (typeof raw === "string" && /^0x[0-9a-fA-F]+$/.test(raw)) chainId = safeChainId(BigInt(raw));
|
|
384
505
|
return chainId === void 0 || !Number.isSafeInteger(chainId) ? domain : { ...domain, chainId };
|
|
385
506
|
}
|
|
507
|
+
function assertTypedOrder(typedData, approvalAddress, e) {
|
|
508
|
+
const fail = (why) => new Swap2ValidationError(`swap2: typed order refused \u2014 ${why}`);
|
|
509
|
+
let parsed;
|
|
510
|
+
try {
|
|
511
|
+
parsed = JSON.parse(typedData);
|
|
512
|
+
} catch {
|
|
513
|
+
throw fail("the typed data is not JSON");
|
|
514
|
+
}
|
|
515
|
+
if (!parsed || typeof parsed !== "object") throw fail("the typed data is not an object");
|
|
516
|
+
const td = parsed;
|
|
517
|
+
const primary = td.primaryType;
|
|
518
|
+
if (typeof primary !== "string" || !primary) throw fail("it names no primaryType");
|
|
519
|
+
if (/^permit/i.test(primary)) throw fail(`${primary} is a token approval, not an order`);
|
|
520
|
+
if (primary !== "Order") throw fail(`primaryType ${primary} is not an order this engine signs`);
|
|
521
|
+
const fields = td.types?.Order;
|
|
522
|
+
const has = (name, type) => Array.isArray(fields) && fields.some((f) => f?.name === name && f?.type === type);
|
|
523
|
+
if (!has("maker", "address") || !has("makerAsset", "address") || !has("makingAmount", "uint256")) throw fail("the Order type has no maker / makerAsset / makingAmount");
|
|
524
|
+
const verifying = td.domain?.verifyingContract;
|
|
525
|
+
if (!approvalAddress || typeof verifying !== "string" || verifying.toLowerCase() !== approvalAddress.toLowerCase()) {
|
|
526
|
+
throw fail(`verifyingContract ${String(verifying)} is not the settlement contract the swap approves`);
|
|
527
|
+
}
|
|
528
|
+
if (e.chainId !== null && normalizeTypedDomain(td.domain)?.chainId !== e.chainId) throw fail("the domain chainId is not the source chain");
|
|
529
|
+
const m = td.message ?? {};
|
|
530
|
+
if (String(m.maker ?? "").toLowerCase() !== e.self.toLowerCase()) throw fail("the maker is not this account");
|
|
531
|
+
if (!e.token || String(m.makerAsset ?? "").toLowerCase() !== e.token.toLowerCase()) throw fail("the makerAsset is not the source token");
|
|
532
|
+
const raw = m.makingAmount;
|
|
533
|
+
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;
|
|
534
|
+
if (making === null || making !== BigInt(e.amount)) throw fail(`makingAmount ${String(raw)} is not the swap's ${e.amount}`);
|
|
535
|
+
}
|
|
386
536
|
var DEFAULT_SERIALIZED_NATIVE_ALLOWANCE = 20000000n;
|
|
537
|
+
var DEFAULT_TON_GAS_CEILING = 1000000000n;
|
|
538
|
+
var JETTON_TRANSFER_OP = 260734629;
|
|
539
|
+
function readTonBody(cell) {
|
|
540
|
+
let s;
|
|
541
|
+
try {
|
|
542
|
+
s = cell.beginParse();
|
|
543
|
+
} catch {
|
|
544
|
+
return { op: null };
|
|
545
|
+
}
|
|
546
|
+
let op;
|
|
547
|
+
try {
|
|
548
|
+
op = s.loadUint(32);
|
|
549
|
+
} catch {
|
|
550
|
+
return { op: null };
|
|
551
|
+
}
|
|
552
|
+
if (op !== JETTON_TRANSFER_OP) return { op };
|
|
553
|
+
try {
|
|
554
|
+
s.loadUintBig(64);
|
|
555
|
+
const amount = s.loadCoins();
|
|
556
|
+
const destination = tonRawAddress(s.loadAddress());
|
|
557
|
+
const response = s.loadMaybeAddress();
|
|
558
|
+
s.loadMaybeRef();
|
|
559
|
+
const forwardTon = s.loadCoins();
|
|
560
|
+
return { op, jettonTransfer: { amount, destination, responseDestination: response == null ? null : tonRawAddress(response), forwardTon } };
|
|
561
|
+
} catch {
|
|
562
|
+
return { op };
|
|
563
|
+
}
|
|
564
|
+
}
|
|
387
565
|
function reviewedIdentity(plan) {
|
|
388
566
|
const { feeUsd: _fee, etaSeconds: _eta, validUntilMs: _valid, approvals: _approvals, destAmount: _dest, requoted: _requoted, ...pinned } = plan;
|
|
389
567
|
return JSON.stringify(pinned, (_k, v) => typeof v === "bigint" ? v.toString() : v);
|
|
@@ -843,12 +1021,15 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
843
1021
|
}
|
|
844
1022
|
}
|
|
845
1023
|
const executionModels = [unsigned.data !== void 0, unsigned.bodyBoc !== void 0, unsigned.typedData !== void 0].filter(Boolean).length;
|
|
846
|
-
if (executionModels > 1 || unsigned.memo !== void 0 && (unsigned.bodyBoc !== void 0 || unsigned.typedData !== void 0)) {
|
|
1024
|
+
if (executionModels > 1 || unsigned.memo !== void 0 && (unsigned.bodyBoc !== void 0 || unsigned.typedData !== void 0) || unsigned.orderExtension !== void 0 && unsigned.typedData === void 0) {
|
|
847
1025
|
throw new Error("swap2: provider returned ambiguous execution payloads");
|
|
848
1026
|
}
|
|
849
1027
|
if (unsigned.approvalAddress && !unsigned.data && !unsigned.typedData) {
|
|
850
1028
|
throw new Error("swap2: approval is only valid for calldata or typed-order execution");
|
|
851
1029
|
}
|
|
1030
|
+
if (provider.remote === true && provider.depositAddressRoutes !== true && unsigned.data === void 0 && unsigned.bodyBoc === void 0 && unsigned.typedData === void 0 && !serialized) {
|
|
1031
|
+
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`);
|
|
1032
|
+
}
|
|
852
1033
|
const tronSource = this.fromChain.startsWith("tron:");
|
|
853
1034
|
const nearSource = this.fromChain.startsWith("near:");
|
|
854
1035
|
if (unsigned.memo && !this.fromChain.startsWith("ton:") && !tronSource && !nearSource) {
|
|
@@ -876,10 +1057,12 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
876
1057
|
throw new Error(`swap2: provider transaction chain ${unsigned.chainId} does not match ${this.fromChain}`);
|
|
877
1058
|
}
|
|
878
1059
|
let tokenId;
|
|
1060
|
+
let engineTransfer = false;
|
|
879
1061
|
if (!unsigned.data && !unsigned.bodyBoc && !unsigned.typedData && !serialized) {
|
|
880
1062
|
const src = this.sourceAsset(executionOptions);
|
|
881
1063
|
if (src?.contract) {
|
|
882
1064
|
if (this.fromChain.startsWith("eip155:")) {
|
|
1065
|
+
engineTransfer = true;
|
|
883
1066
|
unsigned = {
|
|
884
1067
|
...unsigned,
|
|
885
1068
|
to: src.contract,
|
|
@@ -918,12 +1101,69 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
918
1101
|
} else if (!unsigned.typedData && !unsigned.bodyBoc && executionValue !== BigInt(q.sourceAmount) + declaredNativeFee(q, this.fromChain)) {
|
|
919
1102
|
throw new Error("swap2: provider execution amount does not match the validated quote");
|
|
920
1103
|
}
|
|
1104
|
+
let tonBody;
|
|
1105
|
+
if (cellBody !== void 0) {
|
|
1106
|
+
const refuse = (why) => {
|
|
1107
|
+
throw new Swap2ValidationError(`swap2: TON cell-body build refused \u2014 ${why}`);
|
|
1108
|
+
};
|
|
1109
|
+
const raw = this._config.tonGasCeiling;
|
|
1110
|
+
const ceiling = raw === void 0 ? DEFAULT_TON_GAS_CEILING : BigInt(raw);
|
|
1111
|
+
if (ceiling < 0n) throw new Swap2ConfigurationError("swap2: tonGasCeiling must be non-negative");
|
|
1112
|
+
if (provider.remote === true && provider.verifiesBuilds !== true) refuse("a remote provider's cell body needs the app's own validator (remoteProvider verifyBuild)");
|
|
1113
|
+
tonBody = readTonBody(cellBody);
|
|
1114
|
+
const input = BigInt(q.sourceAmount);
|
|
1115
|
+
if (source.contract) {
|
|
1116
|
+
if (executionValue > ceiling) refuse(`it attaches ${executionValue} nanoTON to a jetton transfer, more than the ${ceiling} gas ceiling`);
|
|
1117
|
+
const t = tonBody.jettonTransfer;
|
|
1118
|
+
if (!t) refuse("a jetton-source body must be a jetton transfer");
|
|
1119
|
+
if (t.amount !== input) refuse(`the jetton transfer moves ${t.amount}, not the swap's ${input}`);
|
|
1120
|
+
if (!t.responseDestination || t.responseDestination !== tonRawAddress(selfAddress)) refuse("the jetton transfer returns its excess to another wallet");
|
|
1121
|
+
if (t.forwardTon >= executionValue) refuse("the jetton transfer forwards more TON than the message carries");
|
|
1122
|
+
const own = typeof signer.getJettonWalletAddress === "function" ? tonRawAddress(await signer.getJettonWalletAddress(source.contract)) : null;
|
|
1123
|
+
if (!own) refuse("the account cannot name its own jetton wallet (getJettonWalletAddress), so the target cannot be checked");
|
|
1124
|
+
if (tonRawAddress(unsigned.to) !== own) refuse(`it is sent to ${unsigned.to}, not this account's ${source.symbol ?? "source"} jetton wallet`);
|
|
1125
|
+
} else {
|
|
1126
|
+
if (executionValue < input || executionValue - input > ceiling) refuse(`it attaches ${executionValue} nanoTON for a ${input} input, more than the ${ceiling} gas ceiling on top`);
|
|
1127
|
+
const pinned = provider.remote === true ? [...provider.callTargets?.(this.fromChain) ?? [], ...q.depositAddress ? [q.depositAddress] : []].map(tonRawAddress).filter((a) => !!a) : [];
|
|
1128
|
+
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)`);
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
const remote = provider.remote === true;
|
|
1132
|
+
const pins = (list) => (list ?? []).filter((a) => typeof a === "string" && /^0x[0-9a-fA-F]{40}$/.test(a)).map((a) => a.toLowerCase());
|
|
1133
|
+
const pinnedTargets = remote ? pins(provider.callTargets?.(this.fromChain)) : [];
|
|
1134
|
+
const pinnedSpenders = remote ? pins(provider.spenders?.(this.fromChain)) : [];
|
|
1135
|
+
if (remote && unsigned.approvalAddress && !pinnedSpenders.includes(unsigned.approvalAddress.toLowerCase())) {
|
|
1136
|
+
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`);
|
|
1137
|
+
}
|
|
1138
|
+
if (unsigned.typedData !== void 0) {
|
|
1139
|
+
if (remote && provider.verifiesBuilds !== true) {
|
|
1140
|
+
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`);
|
|
1141
|
+
}
|
|
1142
|
+
const chainId = this.fromChain.startsWith("eip155:") ? Number(this.fromChain.slice("eip155:".length)) : null;
|
|
1143
|
+
assertTypedOrder(unsigned.typedData, unsigned.approvalAddress, { self: selfAddress, token: source.contract ?? void 0, amount: q.sourceAmount, chainId });
|
|
1144
|
+
}
|
|
921
1145
|
if (unsigned.data !== void 0 && this.fromChain.startsWith("eip155:")) {
|
|
922
1146
|
const call = (0, import_tx_preview_evm.decodeCall)({ to: unsigned.to, data: unsigned.data, value: executionValue });
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
1147
|
+
if (call.undecodable) throw new Swap2ValidationError("swap2: quote calldata is not hex bytes \u2014 refused");
|
|
1148
|
+
if (call.approval && call.approval.kind !== "erc20") {
|
|
1149
|
+
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`);
|
|
1150
|
+
}
|
|
1151
|
+
if (call.transfer || call.approval) {
|
|
1152
|
+
if (!source.contract || unsigned.to.toLowerCase() !== source.contract.toLowerCase()) {
|
|
1153
|
+
throw new Swap2ValidationError(`swap2: quote calldata (${call.method}) moves ${(0, import_viem.getAddress)(unsigned.to)}, not the source token \u2014 refused`);
|
|
1154
|
+
}
|
|
1155
|
+
if (call.method === "transferFrom") throw new Swap2ValidationError("swap2: quote calldata pulls the source token with transferFrom \u2014 refused");
|
|
1156
|
+
const amount = call.transfer?.amount ?? call.approval?.amount ?? null;
|
|
1157
|
+
if (amount === null || amount > BigInt(q.sourceAmount)) {
|
|
1158
|
+
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`);
|
|
1159
|
+
}
|
|
1160
|
+
const named = (call.transfer?.to ?? call.approval.spender).toLowerCase();
|
|
1161
|
+
const allowed = engineTransfer ? [q.depositAddress] : remote ? [selfAddress, ...pinnedTargets, ...pinnedSpenders] : [selfAddress, q.depositAddress, unsigned.approvalAddress];
|
|
1162
|
+
if (!allowed.filter((a) => !!a).map((a) => a.toLowerCase()).includes(named)) {
|
|
1163
|
+
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`);
|
|
1164
|
+
}
|
|
1165
|
+
} else if (remote && !pinnedTargets.includes(unsigned.to.toLowerCase())) {
|
|
1166
|
+
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`);
|
|
927
1167
|
}
|
|
928
1168
|
}
|
|
929
1169
|
const needsApproval = !!unsigned.approvalAddress && !!source.contract;
|
|
@@ -985,7 +1225,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
985
1225
|
...unsigned.chainId !== void 0 ? { chainId: unsigned.chainId } : {},
|
|
986
1226
|
...unsigned.gasLimit ? { gasLimit: BigInt(unsigned.gasLimit) } : {},
|
|
987
1227
|
...unsigned.memo ? { memo: unsigned.memo, ...unsigned.memoEncoding ? { memoEncoding: unsigned.memoEncoding } : {} } : {},
|
|
988
|
-
...cellBody !== void 0 ? { cellBody: true } : {}
|
|
1228
|
+
...cellBody !== void 0 ? { cellBody: true, ...tonBody ? { cellBodyDecoded: tonBody } : {} } : {}
|
|
989
1229
|
}
|
|
990
1230
|
};
|
|
991
1231
|
const previous = carried?.reviewed;
|
|
@@ -1250,6 +1490,7 @@ var BaseSwap2Protocol = class extends import_core.Swap2Protocol {
|
|
|
1250
1490
|
throw new Swap2ValidationError(`swap2: claim target ${unsigned.to} is not a contract '${name}' declares for ${unsigned.chain} \u2014 refused`);
|
|
1251
1491
|
}
|
|
1252
1492
|
const claimCall = (0, import_tx_preview_evm.decodeCall)({ to: unsigned.to, data: unsigned.data, value: 0n });
|
|
1493
|
+
if (claimCall.undecodable) throw new Swap2ValidationError("swap2: claim calldata is not hex bytes \u2014 refused");
|
|
1253
1494
|
if (claimCall.transfer || claimCall.approval) {
|
|
1254
1495
|
throw new Swap2ValidationError(`swap2: claim calldata (${claimCall.method}) moves tokens or grants an allowance \u2014 refused`);
|
|
1255
1496
|
}
|
|
@@ -1382,6 +1623,11 @@ var SingleProviderSwap2 = class extends BaseSwap2Protocol {
|
|
|
1382
1623
|
this.lastRefusal ||= `${name}: rejected by validation (minDest=${quote.minDestAmount} vs floor=${snapshot.minAmountOut ?? "-"})`;
|
|
1383
1624
|
return null;
|
|
1384
1625
|
}
|
|
1626
|
+
const unpinned = remotePinRefusal(this.provider, quote, snapshot.sourceChain);
|
|
1627
|
+
if (unpinned) {
|
|
1628
|
+
this.lastRefusal ||= unpinned;
|
|
1629
|
+
return null;
|
|
1630
|
+
}
|
|
1385
1631
|
return { ...quote, provider: name };
|
|
1386
1632
|
} catch (err) {
|
|
1387
1633
|
this.lastRefusal ||= `${name}: ${(err instanceof Error ? err.message : String(err)).slice(0, 160)}`;
|