@anywalletkit/protocol-swap2-lifi 0.7.0 → 0.8.1
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 +38 -2
- package/dist/{chunk-HJRWHGMX.js → chunk-G26GE46T.js} +410 -115
- package/dist/chunk-G26GE46T.js.map +1 -0
- package/dist/{chunk-D42KNYLU.js → chunk-TCPUWYDL.js} +2 -2
- package/dist/index.cjs +409 -114
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/protocol.cjs +409 -114
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.js +2 -2
- package/dist/provider.cjs +409 -114
- package/dist/provider.cjs.map +1 -1
- package/dist/provider.d.cts +3 -1
- package/dist/provider.d.ts +3 -1
- package/dist/provider.js +1 -1
- package/package.json +9 -7
- package/dist/chunk-HJRWHGMX.js.map +0 -1
- /package/dist/{chunk-D42KNYLU.js.map → chunk-TCPUWYDL.js.map} +0 -0
package/dist/provider.cjs
CHANGED
|
@@ -74,6 +74,76 @@ function describeLifiActivity(tx, ctx, isDiamond) {
|
|
|
74
74
|
});
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
// src/solana-address.ts
|
|
78
|
+
var import_ed25519 = require("@noble/curves/ed25519.js");
|
|
79
|
+
var import_sha2 = require("@noble/hashes/sha2.js");
|
|
80
|
+
var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
81
|
+
var BASE58 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
82
|
+
var SOLANA_TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
83
|
+
var ASSOCIATED_TOKEN_PROGRAM = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
|
|
84
|
+
var PDA_MARKER = Uint8Array.from("ProgramDerivedAddress", (c) => c.charCodeAt(0));
|
|
85
|
+
function base58(bytes) {
|
|
86
|
+
let n = 0n;
|
|
87
|
+
for (const b of bytes) n = n << 8n | BigInt(b);
|
|
88
|
+
let out = "";
|
|
89
|
+
while (n > 0n) {
|
|
90
|
+
out = ALPHABET[Number(n % 58n)] + out;
|
|
91
|
+
n /= 58n;
|
|
92
|
+
}
|
|
93
|
+
for (const b of bytes) {
|
|
94
|
+
if (b !== 0) break;
|
|
95
|
+
out = `1${out}`;
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
function solanaKey(address) {
|
|
100
|
+
if (!BASE58.test(address)) return null;
|
|
101
|
+
let n = 0n;
|
|
102
|
+
for (const ch of address) n = n * 58n + BigInt(ALPHABET.indexOf(ch));
|
|
103
|
+
let zeros = 0;
|
|
104
|
+
while (address[zeros] === "1") zeros++;
|
|
105
|
+
const body = [];
|
|
106
|
+
while (n > 0n) {
|
|
107
|
+
body.unshift(Number(n & 0xffn));
|
|
108
|
+
n >>= 8n;
|
|
109
|
+
}
|
|
110
|
+
if (zeros + body.length !== 32) return null;
|
|
111
|
+
const out = new Uint8Array(32);
|
|
112
|
+
out.set(body, zeros);
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
var onCurve = (bytes) => {
|
|
116
|
+
try {
|
|
117
|
+
import_ed25519.ed25519.Point.fromBytes(bytes);
|
|
118
|
+
return true;
|
|
119
|
+
} catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
function programAddress(seeds, program) {
|
|
124
|
+
for (let bump = 255; bump >= 0; bump--) {
|
|
125
|
+
const parts = [...seeds, Uint8Array.of(bump), program, PDA_MARKER];
|
|
126
|
+
const buf = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
|
|
127
|
+
let at = 0;
|
|
128
|
+
for (const p of parts) {
|
|
129
|
+
buf.set(p, at);
|
|
130
|
+
at += p.length;
|
|
131
|
+
}
|
|
132
|
+
const hash = (0, import_sha2.sha256)(buf);
|
|
133
|
+
if (!onCurve(hash)) return hash;
|
|
134
|
+
}
|
|
135
|
+
throw new Error("no program address for these seeds");
|
|
136
|
+
}
|
|
137
|
+
function associatedTokenAccount(owner, mint, tokenProgram = solanaKey(SOLANA_TOKEN_PROGRAM)) {
|
|
138
|
+
return programAddress([owner, tokenProgram, mint], solanaKey(ASSOCIATED_TOKEN_PROGRAM));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// src/refusals.ts
|
|
142
|
+
var import_errors = require("@anywalletkit/protocol-swap2-core/errors");
|
|
143
|
+
var notes = (0, import_errors.refusalNotes)();
|
|
144
|
+
var note = notes.note;
|
|
145
|
+
var reasonOf = notes.reasonOf;
|
|
146
|
+
|
|
77
147
|
// src/calldata.ts
|
|
78
148
|
var GENERIC_SWAPS = {
|
|
79
149
|
"0x4630a0d8": { single: false },
|
|
@@ -293,18 +363,18 @@ function swapsAt(r, base, head) {
|
|
|
293
363
|
function checkSteps(steps, e, finalAsset) {
|
|
294
364
|
const first = steps[0];
|
|
295
365
|
const last = steps[steps.length - 1];
|
|
296
|
-
if (!same(first.sendingAssetId, e.fromToken)) return `spends ${first.sendingAssetId}, not ${e.fromToken}
|
|
297
|
-
if (first.fromAmount === 0n || first.fromAmount > e.amountIn) return `swaps ${first.fromAmount}, not the ${e.amountIn} spent
|
|
366
|
+
if (!same(first.sendingAssetId, e.fromToken)) return note(`spends ${first.sendingAssetId}, not ${e.fromToken}`, "policy_refused");
|
|
367
|
+
if (first.fromAmount === 0n || first.fromAmount > e.amountIn) return note(`swaps ${first.fromAmount}, not the ${e.amountIn} spent`, "policy_refused");
|
|
298
368
|
let deposited = 0n;
|
|
299
369
|
for (let i = 0; i < steps.length; i++) {
|
|
300
370
|
const step = steps[i];
|
|
301
|
-
if (i > 0 && !same(step.sendingAssetId, steps[i - 1].receivingAssetId)) return `step ${i} spends ${step.sendingAssetId}, which step ${i - 1} does not deliver
|
|
371
|
+
if (i > 0 && !same(step.sendingAssetId, steps[i - 1].receivingAssetId)) return note(`step ${i} spends ${step.sendingAssetId}, which step ${i - 1} does not deliver`, "policy_refused");
|
|
302
372
|
if (!step.requiresDeposit) continue;
|
|
303
|
-
if (!same(step.sendingAssetId, e.fromToken)) return `step ${i} pulls ${step.sendingAssetId} from the wallet, not the input ${e.fromToken}
|
|
373
|
+
if (!same(step.sendingAssetId, e.fromToken)) return note(`step ${i} pulls ${step.sendingAssetId} from the wallet, not the input ${e.fromToken}`, "policy_refused");
|
|
304
374
|
deposited += step.fromAmount;
|
|
305
375
|
}
|
|
306
|
-
if (deposited > e.amountIn) return `the steps pull ${deposited} from the wallet, more than the ${e.amountIn} spent
|
|
307
|
-
if (!same(last.receivingAssetId, finalAsset)) return `delivers ${last.receivingAssetId}, not ${finalAsset}
|
|
376
|
+
if (deposited > e.amountIn) return note(`the steps pull ${deposited} from the wallet, more than the ${e.amountIn} spent`, "policy_refused");
|
|
377
|
+
if (!same(last.receivingAssetId, finalAsset)) return note(`delivers ${last.receivingAssetId}, not ${finalAsset}`, "policy_refused");
|
|
308
378
|
return null;
|
|
309
379
|
}
|
|
310
380
|
var MAYAN_REFUND_AT = {
|
|
@@ -345,32 +415,32 @@ function acrossSwapRefunds(r, at) {
|
|
|
345
415
|
}
|
|
346
416
|
var ERC20_TRANSFER = "0xa9059cbb";
|
|
347
417
|
function ecoDelivery(r, at, e) {
|
|
348
|
-
if (kindOf(e.toToken) === "native") return "an Eco route to the native token cannot be checked here";
|
|
418
|
+
if (kindOf(e.toToken) === "native") return note("an Eco route to the native token cannot be checked here", "recipient_unverifiable");
|
|
349
419
|
const destination = r.word(at + 32 * 3);
|
|
350
|
-
if (destination !== e.destChainId) return `the Eco intent executes on chain ${destination}, not the destination ${e.destChainId} \u2014 a route whose delivery cannot be checked here
|
|
420
|
+
if (destination !== e.destChainId) return note(`the Eco intent executes on chain ${destination}, not the destination ${e.destChainId} \u2014 a route whose delivery cannot be checked here`, "recipient_unverifiable");
|
|
351
421
|
const route = r.bytes(at, at + 32 * 4);
|
|
352
422
|
const tuple = route.small(0);
|
|
353
423
|
const calls = tuple + route.small(tuple + 32 * 5);
|
|
354
424
|
const n = route.small(calls);
|
|
355
|
-
if (n === 0) return "the Eco route makes no calls on the destination chain";
|
|
356
|
-
if (n > 16) return `the Eco route makes ${n} calls on the destination chain, more than the 16 read here
|
|
425
|
+
if (n === 0) return note("the Eco route makes no calls on the destination chain", "recipient_unverifiable");
|
|
426
|
+
if (n > 16) return note(`the Eco route makes ${n} calls on the destination chain, more than the 16 read here`, "recipient_unverifiable");
|
|
357
427
|
const last = calls + 32 + route.small(calls + 32 + 32 * (n - 1));
|
|
358
428
|
const target = route.address(last);
|
|
359
429
|
const data = route.bytes(last, last + 32);
|
|
360
|
-
if (route.word(last + 64) !== 0n || data.length !== 68 || data.raw(0, 4) !== ERC20_TRANSFER) return "the Eco route does not end in a token transfer";
|
|
430
|
+
if (route.word(last + 64) !== 0n || data.length !== 68 || data.raw(0, 4) !== ERC20_TRANSFER) return note("the Eco route does not end in a token transfer", "recipient_unverifiable");
|
|
361
431
|
const to = data.address(4);
|
|
362
432
|
const amount = data.word(36);
|
|
363
|
-
if (!same(target, e.toToken)) return `the Eco route delivers ${target}, not ${e.toToken}
|
|
364
|
-
if (!same(to, e.receiver)) return `the Eco route pays ${to}, not ${e.receiver}
|
|
365
|
-
if (amount < e.minOut) return `the Eco route delivers ${amount}, below the ${e.minOut} shown
|
|
433
|
+
if (!same(target, e.toToken)) return note(`the Eco route delivers ${target}, not ${e.toToken}`, "policy_refused");
|
|
434
|
+
if (!same(to, e.receiver)) return note(`the Eco route pays ${to}, not ${e.receiver}`, "recipient_mismatch");
|
|
435
|
+
if (amount < e.minOut) return note(`the Eco route delivers ${amount}, below the ${e.minOut} shown`, "price_check_failed");
|
|
366
436
|
return null;
|
|
367
437
|
}
|
|
368
438
|
function m0Delivery(r, at, e) {
|
|
369
|
-
if (kindOf(e.toToken) === "native") return "an M0 order cannot deliver the native token";
|
|
439
|
+
if (kindOf(e.toToken) === "native") return note("an M0 order cannot deliver the native token", "unsupported_pair");
|
|
370
440
|
const tokenOut = r.address(at + 32 * 3);
|
|
371
441
|
const amountOut = r.word(at + 32 * 5);
|
|
372
|
-
if (!same(tokenOut, e.toToken)) return `the M0 order asks for ${tokenOut}, not ${e.toToken}
|
|
373
|
-
if (amountOut < e.minOut) return `the M0 order asks for ${amountOut}, below the ${e.minOut} shown
|
|
442
|
+
if (!same(tokenOut, e.toToken)) return note(`the M0 order asks for ${tokenOut}, not ${e.toToken}`, "policy_refused");
|
|
443
|
+
if (amountOut < e.minOut) return note(`the M0 order asks for ${amountOut}, below the ${e.minOut} shown`, "price_check_failed");
|
|
374
444
|
return null;
|
|
375
445
|
}
|
|
376
446
|
var FACET_DATA = {
|
|
@@ -384,8 +454,9 @@ var FACET_DATA = {
|
|
|
384
454
|
Frax: { inline: 5, refunds: [["refundRecipient", 3]], zeroRefused: true },
|
|
385
455
|
Glacis: { inline: 4, refunds: [["refundAddress", 1]], zeroRefused: true },
|
|
386
456
|
LayerSwap: { refunds: [["refundRecipient", 2]], zeroRefused: true },
|
|
457
|
+
// The escrow order's user. Not reached while LI.FI Intents is refused on every EVM-source route (INTENTS_UNPINNED); kept for when
|
|
458
|
+
// its oracles and settler are pinned.
|
|
387
459
|
LiFiIntentEscrowV2: { refunds: [["depositAndRefundAddress", 2]], zeroRefused: true },
|
|
388
|
-
// the escrow order's user
|
|
389
460
|
M0: { inline: 7, refunds: [["refundRecipient", 1], ["orderOwner", 2]], zeroRefused: true, delivery: m0Delivery },
|
|
390
461
|
// orderOwner gets a cancelled order back
|
|
391
462
|
Mayan: { refunds: [["refundRecipient", 7]], nested: mayanRefunds, zeroRefused: true },
|
|
@@ -403,79 +474,161 @@ function facetDataAt(r, bridge, spec) {
|
|
|
403
474
|
}
|
|
404
475
|
function refundMismatch(r, bridge, e) {
|
|
405
476
|
const spec = FACET_DATA[bridge.facet];
|
|
406
|
-
if (!spec) return NO_REFUND_FIELDS.has(bridge.facet) ? null : `no known refund layout for ${bridge.facet}
|
|
477
|
+
if (!spec) return NO_REFUND_FIELDS.has(bridge.facet) ? null : note(`no known refund layout for ${bridge.facet}`, "recipient_unverifiable");
|
|
407
478
|
const at = facetDataAt(r, bridge, spec);
|
|
408
479
|
const paid = (spec.refunds ?? []).map(([field, word]) => [`${bridge.facet} ${field}`, r.address(at + 32 * word), spec.zeroRefused === true]);
|
|
409
480
|
paid.push(...spec.nested?.(r, at) ?? []);
|
|
410
481
|
for (const [what, to, zeroRefused] of paid) {
|
|
411
482
|
if (zeroRefused && /^0x0{40}$/.test(to)) continue;
|
|
412
|
-
if (!same(to, e.sender) && !same(to, e.receiver)) return `${what} pays ${to}, neither the sender ${e.sender} nor the recipient
|
|
483
|
+
if (!same(to, e.sender) && !same(to, e.receiver)) return note(`${what} pays ${to}, neither the sender ${e.sender} nor the recipient`, "recipient_mismatch");
|
|
413
484
|
}
|
|
414
485
|
return null;
|
|
415
486
|
}
|
|
487
|
+
var NON_EVM_UNREAD = "the recipient on a non-EVM destination sits in bridge-specific data and cannot be checked here";
|
|
488
|
+
var LIFI_SOLANA_CHAIN_ID = 1151111081099710n;
|
|
489
|
+
var NON_EVM_ADDRESS = "0x11f111f111f111f111f111f111f111f111f111f1";
|
|
490
|
+
var SOLANA_USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
491
|
+
var WORMHOLE_SOLANA = 1n;
|
|
492
|
+
var CCTP_DOMAIN_SOLANA = 5n;
|
|
493
|
+
var CHAINFLIP_SOLANA_ASSETS = {
|
|
494
|
+
"11111111111111111111111111111111": 9n,
|
|
495
|
+
[SOLANA_USDC]: 10n,
|
|
496
|
+
Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB: 16n
|
|
497
|
+
};
|
|
498
|
+
var DEPOSIT_FACETS = /* @__PURE__ */ new Set(["NEARIntents", "LayerSwap", "Unit"]);
|
|
499
|
+
var MAYAN_SOLANA = {
|
|
500
|
+
// Swift v2 createOrderWithToken(tokenIn, amountIn, (payloadType, trader, destAddr, destChainId, referrerAddr, tokenOut, minAmountOut, …), customPayload)
|
|
501
|
+
"0xa3a30834": { destAddr: 4, chain: 5, cctp: false, payloadType: [2, 1n], tokenOut: 7 },
|
|
502
|
+
// MCTP createOrder((tokenIn, amountIn, gasDrop, destAddr, destChain, tokenOut, minAmountOut, deadline, redeemFee, referrerAddr, referrerBps))
|
|
503
|
+
"0x1c59b7fc": { destAddr: 3, chain: 4, cctp: false, tokenOut: 5 },
|
|
504
|
+
// MCTP bridgeWithFee(tokenIn, amountIn, redeemFee, gasDrop, destAddr, destDomain, payloadType, customPayload): delivers USDC
|
|
505
|
+
"0x2072197f": { destAddr: 4, chain: 5, cctp: true, payloadType: [6, 1n] },
|
|
506
|
+
// Fast MCTP bridge(tokenIn, amountIn, redeemFee, circleMaxFee, gasDrop, destAddr, destDomain, referrerAddress, referrerBps, payloadType, minFinalityThreshold, customPayload): delivers USDC
|
|
507
|
+
"0xf58b6de8": { destAddr: 5, chain: 6, cctp: true, payloadType: [9, 1n] },
|
|
508
|
+
// Fast MCTP createOrder(tokenIn, amountIn, circleMaxFee, destDomain, minFinalityThreshold, (payloadType 3, destAddr, tokenOut, amountOutMin, …))
|
|
509
|
+
"0x2337e236": { destAddr: 6, chain: 3, cctp: true, payloadType: [5, 3n], tokenOut: 7 }
|
|
510
|
+
};
|
|
511
|
+
var hexOf = (b) => `0x${Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("")}`;
|
|
512
|
+
var keyOf = (word) => base58(Uint8Array.from(word.slice(2).match(/../g) ?? [], (h) => Number.parseInt(h, 16)));
|
|
513
|
+
function mayanToSolana(r, at, t) {
|
|
514
|
+
const declared = r.raw(at, 32);
|
|
515
|
+
if (declared !== t.ownerHex) return note(`Mayan's nonEVMReceiver is ${keyOf(declared)}, not the recipient ${t.e.receiver}`, "recipient_mismatch");
|
|
516
|
+
const p = r.bytes(at, at + 32 * 2);
|
|
517
|
+
const selector = p.raw(0, 4);
|
|
518
|
+
const o = MAYAN_SOLANA[selector];
|
|
519
|
+
if (!o) return note(`the Mayan call ${selector} is an entry point whose Solana recipient cannot be checked here`, "recipient_unverifiable");
|
|
520
|
+
const word = (i) => 4 + 32 * i;
|
|
521
|
+
if (o.payloadType && p.word(word(o.payloadType[0])) !== o.payloadType[1]) return note(`the Mayan call ${selector} carries payload type ${p.word(word(o.payloadType[0]))}, not a plain delivery`, "recipient_unverifiable");
|
|
522
|
+
const dest = p.raw(word(o.destAddr), 32);
|
|
523
|
+
if (dest !== t.ownerHex) return note(`the Mayan order pays ${keyOf(dest)} on Solana, not the recipient ${t.e.receiver}`, "recipient_mismatch");
|
|
524
|
+
const chain = p.word(word(o.chain));
|
|
525
|
+
if (chain !== (o.cctp ? CCTP_DOMAIN_SOLANA : WORMHOLE_SOLANA)) return note(`the Mayan order goes to ${o.cctp ? "CCTP domain" : "Wormhole chain"} ${chain}, not Solana`, "policy_refused");
|
|
526
|
+
if (o.tokenOut === void 0) return t.e.toToken === SOLANA_USDC ? null : note(`a Mayan CCTP transfer delivers USDC, not ${t.e.toToken}`, "policy_refused");
|
|
527
|
+
const token = p.raw(word(o.tokenOut), 32);
|
|
528
|
+
return token === t.mintHex ? null : note(`the Mayan order delivers ${keyOf(token)}, not ${t.e.toToken}`, "policy_refused");
|
|
529
|
+
}
|
|
530
|
+
function polymerToSolana(r, at, t) {
|
|
531
|
+
const declared = r.raw(at + 32 * 2, 32);
|
|
532
|
+
if (declared !== t.ownerHex) return note(`Polymer CCTP's nonEVMReceiver is ${keyOf(declared)}, not the recipient ${t.e.receiver}`, "recipient_mismatch");
|
|
533
|
+
if (t.e.toToken !== SOLANA_USDC) return note(`Polymer CCTP delivers USDC, not ${t.e.toToken}`, "policy_refused");
|
|
534
|
+
const mintTo = r.raw(at + 32 * 3, 32);
|
|
535
|
+
const account = hexOf(associatedTokenAccount(t.owner, t.mint));
|
|
536
|
+
return mintTo === account ? null : note(`the Polymer CCTP transfer mints to ${keyOf(mintTo)}, not the recipient's USDC account ${keyOf(account)}`, "recipient_mismatch");
|
|
537
|
+
}
|
|
538
|
+
function chainflipToSolana(r, at, t) {
|
|
539
|
+
const dst = r.bytes(at, at);
|
|
540
|
+
if (dst.length !== 32) return note(`Chainflip pays a ${dst.length}-byte address on Solana, not the recipient ${t.e.receiver}`, "recipient_mismatch");
|
|
541
|
+
if (dst.raw(0, 32) !== t.ownerHex) return note(`Chainflip pays ${keyOf(dst.raw(0, 32))} on Solana, not the recipient ${t.e.receiver}`, "recipient_mismatch");
|
|
542
|
+
const asset = r.word(at + 32);
|
|
543
|
+
const want = CHAINFLIP_SOLANA_ASSETS[t.e.toToken];
|
|
544
|
+
return want !== void 0 && asset === want ? null : note(`Chainflip delivers its asset ${asset}, not ${t.e.toToken}`, "policy_refused");
|
|
545
|
+
}
|
|
546
|
+
var SOLANA_DELIVERY = {
|
|
547
|
+
Mayan: mayanToSolana,
|
|
548
|
+
PolymerCCTP: polymerToSolana,
|
|
549
|
+
Chainflip: chainflipToSolana
|
|
550
|
+
};
|
|
551
|
+
var INTENTS_TO_SOLANA = "a LI.FI Intents order to Solana names its own oracles, output settler and output amount, which cannot be checked here \u2014 its escrow could release the input without the delivery";
|
|
552
|
+
var INTENTS_UNPINNED = "a LI.FI Intents order to an EVM chain names its own oracles, output settler and output amount, which cannot be checked here \u2014 its escrow could release the input without the delivery";
|
|
553
|
+
var INTENTS_FACET = "LiFiIntentEscrowV2";
|
|
554
|
+
function solanaRecipientMismatch(r, bridge, receiver, e) {
|
|
555
|
+
if (e.destChainId !== LIFI_SOLANA_CHAIN_ID) return note(NON_EVM_UNREAD, "recipient_unverifiable");
|
|
556
|
+
if (!same(receiver, NON_EVM_ADDRESS)) return note(`BridgeData pays ${receiver}, not LI.FI's non-EVM placeholder ${NON_EVM_ADDRESS}, on a Solana route`, "recipient_mismatch");
|
|
557
|
+
if (DEPOSIT_FACETS.has(bridge.facet)) return note(`${bridge.facet} pays a deposit address whose off-chain order names the Solana recipient \u2014 the transaction does not bind it`, "recipient_unverifiable");
|
|
558
|
+
if (bridge.facet === INTENTS_FACET) return note(INTENTS_TO_SOLANA, "recipient_unverifiable");
|
|
559
|
+
const read = SOLANA_DELIVERY[bridge.facet];
|
|
560
|
+
if (!read) return note(NON_EVM_UNREAD, "recipient_unverifiable");
|
|
561
|
+
const owner = solanaKey(e.receiver);
|
|
562
|
+
if (!owner) return note(`the recipient ${e.receiver} is not a Solana address`, "recipient_mismatch");
|
|
563
|
+
const mint = solanaKey(e.toToken);
|
|
564
|
+
if (!mint) return note(`the destination token ${e.toToken} is not a Solana mint`, "unsupported_pair");
|
|
565
|
+
return read(r, facetDataAt(r, bridge, FACET_DATA[bridge.facet]), { owner, ownerHex: hexOf(owner), mint, mintHex: hexOf(mint), e });
|
|
566
|
+
}
|
|
416
567
|
function diamondCallMismatch(data, e) {
|
|
417
568
|
let r;
|
|
418
569
|
try {
|
|
419
570
|
r = new Reader(data);
|
|
420
571
|
} catch {
|
|
421
|
-
return "the calldata is not hex";
|
|
572
|
+
return note("the calldata is not hex", "upstream_unavailable");
|
|
422
573
|
}
|
|
423
|
-
if (r.length < 4 + 32 * 2) return "the calldata is too short";
|
|
574
|
+
if (r.length < 4 + 32 * 2) return note("the calldata is too short", "upstream_unavailable");
|
|
424
575
|
const selector = data.slice(0, 10).toLowerCase();
|
|
425
576
|
const generic = GENERIC_SWAPS[selector];
|
|
426
577
|
try {
|
|
427
578
|
if (generic) {
|
|
428
|
-
if (!e.sameChain) return "a same-chain swap call on a cross-chain route";
|
|
579
|
+
if (!e.sameChain) return note("a same-chain swap call on a cross-chain route", "policy_refused");
|
|
429
580
|
const receiver2 = r.address(4 + 96);
|
|
430
581
|
const minAmount2 = r.word(4 + 128);
|
|
431
582
|
const steps = generic.single ? [swapAt(r, 4 + r.small(4 + 160))] : swapsAt(r, 4, 4 + 160);
|
|
432
|
-
if (!e.evmDestination || !same(receiver2, e.receiver)) return `pays ${receiver2}, not ${e.receiver}
|
|
433
|
-
if (minAmount2 < e.minOut) return `accepts ${minAmount2}, below the ${e.minOut} shown
|
|
434
|
-
if (generic.from && kindOf(e.fromToken) !== generic.from) return `${selector} swaps from ${generic.from === "native" ? "the native coin" : "a token"}, but the input is ${kindOf(e.fromToken) === "native" ? "the native coin" : "a token"}
|
|
583
|
+
if (!e.evmDestination || !same(receiver2, e.receiver)) return note(`pays ${receiver2}, not ${e.receiver}`, "recipient_mismatch");
|
|
584
|
+
if (minAmount2 < e.minOut) return note(`accepts ${minAmount2}, below the ${e.minOut} shown`, "price_check_failed");
|
|
585
|
+
if (generic.from && kindOf(e.fromToken) !== generic.from) return note(`${selector} swaps from ${generic.from === "native" ? "the native coin" : "a token"}, but the input is ${kindOf(e.fromToken) === "native" ? "the native coin" : "a token"}`, "policy_refused");
|
|
435
586
|
if (generic.to && (kindOf(e.toToken) !== generic.to || kindOf(steps[steps.length - 1].receivingAssetId) !== generic.to)) {
|
|
436
|
-
return `${selector} delivers ${generic.to === "native" ? "the native coin" : "a token"}, but the output is ${kindOf(e.toToken) === "native" ? "the native coin" : "a token"} \u2014 its minimum would be counted in the wrong asset
|
|
587
|
+
return note(`${selector} delivers ${generic.to === "native" ? "the native coin" : "a token"}, but the output is ${kindOf(e.toToken) === "native" ? "the native coin" : "a token"} \u2014 its minimum would be counted in the wrong asset`, "policy_refused");
|
|
437
588
|
}
|
|
438
589
|
return checkSteps(steps, e, e.toToken);
|
|
439
590
|
}
|
|
440
591
|
const unbound = UNBOUND_BRIDGES[selector];
|
|
441
|
-
if (unbound) return `${unbound} delivers to a recipient the transaction does not bind (an off-chain order or unchecked facet data)
|
|
592
|
+
if (unbound) return note(`${unbound} delivers to a recipient the transaction does not bind (an off-chain order or unchecked facet data)`, "recipient_unverifiable");
|
|
442
593
|
const bridge = BRIDGE_CALLS[selector];
|
|
443
|
-
if (!bridge) return `unknown Diamond call ${selector}
|
|
594
|
+
if (!bridge) return note(`unknown Diamond call ${selector}`, "policy_refused");
|
|
595
|
+
if (bridge.facet === INTENTS_FACET && e.evmDestination) return note(INTENTS_UNPINNED, "recipient_unverifiable");
|
|
444
596
|
const head = r.small(4);
|
|
445
|
-
if (head < (bridge.sourceSwaps ? 96 : 64) || head % 32 !== 0 || head > 32 * 32) return `unknown Diamond call ${selector}
|
|
597
|
+
if (head < (bridge.sourceSwaps ? 96 : 64) || head % 32 !== 0 || head > 32 * 32) return note(`unknown Diamond call ${selector}`, "policy_refused");
|
|
446
598
|
const at = 4 + head;
|
|
447
|
-
if (r.small(at + 32) !== 320) return `unknown Diamond call ${selector}
|
|
599
|
+
if (r.small(at + 32) !== 320) return note(`unknown Diamond call ${selector}`, "policy_refused");
|
|
448
600
|
const sendingAssetId = r.address(at + 128);
|
|
449
601
|
const receiver = r.address(at + 160);
|
|
450
602
|
const minAmount = r.word(at + 192);
|
|
451
603
|
const destinationChainId = r.word(at + 224);
|
|
452
604
|
const hasSourceSwaps = r.bool(at + 256);
|
|
453
605
|
const hasDestinationCall = r.bool(at + 288);
|
|
454
|
-
if (e.sameChain) return "a bridge call on a same-chain route";
|
|
455
|
-
if (destinationChainId !== e.destChainId) return `bridges to chain ${destinationChainId}, not ${e.destChainId}
|
|
456
|
-
if (hasDestinationCall) return "the route runs a call on the destination chain, whose recipient cannot be checked here";
|
|
457
|
-
if (!e.evmDestination)
|
|
458
|
-
|
|
459
|
-
|
|
606
|
+
if (e.sameChain) return note("a bridge call on a same-chain route", "policy_refused");
|
|
607
|
+
if (destinationChainId !== e.destChainId) return note(`bridges to chain ${destinationChainId}, not ${e.destChainId}`, "policy_refused");
|
|
608
|
+
if (hasDestinationCall) return note("the route runs a call on the destination chain, whose recipient cannot be checked here", "recipient_unverifiable");
|
|
609
|
+
if (!e.evmDestination) {
|
|
610
|
+
const why = solanaRecipientMismatch(r, bridge, receiver, e);
|
|
611
|
+
if (why) return why;
|
|
612
|
+
} else if (!same(receiver, e.receiver)) return note(`pays ${receiver}, not ${e.receiver}`, "recipient_mismatch");
|
|
613
|
+
if (hasSourceSwaps !== bridge.sourceSwaps) return note(`unknown Diamond call ${selector}`, "policy_refused");
|
|
460
614
|
const refund = refundMismatch(r, bridge, e);
|
|
461
615
|
if (refund) return refund;
|
|
462
616
|
const spec = FACET_DATA[bridge.facet];
|
|
463
617
|
const delivery = spec?.delivery?.(r, facetDataAt(r, bridge, spec), e);
|
|
464
618
|
if (delivery) return delivery;
|
|
465
619
|
if (hasSourceSwaps) {
|
|
466
|
-
if (minAmount === 0n) return "bridges nothing after the source swaps";
|
|
620
|
+
if (minAmount === 0n) return note("bridges nothing after the source swaps", "policy_refused");
|
|
467
621
|
return checkSteps(swapsAt(r, 4, 4 + 32), e, sendingAssetId);
|
|
468
622
|
}
|
|
469
|
-
if (minAmount > e.amountIn || minAmount * 10000n < e.amountIn * (10000n - BRIDGE_INPUT_FEE_MAX_BPS)) return `bridges ${minAmount}, not the ${e.amountIn} spent
|
|
470
|
-
return same(sendingAssetId, e.fromToken) ? null : `bridges ${sendingAssetId}, not ${e.fromToken}
|
|
623
|
+
if (minAmount > e.amountIn || minAmount * 10000n < e.amountIn * (10000n - BRIDGE_INPUT_FEE_MAX_BPS)) return note(`bridges ${minAmount}, not the ${e.amountIn} spent`, "policy_refused");
|
|
624
|
+
return same(sendingAssetId, e.fromToken) ? null : note(`bridges ${sendingAssetId}, not ${e.fromToken}`, "policy_refused");
|
|
471
625
|
} catch (err) {
|
|
472
|
-
return `the Diamond calldata does not decode (${err instanceof Error ? err.message : String(err)})
|
|
626
|
+
return note(`the Diamond calldata does not decode (${err instanceof Error ? err.message : String(err)})`, "policy_refused");
|
|
473
627
|
}
|
|
474
628
|
}
|
|
475
629
|
|
|
476
630
|
// src/solana.ts
|
|
477
631
|
var import_util2 = require("@anywalletkit/protocol-swap2-core/util");
|
|
478
|
-
var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
479
632
|
var MAYAN_ENTRY_PROGRAMS = /* @__PURE__ */ new Set([
|
|
480
633
|
"D8C8iW6zmoKg5TRr8nQ7h14TMWqQX8FiBdj2ju5MF3wa",
|
|
481
634
|
// CPI_PROXY_PROGRAM_ID
|
|
@@ -490,20 +643,6 @@ var MAYAN_ENTRY_PROGRAMS = /* @__PURE__ */ new Set([
|
|
|
490
643
|
]);
|
|
491
644
|
var SYSTEM_PROGRAM = "11111111111111111111111111111111";
|
|
492
645
|
var LIFI_SOLANA_FEE_WALLET = "34FKjAdVcTax2DHqV2XnbXa9J3zmyKcFuFKWbcmgxjgm";
|
|
493
|
-
function base58(bytes) {
|
|
494
|
-
let n = 0n;
|
|
495
|
-
for (const b of bytes) n = n << 8n | BigInt(b);
|
|
496
|
-
let out = "";
|
|
497
|
-
while (n > 0n) {
|
|
498
|
-
out = ALPHABET[Number(n % 58n)] + out;
|
|
499
|
-
n /= 58n;
|
|
500
|
-
}
|
|
501
|
-
for (const b of bytes) {
|
|
502
|
-
if (b !== 0) break;
|
|
503
|
-
out = `1${out}`;
|
|
504
|
-
}
|
|
505
|
-
return out;
|
|
506
|
-
}
|
|
507
646
|
var Cursor = class {
|
|
508
647
|
constructor(b) {
|
|
509
648
|
this.b = b;
|
|
@@ -533,7 +672,7 @@ var Cursor = class {
|
|
|
533
672
|
};
|
|
534
673
|
function solanaBridgeTxMismatch(base64, sender) {
|
|
535
674
|
const bytes = (0, import_util2.base64Bytes)(base64);
|
|
536
|
-
if (!bytes) return "the Solana transaction is not base64";
|
|
675
|
+
if (!bytes) return note("the Solana transaction is not base64", "upstream_unavailable");
|
|
537
676
|
const c = new Cursor(bytes);
|
|
538
677
|
try {
|
|
539
678
|
c.bytes(64 * c.short());
|
|
@@ -541,7 +680,7 @@ function solanaBridgeTxMismatch(base64, sender) {
|
|
|
541
680
|
if ((first & 128) === 0) c.at--;
|
|
542
681
|
c.bytes(3);
|
|
543
682
|
const keys = Array.from({ length: c.short() }, () => base58(c.bytes(32)));
|
|
544
|
-
if (keys[0] !== sender) return `the Solana transaction is paid by ${keys[0] ?? "nobody"}, not the sender ${sender}
|
|
683
|
+
if (keys[0] !== sender) return note(`the Solana transaction is paid by ${keys[0] ?? "nobody"}, not the sender ${sender}`, "policy_refused");
|
|
545
684
|
c.bytes(32);
|
|
546
685
|
let mayan = false;
|
|
547
686
|
const payees = [];
|
|
@@ -556,15 +695,16 @@ function solanaBridgeTxMismatch(base64, sender) {
|
|
|
556
695
|
if (to !== void 0 && to !== sender && to !== LIFI_SOLANA_FEE_WALLET) payees.push(to);
|
|
557
696
|
}
|
|
558
697
|
}
|
|
559
|
-
return mayan && payees.length ? `the Solana transaction transfers SOL to ${payees[0]}, which is neither the sender nor LI.FI's fee wallet
|
|
698
|
+
return mayan && payees.length ? note(`the Solana transaction transfers SOL to ${payees[0]}, which is neither the sender nor LI.FI's fee wallet`, "recipient_mismatch") : null;
|
|
560
699
|
} catch (err) {
|
|
561
|
-
return `the Solana transaction does not decode (${err instanceof Error ? err.message : String(err)})
|
|
700
|
+
return note(`the Solana transaction does not decode (${err instanceof Error ? err.message : String(err)})`, "policy_refused");
|
|
562
701
|
}
|
|
563
702
|
}
|
|
564
703
|
|
|
565
704
|
// src/provider.ts
|
|
566
705
|
var import_util3 = require("@anywalletkit/protocol-swap2-core/util");
|
|
567
706
|
var import_validate = require("@anywalletkit/protocol-swap2-core/validate");
|
|
707
|
+
var import_errors2 = require("@anywalletkit/protocol-swap2-core/errors");
|
|
568
708
|
var import_core = require("@anywalletkit/core");
|
|
569
709
|
var DEFAULT_LIFI_BASE = "https://li.quest/v1";
|
|
570
710
|
var LIFI_CHAIN = {
|
|
@@ -605,7 +745,8 @@ var LIFI_CHAIN = {
|
|
|
605
745
|
"eip155:2818": "2818",
|
|
606
746
|
"eip155:14": "14",
|
|
607
747
|
// 2026-11-10: Elysium (Kinetiq; li.quest/v1/chains id 99901, key "ely"). LI.FI's bridges there are Relay (refused
|
|
608
|
-
// below: UNBOUND_TOOLS) and LI.FI Intents
|
|
748
|
+
// below: UNBOUND_TOOLS) and LI.FI Intents (refused too: UNPINNED_TOOLS), so it has no cross-chain LI.FI route for now;
|
|
749
|
+
// same-chain swaps go through GenericSwapFacetV3 (KyberSwap, Nordstern).
|
|
609
750
|
"eip155:99901": "99901",
|
|
610
751
|
[LIFI_SOLANA_CAIP2]: "1151111081099710"
|
|
611
752
|
};
|
|
@@ -615,12 +756,12 @@ var SOLANA_NATIVE = "11111111111111111111111111111111";
|
|
|
615
756
|
var EVM_PLACEHOLDER = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
|
|
616
757
|
var SOLANA_PLACEHOLDER = "HAgk14JpMQLgt6rVgv7cBQFJWFto5Dqxi472uT3DKpqk";
|
|
617
758
|
var isSolana = (chain) => chain === LIFI_SOLANA_CAIP2;
|
|
618
|
-
var
|
|
759
|
+
var BASE582 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
619
760
|
var BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
620
761
|
var SAME_CHAIN_MAX_SECONDS = 90;
|
|
621
762
|
var MAX_INPUT_FEE_BPS = 300;
|
|
622
763
|
function sameChainAddress(chain, actual, expected) {
|
|
623
|
-
if (isSolana(chain)) return !!actual &&
|
|
764
|
+
if (isSolana(chain)) return !!actual && BASE582.test(actual) && actual === expected;
|
|
624
765
|
return sameAddress(actual, expected);
|
|
625
766
|
}
|
|
626
767
|
function nativeFor(chain, token) {
|
|
@@ -709,7 +850,16 @@ function headers(key) {
|
|
|
709
850
|
}
|
|
710
851
|
var TOOL_KEY = /^[A-Za-z0-9_-]{1,64}$/;
|
|
711
852
|
var UNBOUND_TOOLS = Object.freeze(["relaydepository", "garden", "squid", "symbiosis"]);
|
|
712
|
-
|
|
853
|
+
var UNPINNED_TOOLS = Object.freeze(["lifiIntents"]);
|
|
854
|
+
var REFUSED_EVM_TOOLS = Object.freeze([...UNBOUND_TOOLS, ...UNPINNED_TOOLS]);
|
|
855
|
+
var SOLANA_DESTINATION_TOOLS = Object.freeze(["mayan", "mayanMCTP", "mayanFastMCTP", "polymer", "polymerStandard", "chainflip"]);
|
|
856
|
+
var SMART_DEPOSITS_TOOL = /^smartdeposits?$/i;
|
|
857
|
+
var dryRecipient = (params, fromAddress) => !isSolana(params.sourceChain) && isSolana(params.destChain) ? SOLANA_PLACEHOLDER : fromAddress;
|
|
858
|
+
function placeholderIn(params) {
|
|
859
|
+
if (!params.refundAddress) return "sender";
|
|
860
|
+
return !params.destAddress && dryRecipient(params, params.refundAddress) !== params.refundAddress ? "recipient" : null;
|
|
861
|
+
}
|
|
862
|
+
function routeOptions(body, excluded, allowed) {
|
|
713
863
|
const routes = body?.routes;
|
|
714
864
|
if (!Array.isArray(routes)) return [];
|
|
715
865
|
const out = [];
|
|
@@ -719,7 +869,7 @@ function routeOptions(body, excluded) {
|
|
|
719
869
|
const cross = Array.isArray(step.includedSteps) ? step.includedSteps.find((i) => i?.type === "cross")?.tool : void 0;
|
|
720
870
|
const tool = typeof cross === "string" ? cross : step.tool;
|
|
721
871
|
const eta = step.estimate?.executionDuration;
|
|
722
|
-
if (typeof tool !== "string" || !TOOL_KEY.test(tool) || excluded.includes(tool) || typeof eta !== "number" || !Number.isFinite(eta) || eta < 0) continue;
|
|
872
|
+
if (typeof tool !== "string" || !TOOL_KEY.test(tool) || excluded.includes(tool) || allowed && !allowed.includes(tool) || typeof eta !== "number" || !Number.isFinite(eta) || eta < 0) continue;
|
|
723
873
|
if (typeof r.toAmount !== "string" || typeof r.toAmountMin !== "string" || !/^\d{1,60}$/.test(r.toAmount) || !/^\d{1,60}$/.test(r.toAmountMin)) continue;
|
|
724
874
|
out.push({
|
|
725
875
|
tool,
|
|
@@ -732,6 +882,92 @@ function routeOptions(body, excluded) {
|
|
|
732
882
|
}
|
|
733
883
|
return out;
|
|
734
884
|
}
|
|
885
|
+
var LIFI_TOOL_REASONS = {
|
|
886
|
+
NO_POSSIBLE_ROUTE: "no_route",
|
|
887
|
+
INSUFFICIENT_LIQUIDITY: "insufficient_liquidity",
|
|
888
|
+
TOOL_TIMEOUT: "upstream_unavailable",
|
|
889
|
+
RPC_ERROR: "upstream_unavailable",
|
|
890
|
+
AMOUNT_TOO_LOW: "amount_too_small",
|
|
891
|
+
AMOUNT_TOO_HIGH: "amount_too_large",
|
|
892
|
+
FEES_HIGHER_THAN_AMOUNT: "amount_too_small",
|
|
893
|
+
FEES_HGHER_THAN_AMOUNT: "amount_too_small",
|
|
894
|
+
// an older spelling of the same code
|
|
895
|
+
DIFFERENT_RECIPIENT_NOT_SUPPORTED: "unsupported_pair",
|
|
896
|
+
CANNOT_GUARANTEE_MIN_AMOUNT: "price_check_failed"
|
|
897
|
+
};
|
|
898
|
+
var LIFI_ERROR_REASONS = {
|
|
899
|
+
1001: "no_route",
|
|
900
|
+
// FailedToBuildTransactionError: "None of the available routes could successfully generate a tx"
|
|
901
|
+
1002: "no_route",
|
|
902
|
+
// NoQuoteError: "No available quotes for the requested transfer"
|
|
903
|
+
1003: "no_route",
|
|
904
|
+
// NotFoundError
|
|
905
|
+
1005: "rate_limited",
|
|
906
|
+
// RateLimitError
|
|
907
|
+
1006: "upstream_unavailable",
|
|
908
|
+
// ServerError
|
|
909
|
+
1007: "price_check_failed",
|
|
910
|
+
// SlippageError
|
|
911
|
+
1008: "upstream_unavailable",
|
|
912
|
+
// ThirdPartyError
|
|
913
|
+
1009: "upstream_unavailable",
|
|
914
|
+
// TimeoutError
|
|
915
|
+
1010: "key_rejected",
|
|
916
|
+
// UnauthorizedError
|
|
917
|
+
1012: "upstream_unavailable"
|
|
918
|
+
// RpcFailure
|
|
919
|
+
};
|
|
920
|
+
var LIFI_MESSAGE_REASONS = [
|
|
921
|
+
[/no available quotes|unable to find a quote|none of the available routes|no route/i, "no_route"],
|
|
922
|
+
[/rate limit|too many requests/i, "rate_limited"],
|
|
923
|
+
[/api[ -]?key|unauthori[sz]ed/i, "key_rejected"],
|
|
924
|
+
[/insufficient liquidity|not enough liquidity/i, "insufficient_liquidity"],
|
|
925
|
+
[/amount (?:is )?too (?:low|small)|below the minimum/i, "amount_too_small"],
|
|
926
|
+
[/amount (?:is )?too (?:high|large)|above the maximum/i, "amount_too_large"],
|
|
927
|
+
[/not supported|unsupported|unknown token|invalid token/i, "unsupported_pair"],
|
|
928
|
+
[/halted|paused|maintenance/i, "chain_halted"]
|
|
929
|
+
];
|
|
930
|
+
function lifiToolCodes(errors) {
|
|
931
|
+
const out = /* @__PURE__ */ new Set();
|
|
932
|
+
const stack = [errors];
|
|
933
|
+
for (let visited = 0; stack.length > 0 && visited < 256; visited++) {
|
|
934
|
+
const node = stack.pop();
|
|
935
|
+
if (Array.isArray(node)) {
|
|
936
|
+
stack.push(...node.slice(0, 64));
|
|
937
|
+
continue;
|
|
938
|
+
}
|
|
939
|
+
if (!node || typeof node !== "object") continue;
|
|
940
|
+
const code = node.code;
|
|
941
|
+
if (typeof code === "string" && Object.prototype.hasOwnProperty.call(LIFI_TOOL_REASONS, code)) out.add(code);
|
|
942
|
+
for (const [key, value] of Object.entries(node).slice(0, 64)) if (key !== "action" && value && typeof value === "object") stack.push(value);
|
|
943
|
+
}
|
|
944
|
+
return [...out];
|
|
945
|
+
}
|
|
946
|
+
function lifiBodyReason(body, filteredOut) {
|
|
947
|
+
const b = body && typeof body === "object" ? body : {};
|
|
948
|
+
const tools = [...new Set(lifiToolCodes(b.errors).map((code) => LIFI_TOOL_REASONS[code]))];
|
|
949
|
+
if (tools.length === 1) return tools[0];
|
|
950
|
+
if (filteredOut.length > 0 && filteredOut.every((r) => /^ROUTE_SLIPPAGE_FLOOR_UNREACHABLE/.test(r))) return "price_check_failed";
|
|
951
|
+
const byCode = typeof b.code === "number" ? LIFI_ERROR_REASONS[b.code] : void 0;
|
|
952
|
+
if (byCode) return byCode;
|
|
953
|
+
const message = typeof b.message === "string" ? b.message : "";
|
|
954
|
+
return LIFI_MESSAGE_REASONS.find(([pattern]) => pattern.test(message))?.[1];
|
|
955
|
+
}
|
|
956
|
+
var LIFI_TRANSIENT_REASONS = /* @__PURE__ */ new Set(["rate_limited", "insufficient_liquidity", "chain_halted"]);
|
|
957
|
+
function lifiHttpRefusal(status, retryAfter, body, filteredOut) {
|
|
958
|
+
const http = (0, import_errors2.httpRefusal)(status, retryAfter);
|
|
959
|
+
if (http && http.reason !== "upstream_unavailable") return http;
|
|
960
|
+
const info = (reason) => {
|
|
961
|
+
if (reason === "rate_limited") {
|
|
962
|
+
const wait = (0, import_errors2.parseRetryAfter)(retryAfter);
|
|
963
|
+
return { reason, httpStatus: status, ...wait !== void 0 ? { retryAfterMs: wait } : {} };
|
|
964
|
+
}
|
|
965
|
+
return reason === "upstream_unavailable" || reason === "key_rejected" ? { reason, httpStatus: status } : { reason };
|
|
966
|
+
};
|
|
967
|
+
const byBody = lifiBodyReason(body, filteredOut);
|
|
968
|
+
if (http) return byBody !== void 0 && LIFI_TRANSIENT_REASONS.has(byBody) ? info(byBody) : http;
|
|
969
|
+
return byBody !== void 0 ? info(byBody) : { reason: "no_route" };
|
|
970
|
+
}
|
|
735
971
|
function cleanChoice(choice) {
|
|
736
972
|
if (!choice) return null;
|
|
737
973
|
const allow = (choice.allowBridges ?? []).filter((t) => TOOL_KEY.test(t));
|
|
@@ -772,8 +1008,10 @@ function lifiProvider(config = {}) {
|
|
|
772
1008
|
return !!LIFI_CHAIN[from.chain] && !!LIFI_CHAIN[to.chain];
|
|
773
1009
|
},
|
|
774
1010
|
async getQuote(params, signal) {
|
|
775
|
-
const reject = (why) => {
|
|
776
|
-
|
|
1011
|
+
const reject = (why, reason) => {
|
|
1012
|
+
const info = typeof reason === "string" ? { reason } : reason;
|
|
1013
|
+
if (info) params.onRefusal?.(`lifi: ${why}`, info);
|
|
1014
|
+
else params.onRefusal?.(`lifi: ${why}`);
|
|
777
1015
|
return null;
|
|
778
1016
|
};
|
|
779
1017
|
if (params.flex) return null;
|
|
@@ -784,13 +1022,15 @@ function lifiProvider(config = {}) {
|
|
|
784
1022
|
if (!fromToken) return null;
|
|
785
1023
|
const solanaSource = isSolana(params.sourceChain);
|
|
786
1024
|
const crossChain = params.sourceChain !== params.destChain;
|
|
1025
|
+
const evmToSolana = !solanaSource && isSolana(params.destChain);
|
|
787
1026
|
const noDestinationCall = crossChain && !solanaSource;
|
|
788
1027
|
let routeScope = config.routeWideSlippage === true && !solanaSource && !isSolana(params.destChain);
|
|
789
1028
|
const dry = params.dry !== false;
|
|
790
1029
|
const fromAddress = params.refundAddress || (dry ? solanaSource ? SOLANA_PLACEHOLDER : EVM_PLACEHOLDER : "");
|
|
791
|
-
const toAddress = params.destAddress || (dry ? fromAddress : "");
|
|
1030
|
+
const toAddress = params.destAddress || (dry ? dryRecipient(params, fromAddress) : "");
|
|
792
1031
|
if (!fromAddress) return reject("a live quote needs the sender's address");
|
|
793
1032
|
if (!toAddress) return reject("a live quote needs a recipient");
|
|
1033
|
+
const placeholder = dry ? placeholderIn(params) : null;
|
|
794
1034
|
const slippageBps = params.slippageBps ?? defaultSlippageBps;
|
|
795
1035
|
let sentSlippage = slippageBps / 1e4;
|
|
796
1036
|
let deadline;
|
|
@@ -811,7 +1051,7 @@ function lifiProvider(config = {}) {
|
|
|
811
1051
|
options: {
|
|
812
1052
|
slippage: slippageBps / 1e4,
|
|
813
1053
|
...routeScope ? { slippageScope: "route" } : {},
|
|
814
|
-
...solanaSource ? {} : { bridges: { deny:
|
|
1054
|
+
...solanaSource ? {} : { bridges: { ...evmToSolana ? { allow: SOLANA_DESTINATION_TOOLS } : {}, deny: REFUSED_EVM_TOOLS } },
|
|
815
1055
|
...noDestinationCall ? { allowDestinationCall: false } : {},
|
|
816
1056
|
...integrator().trim() ? { integrator: integrator().trim() } : {}
|
|
817
1057
|
}
|
|
@@ -819,7 +1059,7 @@ function lifiProvider(config = {}) {
|
|
|
819
1059
|
signal: signal ?? (0, import_util3.timeoutSignal)(8e3)
|
|
820
1060
|
});
|
|
821
1061
|
if (res.ok) {
|
|
822
|
-
const options = routeOptions(await res.json(), solanaSource ? [] :
|
|
1062
|
+
const options = routeOptions(await res.json(), solanaSource ? [] : REFUSED_EVM_TOOLS, evmToSolana ? SOLANA_DESTINATION_TOOLS : void 0);
|
|
823
1063
|
if (options.length) choice = cleanChoice(config.chooseRoute(options, params));
|
|
824
1064
|
}
|
|
825
1065
|
} catch {
|
|
@@ -841,8 +1081,10 @@ function lifiProvider(config = {}) {
|
|
|
841
1081
|
}
|
|
842
1082
|
if (routeScope) url.searchParams.set("slippageScope", "route");
|
|
843
1083
|
if (noDestinationCall) url.searchParams.set("allowDestinationCall", "false");
|
|
844
|
-
const unbound = crossChain && !solanaSource ?
|
|
845
|
-
|
|
1084
|
+
const unbound = crossChain && !solanaSource ? REFUSED_EVM_TOOLS : [];
|
|
1085
|
+
const allow = (choice?.allowBridges ?? []).filter((b) => solanaSource || !REFUSED_EVM_TOOLS.includes(b));
|
|
1086
|
+
const solanaAllow = evmToSolana ? allow.filter((b) => SOLANA_DESTINATION_TOOLS.includes(b)) : [];
|
|
1087
|
+
for (const t of evmToSolana ? solanaAllow.length ? solanaAllow : SOLANA_DESTINATION_TOOLS : allow) url.searchParams.append("allowBridges", t);
|
|
846
1088
|
for (const t of /* @__PURE__ */ new Set([...choice?.denyBridges ?? [], ...unbound])) url.searchParams.append("denyBridges", t);
|
|
847
1089
|
if (choice?.order) url.searchParams.set("order", choice.order);
|
|
848
1090
|
deadline = signal ? void 0 : (0, import_util3.timeoutSignal)(12e3);
|
|
@@ -861,18 +1103,28 @@ function lifiProvider(config = {}) {
|
|
|
861
1103
|
continue;
|
|
862
1104
|
}
|
|
863
1105
|
const why = reasons.length === 1 ? ` (${reasons[0].slice(0, 200)})` : "";
|
|
864
|
-
params.onRefusal?.(
|
|
1106
|
+
params.onRefusal?.(
|
|
1107
|
+
`lifi: ${typeof body?.message === "string" ? body.message.slice(0, 200) : `HTTP ${res.status}`}${why}`,
|
|
1108
|
+
lifiHttpRefusal(res.status, res.headers?.get?.("retry-after"), body, reasons)
|
|
1109
|
+
);
|
|
865
1110
|
return null;
|
|
866
1111
|
}
|
|
867
1112
|
const q = await res.json();
|
|
868
1113
|
const receivedAt = Date.now();
|
|
1114
|
+
if (evmToSolana) {
|
|
1115
|
+
const steps = q.includedSteps;
|
|
1116
|
+
const tools = [q.tool, ...Array.isArray(steps) ? steps.map((i) => i?.tool) : []];
|
|
1117
|
+
if (tools.some((t) => typeof t === "string" && SMART_DEPOSITS_TOOL.test(t))) {
|
|
1118
|
+
return reject("LI.FI Smart Deposits pay a deposit address whose recipient the transaction does not bind \u2014 refused for a Solana destination", "recipient_unverifiable");
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
869
1121
|
const action = q.action;
|
|
870
1122
|
const est = q.estimate;
|
|
871
1123
|
const tr = q.transactionRequest;
|
|
872
1124
|
const expectedToToken = nativeFor(params.destChain, params.destToken);
|
|
873
1125
|
const expectedFromChain = Number(fromChain);
|
|
874
1126
|
const expectedToChain = Number(toChain);
|
|
875
|
-
if (q.type !== "lifi" || !q.id || !action || !est?.toAmount || !est.fromAmount || !tr) return reject("incomplete quote (no action, estimate or transaction)");
|
|
1127
|
+
if (q.type !== "lifi" || !q.id || !action || !est?.toAmount || !est.fromAmount || !tr) return reject("incomplete quote (no action, estimate or transaction)", "upstream_unavailable");
|
|
876
1128
|
const mismatch = [
|
|
877
1129
|
action.fromChainId !== expectedFromChain && "source chain",
|
|
878
1130
|
action.toChainId !== expectedToChain && "destination chain",
|
|
@@ -882,41 +1134,53 @@ function lifiProvider(config = {}) {
|
|
|
882
1134
|
!sameChainAddress(params.sourceChain, action.fromAddress, fromAddress) && "sender",
|
|
883
1135
|
!sameChainAddress(params.destChain, action.toAddress, toAddress) && "recipient"
|
|
884
1136
|
].filter(Boolean);
|
|
885
|
-
if (mismatch.length)
|
|
1137
|
+
if (mismatch.length) {
|
|
1138
|
+
return reject(
|
|
1139
|
+
`quote does not match the request (${mismatch.join(", ")})`,
|
|
1140
|
+
// LI.FI refunds to the sender it was asked for (fromAddress): either address differing pays or refunds someone else.
|
|
1141
|
+
mismatch.includes("recipient") || mismatch.includes("sender") ? "recipient_mismatch" : "policy_refused"
|
|
1142
|
+
);
|
|
1143
|
+
}
|
|
886
1144
|
const bridged = BigInt(est.fromAmount);
|
|
887
1145
|
const spent = BigInt(params.amountIn);
|
|
888
|
-
if (bridged > spent) return reject(`the estimate bridges ${est.fromAmount}, more than the ${params.amountIn} spent
|
|
1146
|
+
if (bridged > spent) return reject(`the estimate bridges ${est.fromAmount}, more than the ${params.amountIn} spent`, "price_check_failed");
|
|
889
1147
|
if ((spent - bridged) * 10000n > spent * BigInt(MAX_INPUT_FEE_BPS)) {
|
|
890
|
-
return reject(`the route takes ${(0, import_core.bigintToNumber)((spent - bridged) * 10000n / spent) / 100}% of the input as a fee (over ${MAX_INPUT_FEE_BPS / 100}%)
|
|
1148
|
+
return reject(`the route takes ${(0, import_core.bigintToNumber)((spent - bridged) * 10000n / spent) / 100}% of the input as a fee (over ${MAX_INPUT_FEE_BPS / 100}%)`, "policy_refused");
|
|
891
1149
|
}
|
|
892
1150
|
if (solanaSource) {
|
|
893
|
-
if (!tr.data || tr.data.length < 64 || tr.data.length % 4 !== 0 || !BASE64.test(tr.data)) return reject("the Solana transaction is not base64");
|
|
894
|
-
if (tr.to !== void 0 || tr.value !== void 0 || tr.gasLimit !== void 0) return reject("the Solana transaction carries EVM fields");
|
|
1151
|
+
if (!tr.data || tr.data.length < 64 || tr.data.length % 4 !== 0 || !BASE64.test(tr.data)) return reject("the Solana transaction is not base64", "upstream_unavailable");
|
|
1152
|
+
if (tr.to !== void 0 || tr.value !== void 0 || tr.gasLimit !== void 0) return reject("the Solana transaction carries EVM fields", "upstream_unavailable");
|
|
895
1153
|
if (fromChain !== toChain) {
|
|
896
1154
|
const why = solanaBridgeTxMismatch(tr.data, fromAddress);
|
|
897
|
-
if (why) return reject(why);
|
|
1155
|
+
if (why) return reject(why, reasonOf(why));
|
|
898
1156
|
}
|
|
899
|
-
} else if (!isHexData(tr.data) || !normalizeAddress(tr.to) || !normalizeAddress(tr.from) || tr.chainId !== expectedFromChain || !tr.value || !tr.gasLimit || !trustedAddress(params.sourceChain, tr.to, trustedTransactionTargets) || !sameAddress(tr.from, fromAddress))
|
|
900
|
-
|
|
1157
|
+
} else if (!isHexData(tr.data) || !normalizeAddress(tr.to) || !normalizeAddress(tr.from) || tr.chainId !== expectedFromChain || !tr.value || !tr.gasLimit || !trustedAddress(params.sourceChain, tr.to, trustedTransactionTargets) || !sameAddress(tr.from, fromAddress)) {
|
|
1158
|
+
return reject(
|
|
1159
|
+
"the transaction is malformed or targets an untrusted contract",
|
|
1160
|
+
!isHexData(tr.data) || !normalizeAddress(tr.to) || !normalizeAddress(tr.from) || !tr.value || !tr.gasLimit ? "upstream_unavailable" : !trustedAddress(params.sourceChain, tr.to, trustedTransactionTargets) ? "vault_mismatch" : "policy_refused"
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
if (sentSlippage !== void 0 && (typeof action.slippage !== "number" || Math.abs(action.slippage - sentSlippage) > 1e-12)) return reject("the quote ignored the requested slippage", "price_check_failed");
|
|
901
1164
|
const sourceIsToken = fromToken !== EVM_ZERO && fromToken !== SOLANA_NATIVE;
|
|
902
1165
|
if (solanaSource) {
|
|
903
1166
|
} else if (sourceIsToken) {
|
|
904
|
-
if (!trustedAddress(params.sourceChain, est.approvalAddress, trustedApprovalAddresses)) return reject("the approval target is not a trusted LI.FI contract");
|
|
1167
|
+
if (!trustedAddress(params.sourceChain, est.approvalAddress, trustedApprovalAddresses)) return reject("the approval target is not a trusted LI.FI contract", "vault_mismatch");
|
|
905
1168
|
} else if (est.approvalAddress !== void 0) {
|
|
906
|
-
if (!trustedAddress(params.sourceChain, est.approvalAddress, trustedApprovalAddresses)) return reject("the approval target is not a trusted LI.FI contract");
|
|
1169
|
+
if (!trustedAddress(params.sourceChain, est.approvalAddress, trustedApprovalAddresses)) return reject("the approval target is not a trusted LI.FI contract", "vault_mismatch");
|
|
907
1170
|
}
|
|
908
1171
|
let txRequest;
|
|
1172
|
+
let nativeFee = 0n;
|
|
909
1173
|
if (solanaSource) {
|
|
910
1174
|
txRequest = { serializedTx: tr.data, value: sourceIsToken ? "0" : params.amountIn };
|
|
911
1175
|
} else {
|
|
912
1176
|
try {
|
|
913
|
-
if (BigInt(tr.value) < 0n || BigInt(tr.gasLimit) <= 0n) return reject("the transaction has a negative value or no gas limit");
|
|
1177
|
+
if (BigInt(tr.value) < 0n || BigInt(tr.gasLimit) <= 0n) return reject("the transaction has a negative value or no gas limit", "upstream_unavailable");
|
|
914
1178
|
} catch {
|
|
915
|
-
return reject("the transaction value or gas limit is not a number");
|
|
1179
|
+
return reject("the transaction value or gas limit is not a number", "upstream_unavailable");
|
|
916
1180
|
}
|
|
917
1181
|
const value = BigInt(tr.value);
|
|
918
|
-
|
|
919
|
-
if (!sourceIsToken && value < BigInt(params.amountIn)) return reject("the transaction sends less native coin than the input");
|
|
1182
|
+
nativeFee = sourceIsToken ? value : value > BigInt(params.amountIn) ? value - BigInt(params.amountIn) : 0n;
|
|
1183
|
+
if (!sourceIsToken && value < BigInt(params.amountIn)) return reject("the transaction sends less native coin than the input", "policy_refused");
|
|
920
1184
|
txRequest = {
|
|
921
1185
|
to: tr.to,
|
|
922
1186
|
data: tr.data,
|
|
@@ -928,7 +1192,7 @@ function lifiProvider(config = {}) {
|
|
|
928
1192
|
};
|
|
929
1193
|
}
|
|
930
1194
|
const feeUsd = (est.feeCosts ?? []).reduce((a, f) => a + Number(f.amountUSD ?? 0), 0);
|
|
931
|
-
if (!est.toAmountMin) return reject("the quote has no minimum output");
|
|
1195
|
+
if (!est.toAmountMin) return reject("the quote has no minimum output", "upstream_unavailable");
|
|
932
1196
|
const minDestAmount = est.toAmountMin;
|
|
933
1197
|
if (sentSlippage !== void 0) {
|
|
934
1198
|
const dest = BigInt(est.toAmount);
|
|
@@ -942,18 +1206,22 @@ function lifiProvider(config = {}) {
|
|
|
942
1206
|
continue;
|
|
943
1207
|
}
|
|
944
1208
|
params.onRefusal?.(
|
|
945
|
-
`lifi: this route's minimum is ${(effective * 100).toFixed(2)}% under the quote (LI.FI applies slippage on every step) \u2014 more than the requested ${slippageBps / 100}
|
|
1209
|
+
`lifi: this route's minimum is ${(effective * 100).toFixed(2)}% under the quote (LI.FI applies slippage on every step) \u2014 more than the requested ${slippageBps / 100}%`,
|
|
1210
|
+
{ reason: "price_check_failed" }
|
|
946
1211
|
);
|
|
947
1212
|
return null;
|
|
948
1213
|
}
|
|
949
1214
|
}
|
|
950
1215
|
if (params.minAmountOut !== void 0 && BigInt(minDestAmount) < BigInt(params.minAmountOut)) {
|
|
951
|
-
params.onRefusal?.(`lifi: floor breach (min=${minDestAmount} < floor=${params.minAmountOut})
|
|
1216
|
+
params.onRefusal?.(`lifi: floor breach (min=${minDestAmount} < floor=${params.minAmountOut})`, { reason: "price_check_failed" });
|
|
952
1217
|
return null;
|
|
953
1218
|
}
|
|
954
1219
|
const duration = est.executionDuration ?? 0;
|
|
955
1220
|
if (fromChain === toChain && duration > SAME_CHAIN_MAX_SECONDS) {
|
|
956
|
-
params.onRefusal?.(
|
|
1221
|
+
params.onRefusal?.(
|
|
1222
|
+
`lifi: same-chain route settles asynchronously (${duration} s via ${q.tool ?? "unknown tool"}) \u2014 a same-chain swap must deliver in the same transaction`,
|
|
1223
|
+
{ reason: "unsupported_pair" }
|
|
1224
|
+
);
|
|
957
1225
|
return null;
|
|
958
1226
|
}
|
|
959
1227
|
if (!solanaSource) {
|
|
@@ -968,7 +1236,7 @@ function lifiProvider(config = {}) {
|
|
|
968
1236
|
sameChain: fromChain === toChain,
|
|
969
1237
|
evmDestination: !isSolana(params.destChain)
|
|
970
1238
|
});
|
|
971
|
-
if (why) return reject(`the transaction does not match the quote: ${why}
|
|
1239
|
+
if (why) return reject(`the transaction does not match the quote: ${why}`, reasonOf(why) ?? "policy_refused");
|
|
972
1240
|
}
|
|
973
1241
|
return {
|
|
974
1242
|
provider: "lifi",
|
|
@@ -976,7 +1244,10 @@ function lifiProvider(config = {}) {
|
|
|
976
1244
|
intentId: `lifi:${fromChain}:${toChain}`,
|
|
977
1245
|
depositAddress: "",
|
|
978
1246
|
depositMemo: null,
|
|
979
|
-
|
|
1247
|
+
// Every check above ran on LI.FI's transaction; one built for a placeholder is not handed out (placeholderIn).
|
|
1248
|
+
// Its bridge fee on top of the input still is, as `providerData.nativeFee`, so the router's value nets it out
|
|
1249
|
+
// (usdValue reads it when there is no txRequest) instead of ranking the route on its gross output.
|
|
1250
|
+
...placeholder ? { providerData: { placeholder, ...nativeFee > 0n ? { nativeFee: nativeFee.toString() } : {} } } : { txRequest },
|
|
980
1251
|
// What the wallet spends (action.fromAmount, = the input); an input-side bridge fee is inside destAmount.
|
|
981
1252
|
sourceAmount: params.amountIn,
|
|
982
1253
|
destAmount: est.toAmount,
|
|
@@ -990,14 +1261,18 @@ function lifiProvider(config = {}) {
|
|
|
990
1261
|
validUntilMs: receivedAt + quoteTtlMs
|
|
991
1262
|
};
|
|
992
1263
|
} catch {
|
|
993
|
-
if (deadline?.aborted) params.onRefusal?.("lifi: no quote within 12 s");
|
|
1264
|
+
if (deadline?.aborted) params.onRefusal?.("lifi: no quote within 12 s", { reason: "upstream_unavailable" });
|
|
994
1265
|
return null;
|
|
995
1266
|
}
|
|
996
1267
|
return null;
|
|
997
1268
|
},
|
|
998
1269
|
async buildDeposit(quote) {
|
|
1270
|
+
const placeholder = quote.providerData?.placeholder;
|
|
1271
|
+
if (placeholder !== void 0) {
|
|
1272
|
+
throw (0, import_errors2.refusal)("policy_refused", `swap2: this LI.FI quote was priced for a placeholder ${String(placeholder).slice(0, 20)} (a dry quote without that address) and has no transaction to build \u2014 quote again with the sender and the recipient`);
|
|
1273
|
+
}
|
|
999
1274
|
const tx = quote.txRequest;
|
|
1000
|
-
if (!tx) throw
|
|
1275
|
+
if (!tx) throw (0, import_errors2.refusal)("policy_refused", "swap2: LI.FI quote has no transaction request");
|
|
1001
1276
|
const [, fromChain] = quote.intentId.split(":");
|
|
1002
1277
|
return {
|
|
1003
1278
|
chain: tx.serializedTx ? CAIP2_BY_LIFI_CHAIN[fromChain ?? ""] ?? "" : "",
|
|
@@ -1046,22 +1321,27 @@ function lifiProvider(config = {}) {
|
|
|
1046
1321
|
function lifiVerifyQuote(params, quote) {
|
|
1047
1322
|
const fromChain = LIFI_CHAIN[params.sourceChain];
|
|
1048
1323
|
const toChain = LIFI_CHAIN[params.destChain];
|
|
1049
|
-
if (!fromChain || !toChain) return "not a LI.FI chain pair";
|
|
1324
|
+
if (!fromChain || !toChain) return note("not a LI.FI chain pair", "unsupported_pair");
|
|
1325
|
+
const placeholder = params.dry !== false ? placeholderIn(params) : null;
|
|
1326
|
+
if (placeholder) {
|
|
1327
|
+
if (quote.txRequest !== void 0) return note(`a dry quote priced for a placeholder ${placeholder} carries a transaction \u2014 it would pay or refund that placeholder, not the wallet that signs it`, "recipient_mismatch");
|
|
1328
|
+
return priceOnlyMismatch(params, quote, `lifi:${fromChain}:${toChain}`);
|
|
1329
|
+
}
|
|
1050
1330
|
const tx = quote.txRequest;
|
|
1051
|
-
if (!tx) return "the quote carries no transaction";
|
|
1331
|
+
if (!tx) return note("the quote carries no transaction", "policy_refused");
|
|
1052
1332
|
if (isSolana(params.sourceChain)) {
|
|
1053
|
-
if (!tx.serializedTx || tx.data !== void 0 || tx.to !== void 0) return "a Solana source carries a serialized transaction only";
|
|
1333
|
+
if (!tx.serializedTx || tx.data !== void 0 || tx.to !== void 0) return note("a Solana source carries a serialized transaction only", "policy_refused");
|
|
1054
1334
|
return fromChain === toChain ? null : solanaBridgeTxMismatch(tx.serializedTx, params.refundAddress || (params.dry !== false ? SOLANA_PLACEHOLDER : ""));
|
|
1055
1335
|
}
|
|
1056
|
-
if (!isHexData(tx.data) || !tx.to || tx.serializedTx !== void 0) return "the quote carries no Diamond call";
|
|
1057
|
-
if (!sameAddress(tx.to, diamondFor(params.sourceChain))) return `the call targets ${tx.to}, not the LI.FI Diamond
|
|
1058
|
-
if (tx.chainId !== void 0 && String(tx.chainId) !== fromChain) return `the call is for chain ${tx.chainId}, not ${fromChain}
|
|
1336
|
+
if (!isHexData(tx.data) || !tx.to || tx.serializedTx !== void 0) return note("the quote carries no Diamond call", "policy_refused");
|
|
1337
|
+
if (!sameAddress(tx.to, diamondFor(params.sourceChain))) return note(`the call targets ${tx.to}, not the LI.FI Diamond`, "vault_mismatch");
|
|
1338
|
+
if (tx.chainId !== void 0 && String(tx.chainId) !== fromChain) return note(`the call is for chain ${tx.chainId}, not ${fromChain}`, "policy_refused");
|
|
1059
1339
|
const fromToken = lifiToken(params);
|
|
1060
|
-
if (!fromToken) return `unknown source token ${params.sourceToken}
|
|
1340
|
+
if (!fromToken) return note(`unknown source token ${params.sourceToken}`, "unsupported_pair");
|
|
1061
1341
|
const sourceIsToken = fromToken !== EVM_ZERO;
|
|
1062
1342
|
const dry = params.dry !== false;
|
|
1063
1343
|
const fromAddress = params.refundAddress || (dry ? EVM_PLACEHOLDER : "");
|
|
1064
|
-
const toAddress = params.destAddress || (dry ? fromAddress : "");
|
|
1344
|
+
const toAddress = params.destAddress || (dry ? dryRecipient(params, fromAddress) : "");
|
|
1065
1345
|
if (!fromAddress || !toAddress) return "a live quote needs the sender and the recipient";
|
|
1066
1346
|
let amountIn, minOut, value, nativeFee;
|
|
1067
1347
|
try {
|
|
@@ -1070,18 +1350,18 @@ function lifiVerifyQuote(params, quote) {
|
|
|
1070
1350
|
value = BigInt(tx.value ?? "0");
|
|
1071
1351
|
nativeFee = BigInt(tx.nativeFee ?? "0");
|
|
1072
1352
|
} catch {
|
|
1073
|
-
return "the quote's amounts are not integers";
|
|
1353
|
+
return note("the quote's amounts are not integers", "upstream_unavailable");
|
|
1074
1354
|
}
|
|
1075
|
-
if (BigInt(quote.sourceAmount) !== amountIn) return `the quote spends ${quote.sourceAmount}, not the ${amountIn} input
|
|
1076
|
-
if (params.minAmountOut !== void 0 && minOut < BigInt(params.minAmountOut)) return `the quoted minimum ${minOut} is below the caller's floor ${params.minAmountOut}
|
|
1355
|
+
if (BigInt(quote.sourceAmount) !== amountIn) return note(`the quote spends ${quote.sourceAmount}, not the ${amountIn} input`, "policy_refused");
|
|
1356
|
+
if (params.minAmountOut !== void 0 && minOut < BigInt(params.minAmountOut)) return note(`the quoted minimum ${minOut} is below the caller's floor ${params.minAmountOut}`, "price_check_failed");
|
|
1077
1357
|
if (sourceIsToken) {
|
|
1078
|
-
if (value !== nativeFee) return "a token input attaches only its declared bridge fee";
|
|
1079
|
-
if (!tx.approvalAddress || !sameAddress(tx.approvalAddress, diamondFor(params.sourceChain))) return "a token input approves the LI.FI Diamond";
|
|
1358
|
+
if (value !== nativeFee) return note("a token input attaches only its declared bridge fee", "policy_refused");
|
|
1359
|
+
if (!tx.approvalAddress || !sameAddress(tx.approvalAddress, diamondFor(params.sourceChain))) return note("a token input approves the LI.FI Diamond", "vault_mismatch");
|
|
1080
1360
|
} else {
|
|
1081
|
-
if (value < amountIn || value - amountIn !== nativeFee) return "a native input is paid as the input plus its declared bridge fee";
|
|
1082
|
-
if (tx.approvalAddress !== void 0) return "a native input approves nothing";
|
|
1361
|
+
if (value < amountIn || value - amountIn !== nativeFee) return note("a native input is paid as the input plus its declared bridge fee", "policy_refused");
|
|
1362
|
+
if (tx.approvalAddress !== void 0) return note("a native input approves nothing", "vault_mismatch");
|
|
1083
1363
|
}
|
|
1084
|
-
if (quote.intentId.replace(/~[0-9a-f]{32}$/, "") !== `lifi:${fromChain}:${toChain}`) return "the intent id does not name this route";
|
|
1364
|
+
if (quote.intentId.replace(/~[0-9a-f]{32}$/, "") !== `lifi:${fromChain}:${toChain}`) return note("the intent id does not name this route", "policy_refused");
|
|
1085
1365
|
const why = diamondCallMismatch(tx.data, {
|
|
1086
1366
|
fromToken,
|
|
1087
1367
|
toToken: nativeFor(params.destChain, params.destToken),
|
|
@@ -1093,11 +1373,26 @@ function lifiVerifyQuote(params, quote) {
|
|
|
1093
1373
|
sameChain: fromChain === toChain,
|
|
1094
1374
|
evmDestination: !isSolana(params.destChain)
|
|
1095
1375
|
});
|
|
1096
|
-
return why ? `the transaction does not match the request: ${why}
|
|
1376
|
+
return why ? note(`the transaction does not match the request: ${why}`, reasonOf(why) ?? "policy_refused") : null;
|
|
1377
|
+
}
|
|
1378
|
+
function priceOnlyMismatch(params, quote, route) {
|
|
1379
|
+
if (quote.depositAddress !== "") return note(`a LI.FI quote pays no deposit address, but this one names ${String(quote.depositAddress).slice(0, 64)}`, "vault_mismatch");
|
|
1380
|
+
const fee = quote.providerData?.nativeFee;
|
|
1381
|
+
if (fee !== void 0 && !/^\d{1,80}$/.test(String(fee))) return note(`the price-only quote declares a native fee of ${String(fee).slice(0, 40)}, not a base-unit integer`, "upstream_unavailable");
|
|
1382
|
+
try {
|
|
1383
|
+
if (BigInt(quote.sourceAmount) !== BigInt(params.amountIn)) return note(`the quote spends ${quote.sourceAmount}, not the ${params.amountIn} input`, "policy_refused");
|
|
1384
|
+
const minOut = BigInt(quote.minDestAmount);
|
|
1385
|
+
if (params.minAmountOut !== void 0 && minOut < BigInt(params.minAmountOut)) return note(`the quoted minimum ${minOut} is below the caller's floor ${params.minAmountOut}`, "price_check_failed");
|
|
1386
|
+
} catch {
|
|
1387
|
+
return note("the quote's amounts are not integers", "upstream_unavailable");
|
|
1388
|
+
}
|
|
1389
|
+
return quote.intentId.replace(/~[0-9a-f]{32}$/, "") === route ? null : note("the intent id does not name this route", "policy_refused");
|
|
1097
1390
|
}
|
|
1098
1391
|
var lifiPins = Object.freeze({
|
|
1099
1392
|
callTargets: (chain) => /^eip155:[0-9]+$/.test(chain) ? [diamondFor(chain)] : [],
|
|
1100
1393
|
spenders: (chain) => /^eip155:[0-9]+$/.test(chain) ? [diamondFor(chain)] : [],
|
|
1101
|
-
verifyQuote: lifiVerifyQuote
|
|
1394
|
+
verifyQuote: lifiVerifyQuote,
|
|
1395
|
+
/** The reason of a text {@link lifiVerifyQuote} returned (`remoteProvider` puts it on the stub's refusal). */
|
|
1396
|
+
reasonOf: (text) => reasonOf(text)
|
|
1102
1397
|
});
|
|
1103
1398
|
//# sourceMappingURL=provider.cjs.map
|