@anywalletkit/protocol-swap2-lifi 0.8.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/dist/{chunk-GWUGK4SG.js → chunk-G26GE46T.js} +230 -115
- package/dist/chunk-G26GE46T.js.map +1 -0
- package/dist/{chunk-OYGVC7DJ.js → chunk-TCPUWYDL.js} +2 -2
- package/dist/index.cjs +229 -114
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/protocol.cjs +229 -114
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.js +2 -2
- package/dist/provider.cjs +229 -114
- package/dist/provider.cjs.map +1 -1
- package/dist/provider.js +1 -1
- package/package.json +3 -3
- package/dist/chunk-GWUGK4SG.js.map +0 -1
- /package/dist/{chunk-OYGVC7DJ.js.map → chunk-TCPUWYDL.js.map} +0 -0
|
@@ -70,6 +70,12 @@ function associatedTokenAccount(owner, mint, tokenProgram = solanaKey(SOLANA_TOK
|
|
|
70
70
|
return programAddress([owner, tokenProgram, mint], solanaKey(ASSOCIATED_TOKEN_PROGRAM));
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
// src/refusals.ts
|
|
74
|
+
import { refusalNotes } from "@anywalletkit/protocol-swap2-core/errors";
|
|
75
|
+
var notes = refusalNotes();
|
|
76
|
+
var note = notes.note;
|
|
77
|
+
var reasonOf = notes.reasonOf;
|
|
78
|
+
|
|
73
79
|
// src/calldata.ts
|
|
74
80
|
var GENERIC_SWAPS = {
|
|
75
81
|
"0x4630a0d8": { single: false },
|
|
@@ -289,18 +295,18 @@ function swapsAt(r, base, head) {
|
|
|
289
295
|
function checkSteps(steps, e, finalAsset) {
|
|
290
296
|
const first = steps[0];
|
|
291
297
|
const last = steps[steps.length - 1];
|
|
292
|
-
if (!same(first.sendingAssetId, e.fromToken)) return `spends ${first.sendingAssetId}, not ${e.fromToken}
|
|
293
|
-
if (first.fromAmount === 0n || first.fromAmount > e.amountIn) return `swaps ${first.fromAmount}, not the ${e.amountIn} spent
|
|
298
|
+
if (!same(first.sendingAssetId, e.fromToken)) return note(`spends ${first.sendingAssetId}, not ${e.fromToken}`, "policy_refused");
|
|
299
|
+
if (first.fromAmount === 0n || first.fromAmount > e.amountIn) return note(`swaps ${first.fromAmount}, not the ${e.amountIn} spent`, "policy_refused");
|
|
294
300
|
let deposited = 0n;
|
|
295
301
|
for (let i = 0; i < steps.length; i++) {
|
|
296
302
|
const step = steps[i];
|
|
297
|
-
if (i > 0 && !same(step.sendingAssetId, steps[i - 1].receivingAssetId)) return `step ${i} spends ${step.sendingAssetId}, which step ${i - 1} does not deliver
|
|
303
|
+
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");
|
|
298
304
|
if (!step.requiresDeposit) continue;
|
|
299
|
-
if (!same(step.sendingAssetId, e.fromToken)) return `step ${i} pulls ${step.sendingAssetId} from the wallet, not the input ${e.fromToken}
|
|
305
|
+
if (!same(step.sendingAssetId, e.fromToken)) return note(`step ${i} pulls ${step.sendingAssetId} from the wallet, not the input ${e.fromToken}`, "policy_refused");
|
|
300
306
|
deposited += step.fromAmount;
|
|
301
307
|
}
|
|
302
|
-
if (deposited > e.amountIn) return `the steps pull ${deposited} from the wallet, more than the ${e.amountIn} spent
|
|
303
|
-
if (!same(last.receivingAssetId, finalAsset)) return `delivers ${last.receivingAssetId}, not ${finalAsset}
|
|
308
|
+
if (deposited > e.amountIn) return note(`the steps pull ${deposited} from the wallet, more than the ${e.amountIn} spent`, "policy_refused");
|
|
309
|
+
if (!same(last.receivingAssetId, finalAsset)) return note(`delivers ${last.receivingAssetId}, not ${finalAsset}`, "policy_refused");
|
|
304
310
|
return null;
|
|
305
311
|
}
|
|
306
312
|
var MAYAN_REFUND_AT = {
|
|
@@ -341,32 +347,32 @@ function acrossSwapRefunds(r, at) {
|
|
|
341
347
|
}
|
|
342
348
|
var ERC20_TRANSFER = "0xa9059cbb";
|
|
343
349
|
function ecoDelivery(r, at, e) {
|
|
344
|
-
if (kindOf(e.toToken) === "native") return "an Eco route to the native token cannot be checked here";
|
|
350
|
+
if (kindOf(e.toToken) === "native") return note("an Eco route to the native token cannot be checked here", "recipient_unverifiable");
|
|
345
351
|
const destination = r.word(at + 32 * 3);
|
|
346
|
-
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
|
|
352
|
+
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");
|
|
347
353
|
const route = r.bytes(at, at + 32 * 4);
|
|
348
354
|
const tuple = route.small(0);
|
|
349
355
|
const calls = tuple + route.small(tuple + 32 * 5);
|
|
350
356
|
const n = route.small(calls);
|
|
351
|
-
if (n === 0) return "the Eco route makes no calls on the destination chain";
|
|
352
|
-
if (n > 16) return `the Eco route makes ${n} calls on the destination chain, more than the 16 read here
|
|
357
|
+
if (n === 0) return note("the Eco route makes no calls on the destination chain", "recipient_unverifiable");
|
|
358
|
+
if (n > 16) return note(`the Eco route makes ${n} calls on the destination chain, more than the 16 read here`, "recipient_unverifiable");
|
|
353
359
|
const last = calls + 32 + route.small(calls + 32 + 32 * (n - 1));
|
|
354
360
|
const target = route.address(last);
|
|
355
361
|
const data = route.bytes(last, last + 32);
|
|
356
|
-
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";
|
|
362
|
+
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");
|
|
357
363
|
const to = data.address(4);
|
|
358
364
|
const amount = data.word(36);
|
|
359
|
-
if (!same(target, e.toToken)) return `the Eco route delivers ${target}, not ${e.toToken}
|
|
360
|
-
if (!same(to, e.receiver)) return `the Eco route pays ${to}, not ${e.receiver}
|
|
361
|
-
if (amount < e.minOut) return `the Eco route delivers ${amount}, below the ${e.minOut} shown
|
|
365
|
+
if (!same(target, e.toToken)) return note(`the Eco route delivers ${target}, not ${e.toToken}`, "policy_refused");
|
|
366
|
+
if (!same(to, e.receiver)) return note(`the Eco route pays ${to}, not ${e.receiver}`, "recipient_mismatch");
|
|
367
|
+
if (amount < e.minOut) return note(`the Eco route delivers ${amount}, below the ${e.minOut} shown`, "price_check_failed");
|
|
362
368
|
return null;
|
|
363
369
|
}
|
|
364
370
|
function m0Delivery(r, at, e) {
|
|
365
|
-
if (kindOf(e.toToken) === "native") return "an M0 order cannot deliver the native token";
|
|
371
|
+
if (kindOf(e.toToken) === "native") return note("an M0 order cannot deliver the native token", "unsupported_pair");
|
|
366
372
|
const tokenOut = r.address(at + 32 * 3);
|
|
367
373
|
const amountOut = r.word(at + 32 * 5);
|
|
368
|
-
if (!same(tokenOut, e.toToken)) return `the M0 order asks for ${tokenOut}, not ${e.toToken}
|
|
369
|
-
if (amountOut < e.minOut) return `the M0 order asks for ${amountOut}, below the ${e.minOut} shown
|
|
374
|
+
if (!same(tokenOut, e.toToken)) return note(`the M0 order asks for ${tokenOut}, not ${e.toToken}`, "policy_refused");
|
|
375
|
+
if (amountOut < e.minOut) return note(`the M0 order asks for ${amountOut}, below the ${e.minOut} shown`, "price_check_failed");
|
|
370
376
|
return null;
|
|
371
377
|
}
|
|
372
378
|
var FACET_DATA = {
|
|
@@ -400,13 +406,13 @@ function facetDataAt(r, bridge, spec) {
|
|
|
400
406
|
}
|
|
401
407
|
function refundMismatch(r, bridge, e) {
|
|
402
408
|
const spec = FACET_DATA[bridge.facet];
|
|
403
|
-
if (!spec) return NO_REFUND_FIELDS.has(bridge.facet) ? null : `no known refund layout for ${bridge.facet}
|
|
409
|
+
if (!spec) return NO_REFUND_FIELDS.has(bridge.facet) ? null : note(`no known refund layout for ${bridge.facet}`, "recipient_unverifiable");
|
|
404
410
|
const at = facetDataAt(r, bridge, spec);
|
|
405
411
|
const paid = (spec.refunds ?? []).map(([field, word]) => [`${bridge.facet} ${field}`, r.address(at + 32 * word), spec.zeroRefused === true]);
|
|
406
412
|
paid.push(...spec.nested?.(r, at) ?? []);
|
|
407
413
|
for (const [what, to, zeroRefused] of paid) {
|
|
408
414
|
if (zeroRefused && /^0x0{40}$/.test(to)) continue;
|
|
409
|
-
if (!same(to, e.sender) && !same(to, e.receiver)) return `${what} pays ${to}, neither the sender ${e.sender} nor the recipient
|
|
415
|
+
if (!same(to, e.sender) && !same(to, e.receiver)) return note(`${what} pays ${to}, neither the sender ${e.sender} nor the recipient`, "recipient_mismatch");
|
|
410
416
|
}
|
|
411
417
|
return null;
|
|
412
418
|
}
|
|
@@ -438,36 +444,36 @@ var hexOf = (b) => `0x${Array.from(b, (x) => x.toString(16).padStart(2, "0")).jo
|
|
|
438
444
|
var keyOf = (word) => base58(Uint8Array.from(word.slice(2).match(/../g) ?? [], (h) => Number.parseInt(h, 16)));
|
|
439
445
|
function mayanToSolana(r, at, t) {
|
|
440
446
|
const declared = r.raw(at, 32);
|
|
441
|
-
if (declared !== t.ownerHex) return `Mayan's nonEVMReceiver is ${keyOf(declared)}, not the recipient ${t.e.receiver}
|
|
447
|
+
if (declared !== t.ownerHex) return note(`Mayan's nonEVMReceiver is ${keyOf(declared)}, not the recipient ${t.e.receiver}`, "recipient_mismatch");
|
|
442
448
|
const p = r.bytes(at, at + 32 * 2);
|
|
443
449
|
const selector = p.raw(0, 4);
|
|
444
450
|
const o = MAYAN_SOLANA[selector];
|
|
445
|
-
if (!o) return `the Mayan call ${selector} is an entry point whose Solana recipient cannot be checked here
|
|
451
|
+
if (!o) return note(`the Mayan call ${selector} is an entry point whose Solana recipient cannot be checked here`, "recipient_unverifiable");
|
|
446
452
|
const word = (i) => 4 + 32 * i;
|
|
447
|
-
if (o.payloadType && p.word(word(o.payloadType[0])) !== o.payloadType[1]) return `the Mayan call ${selector} carries payload type ${p.word(word(o.payloadType[0]))}, not a plain delivery
|
|
453
|
+
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");
|
|
448
454
|
const dest = p.raw(word(o.destAddr), 32);
|
|
449
|
-
if (dest !== t.ownerHex) return `the Mayan order pays ${keyOf(dest)} on Solana, not the recipient ${t.e.receiver}
|
|
455
|
+
if (dest !== t.ownerHex) return note(`the Mayan order pays ${keyOf(dest)} on Solana, not the recipient ${t.e.receiver}`, "recipient_mismatch");
|
|
450
456
|
const chain = p.word(word(o.chain));
|
|
451
|
-
if (chain !== (o.cctp ? CCTP_DOMAIN_SOLANA : WORMHOLE_SOLANA)) return `the Mayan order goes to ${o.cctp ? "CCTP domain" : "Wormhole chain"} ${chain}, not Solana
|
|
452
|
-
if (o.tokenOut === void 0) return t.e.toToken === SOLANA_USDC ? null : `a Mayan CCTP transfer delivers USDC, not ${t.e.toToken}
|
|
457
|
+
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");
|
|
458
|
+
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");
|
|
453
459
|
const token = p.raw(word(o.tokenOut), 32);
|
|
454
|
-
return token === t.mintHex ? null : `the Mayan order delivers ${keyOf(token)}, not ${t.e.toToken}
|
|
460
|
+
return token === t.mintHex ? null : note(`the Mayan order delivers ${keyOf(token)}, not ${t.e.toToken}`, "policy_refused");
|
|
455
461
|
}
|
|
456
462
|
function polymerToSolana(r, at, t) {
|
|
457
463
|
const declared = r.raw(at + 32 * 2, 32);
|
|
458
|
-
if (declared !== t.ownerHex) return `Polymer CCTP's nonEVMReceiver is ${keyOf(declared)}, not the recipient ${t.e.receiver}
|
|
459
|
-
if (t.e.toToken !== SOLANA_USDC) return `Polymer CCTP delivers USDC, not ${t.e.toToken}
|
|
464
|
+
if (declared !== t.ownerHex) return note(`Polymer CCTP's nonEVMReceiver is ${keyOf(declared)}, not the recipient ${t.e.receiver}`, "recipient_mismatch");
|
|
465
|
+
if (t.e.toToken !== SOLANA_USDC) return note(`Polymer CCTP delivers USDC, not ${t.e.toToken}`, "policy_refused");
|
|
460
466
|
const mintTo = r.raw(at + 32 * 3, 32);
|
|
461
467
|
const account = hexOf(associatedTokenAccount(t.owner, t.mint));
|
|
462
|
-
return mintTo === account ? null : `the Polymer CCTP transfer mints to ${keyOf(mintTo)}, not the recipient's USDC account ${keyOf(account)}
|
|
468
|
+
return mintTo === account ? null : note(`the Polymer CCTP transfer mints to ${keyOf(mintTo)}, not the recipient's USDC account ${keyOf(account)}`, "recipient_mismatch");
|
|
463
469
|
}
|
|
464
470
|
function chainflipToSolana(r, at, t) {
|
|
465
471
|
const dst = r.bytes(at, at);
|
|
466
|
-
if (dst.length !== 32) return `Chainflip pays a ${dst.length}-byte address on Solana, not the recipient ${t.e.receiver}
|
|
467
|
-
if (dst.raw(0, 32) !== t.ownerHex) return `Chainflip pays ${keyOf(dst.raw(0, 32))} on Solana, not the recipient ${t.e.receiver}
|
|
472
|
+
if (dst.length !== 32) return note(`Chainflip pays a ${dst.length}-byte address on Solana, not the recipient ${t.e.receiver}`, "recipient_mismatch");
|
|
473
|
+
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");
|
|
468
474
|
const asset = r.word(at + 32);
|
|
469
475
|
const want = CHAINFLIP_SOLANA_ASSETS[t.e.toToken];
|
|
470
|
-
return want !== void 0 && asset === want ? null : `Chainflip delivers its asset ${asset}, not ${t.e.toToken}
|
|
476
|
+
return want !== void 0 && asset === want ? null : note(`Chainflip delivers its asset ${asset}, not ${t.e.toToken}`, "policy_refused");
|
|
471
477
|
}
|
|
472
478
|
var SOLANA_DELIVERY = {
|
|
473
479
|
Mayan: mayanToSolana,
|
|
@@ -478,16 +484,16 @@ var INTENTS_TO_SOLANA = "a LI.FI Intents order to Solana names its own oracles,
|
|
|
478
484
|
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";
|
|
479
485
|
var INTENTS_FACET = "LiFiIntentEscrowV2";
|
|
480
486
|
function solanaRecipientMismatch(r, bridge, receiver, e) {
|
|
481
|
-
if (e.destChainId !== LIFI_SOLANA_CHAIN_ID) return NON_EVM_UNREAD;
|
|
482
|
-
if (!same(receiver, NON_EVM_ADDRESS)) return `BridgeData pays ${receiver}, not LI.FI's non-EVM placeholder ${NON_EVM_ADDRESS}, on a Solana route
|
|
483
|
-
if (DEPOSIT_FACETS.has(bridge.facet)) return `${bridge.facet} pays a deposit address whose off-chain order names the Solana recipient \u2014 the transaction does not bind it
|
|
484
|
-
if (bridge.facet === INTENTS_FACET) return INTENTS_TO_SOLANA;
|
|
487
|
+
if (e.destChainId !== LIFI_SOLANA_CHAIN_ID) return note(NON_EVM_UNREAD, "recipient_unverifiable");
|
|
488
|
+
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");
|
|
489
|
+
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");
|
|
490
|
+
if (bridge.facet === INTENTS_FACET) return note(INTENTS_TO_SOLANA, "recipient_unverifiable");
|
|
485
491
|
const read = SOLANA_DELIVERY[bridge.facet];
|
|
486
|
-
if (!read) return NON_EVM_UNREAD;
|
|
492
|
+
if (!read) return note(NON_EVM_UNREAD, "recipient_unverifiable");
|
|
487
493
|
const owner = solanaKey(e.receiver);
|
|
488
|
-
if (!owner) return `the recipient ${e.receiver} is not a Solana address
|
|
494
|
+
if (!owner) return note(`the recipient ${e.receiver} is not a Solana address`, "recipient_mismatch");
|
|
489
495
|
const mint = solanaKey(e.toToken);
|
|
490
|
-
if (!mint) return `the destination token ${e.toToken} is not a Solana mint
|
|
496
|
+
if (!mint) return note(`the destination token ${e.toToken} is not a Solana mint`, "unsupported_pair");
|
|
491
497
|
return read(r, facetDataAt(r, bridge, FACET_DATA[bridge.facet]), { owner, ownerHex: hexOf(owner), mint, mintHex: hexOf(mint), e });
|
|
492
498
|
}
|
|
493
499
|
function diamondCallMismatch(data, e) {
|
|
@@ -495,61 +501,61 @@ function diamondCallMismatch(data, e) {
|
|
|
495
501
|
try {
|
|
496
502
|
r = new Reader(data);
|
|
497
503
|
} catch {
|
|
498
|
-
return "the calldata is not hex";
|
|
504
|
+
return note("the calldata is not hex", "upstream_unavailable");
|
|
499
505
|
}
|
|
500
|
-
if (r.length < 4 + 32 * 2) return "the calldata is too short";
|
|
506
|
+
if (r.length < 4 + 32 * 2) return note("the calldata is too short", "upstream_unavailable");
|
|
501
507
|
const selector = data.slice(0, 10).toLowerCase();
|
|
502
508
|
const generic = GENERIC_SWAPS[selector];
|
|
503
509
|
try {
|
|
504
510
|
if (generic) {
|
|
505
|
-
if (!e.sameChain) return "a same-chain swap call on a cross-chain route";
|
|
511
|
+
if (!e.sameChain) return note("a same-chain swap call on a cross-chain route", "policy_refused");
|
|
506
512
|
const receiver2 = r.address(4 + 96);
|
|
507
513
|
const minAmount2 = r.word(4 + 128);
|
|
508
514
|
const steps = generic.single ? [swapAt(r, 4 + r.small(4 + 160))] : swapsAt(r, 4, 4 + 160);
|
|
509
|
-
if (!e.evmDestination || !same(receiver2, e.receiver)) return `pays ${receiver2}, not ${e.receiver}
|
|
510
|
-
if (minAmount2 < e.minOut) return `accepts ${minAmount2}, below the ${e.minOut} shown
|
|
511
|
-
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"}
|
|
515
|
+
if (!e.evmDestination || !same(receiver2, e.receiver)) return note(`pays ${receiver2}, not ${e.receiver}`, "recipient_mismatch");
|
|
516
|
+
if (minAmount2 < e.minOut) return note(`accepts ${minAmount2}, below the ${e.minOut} shown`, "price_check_failed");
|
|
517
|
+
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");
|
|
512
518
|
if (generic.to && (kindOf(e.toToken) !== generic.to || kindOf(steps[steps.length - 1].receivingAssetId) !== generic.to)) {
|
|
513
|
-
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
|
|
519
|
+
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");
|
|
514
520
|
}
|
|
515
521
|
return checkSteps(steps, e, e.toToken);
|
|
516
522
|
}
|
|
517
523
|
const unbound = UNBOUND_BRIDGES[selector];
|
|
518
|
-
if (unbound) return `${unbound} delivers to a recipient the transaction does not bind (an off-chain order or unchecked facet data)
|
|
524
|
+
if (unbound) return note(`${unbound} delivers to a recipient the transaction does not bind (an off-chain order or unchecked facet data)`, "recipient_unverifiable");
|
|
519
525
|
const bridge = BRIDGE_CALLS[selector];
|
|
520
|
-
if (!bridge) return `unknown Diamond call ${selector}
|
|
521
|
-
if (bridge.facet === INTENTS_FACET && e.evmDestination) return INTENTS_UNPINNED;
|
|
526
|
+
if (!bridge) return note(`unknown Diamond call ${selector}`, "policy_refused");
|
|
527
|
+
if (bridge.facet === INTENTS_FACET && e.evmDestination) return note(INTENTS_UNPINNED, "recipient_unverifiable");
|
|
522
528
|
const head = r.small(4);
|
|
523
|
-
if (head < (bridge.sourceSwaps ? 96 : 64) || head % 32 !== 0 || head > 32 * 32) return `unknown Diamond call ${selector}
|
|
529
|
+
if (head < (bridge.sourceSwaps ? 96 : 64) || head % 32 !== 0 || head > 32 * 32) return note(`unknown Diamond call ${selector}`, "policy_refused");
|
|
524
530
|
const at = 4 + head;
|
|
525
|
-
if (r.small(at + 32) !== 320) return `unknown Diamond call ${selector}
|
|
531
|
+
if (r.small(at + 32) !== 320) return note(`unknown Diamond call ${selector}`, "policy_refused");
|
|
526
532
|
const sendingAssetId = r.address(at + 128);
|
|
527
533
|
const receiver = r.address(at + 160);
|
|
528
534
|
const minAmount = r.word(at + 192);
|
|
529
535
|
const destinationChainId = r.word(at + 224);
|
|
530
536
|
const hasSourceSwaps = r.bool(at + 256);
|
|
531
537
|
const hasDestinationCall = r.bool(at + 288);
|
|
532
|
-
if (e.sameChain) return "a bridge call on a same-chain route";
|
|
533
|
-
if (destinationChainId !== e.destChainId) return `bridges to chain ${destinationChainId}, not ${e.destChainId}
|
|
534
|
-
if (hasDestinationCall) return "the route runs a call on the destination chain, whose recipient cannot be checked here";
|
|
538
|
+
if (e.sameChain) return note("a bridge call on a same-chain route", "policy_refused");
|
|
539
|
+
if (destinationChainId !== e.destChainId) return note(`bridges to chain ${destinationChainId}, not ${e.destChainId}`, "policy_refused");
|
|
540
|
+
if (hasDestinationCall) return note("the route runs a call on the destination chain, whose recipient cannot be checked here", "recipient_unverifiable");
|
|
535
541
|
if (!e.evmDestination) {
|
|
536
542
|
const why = solanaRecipientMismatch(r, bridge, receiver, e);
|
|
537
543
|
if (why) return why;
|
|
538
|
-
} else if (!same(receiver, e.receiver)) return `pays ${receiver}, not ${e.receiver}
|
|
539
|
-
if (hasSourceSwaps !== bridge.sourceSwaps) return `unknown Diamond call ${selector}
|
|
544
|
+
} else if (!same(receiver, e.receiver)) return note(`pays ${receiver}, not ${e.receiver}`, "recipient_mismatch");
|
|
545
|
+
if (hasSourceSwaps !== bridge.sourceSwaps) return note(`unknown Diamond call ${selector}`, "policy_refused");
|
|
540
546
|
const refund = refundMismatch(r, bridge, e);
|
|
541
547
|
if (refund) return refund;
|
|
542
548
|
const spec = FACET_DATA[bridge.facet];
|
|
543
549
|
const delivery = spec?.delivery?.(r, facetDataAt(r, bridge, spec), e);
|
|
544
550
|
if (delivery) return delivery;
|
|
545
551
|
if (hasSourceSwaps) {
|
|
546
|
-
if (minAmount === 0n) return "bridges nothing after the source swaps";
|
|
552
|
+
if (minAmount === 0n) return note("bridges nothing after the source swaps", "policy_refused");
|
|
547
553
|
return checkSteps(swapsAt(r, 4, 4 + 32), e, sendingAssetId);
|
|
548
554
|
}
|
|
549
|
-
if (minAmount > e.amountIn || minAmount * 10000n < e.amountIn * (10000n - BRIDGE_INPUT_FEE_MAX_BPS)) return `bridges ${minAmount}, not the ${e.amountIn} spent
|
|
550
|
-
return same(sendingAssetId, e.fromToken) ? null : `bridges ${sendingAssetId}, not ${e.fromToken}
|
|
555
|
+
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");
|
|
556
|
+
return same(sendingAssetId, e.fromToken) ? null : note(`bridges ${sendingAssetId}, not ${e.fromToken}`, "policy_refused");
|
|
551
557
|
} catch (err) {
|
|
552
|
-
return `the Diamond calldata does not decode (${err instanceof Error ? err.message : String(err)})
|
|
558
|
+
return note(`the Diamond calldata does not decode (${err instanceof Error ? err.message : String(err)})`, "policy_refused");
|
|
553
559
|
}
|
|
554
560
|
}
|
|
555
561
|
|
|
@@ -598,7 +604,7 @@ var Cursor = class {
|
|
|
598
604
|
};
|
|
599
605
|
function solanaBridgeTxMismatch(base64, sender) {
|
|
600
606
|
const bytes = base64Bytes(base64);
|
|
601
|
-
if (!bytes) return "the Solana transaction is not base64";
|
|
607
|
+
if (!bytes) return note("the Solana transaction is not base64", "upstream_unavailable");
|
|
602
608
|
const c = new Cursor(bytes);
|
|
603
609
|
try {
|
|
604
610
|
c.bytes(64 * c.short());
|
|
@@ -606,7 +612,7 @@ function solanaBridgeTxMismatch(base64, sender) {
|
|
|
606
612
|
if ((first & 128) === 0) c.at--;
|
|
607
613
|
c.bytes(3);
|
|
608
614
|
const keys = Array.from({ length: c.short() }, () => base58(c.bytes(32)));
|
|
609
|
-
if (keys[0] !== sender) return `the Solana transaction is paid by ${keys[0] ?? "nobody"}, not the sender ${sender}
|
|
615
|
+
if (keys[0] !== sender) return note(`the Solana transaction is paid by ${keys[0] ?? "nobody"}, not the sender ${sender}`, "policy_refused");
|
|
610
616
|
c.bytes(32);
|
|
611
617
|
let mayan = false;
|
|
612
618
|
const payees = [];
|
|
@@ -621,15 +627,16 @@ function solanaBridgeTxMismatch(base64, sender) {
|
|
|
621
627
|
if (to !== void 0 && to !== sender && to !== LIFI_SOLANA_FEE_WALLET) payees.push(to);
|
|
622
628
|
}
|
|
623
629
|
}
|
|
624
|
-
return mayan && payees.length ? `the Solana transaction transfers SOL to ${payees[0]}, which is neither the sender nor LI.FI's fee wallet
|
|
630
|
+
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;
|
|
625
631
|
} catch (err) {
|
|
626
|
-
return `the Solana transaction does not decode (${err instanceof Error ? err.message : String(err)})
|
|
632
|
+
return note(`the Solana transaction does not decode (${err instanceof Error ? err.message : String(err)})`, "policy_refused");
|
|
627
633
|
}
|
|
628
634
|
}
|
|
629
635
|
|
|
630
636
|
// src/provider.ts
|
|
631
637
|
import { timeoutSignal, sourceAssetOf } from "@anywalletkit/protocol-swap2-core/util";
|
|
632
638
|
import { slippageFloor } from "@anywalletkit/protocol-swap2-core/validate";
|
|
639
|
+
import { httpRefusal, parseRetryAfter, refusal } from "@anywalletkit/protocol-swap2-core/errors";
|
|
633
640
|
import { bigintToNumber } from "@anywalletkit/core";
|
|
634
641
|
var DEFAULT_LIFI_BASE = "https://li.quest/v1";
|
|
635
642
|
var LIFI_CHAIN = {
|
|
@@ -807,6 +814,92 @@ function routeOptions(body, excluded, allowed) {
|
|
|
807
814
|
}
|
|
808
815
|
return out;
|
|
809
816
|
}
|
|
817
|
+
var LIFI_TOOL_REASONS = {
|
|
818
|
+
NO_POSSIBLE_ROUTE: "no_route",
|
|
819
|
+
INSUFFICIENT_LIQUIDITY: "insufficient_liquidity",
|
|
820
|
+
TOOL_TIMEOUT: "upstream_unavailable",
|
|
821
|
+
RPC_ERROR: "upstream_unavailable",
|
|
822
|
+
AMOUNT_TOO_LOW: "amount_too_small",
|
|
823
|
+
AMOUNT_TOO_HIGH: "amount_too_large",
|
|
824
|
+
FEES_HIGHER_THAN_AMOUNT: "amount_too_small",
|
|
825
|
+
FEES_HGHER_THAN_AMOUNT: "amount_too_small",
|
|
826
|
+
// an older spelling of the same code
|
|
827
|
+
DIFFERENT_RECIPIENT_NOT_SUPPORTED: "unsupported_pair",
|
|
828
|
+
CANNOT_GUARANTEE_MIN_AMOUNT: "price_check_failed"
|
|
829
|
+
};
|
|
830
|
+
var LIFI_ERROR_REASONS = {
|
|
831
|
+
1001: "no_route",
|
|
832
|
+
// FailedToBuildTransactionError: "None of the available routes could successfully generate a tx"
|
|
833
|
+
1002: "no_route",
|
|
834
|
+
// NoQuoteError: "No available quotes for the requested transfer"
|
|
835
|
+
1003: "no_route",
|
|
836
|
+
// NotFoundError
|
|
837
|
+
1005: "rate_limited",
|
|
838
|
+
// RateLimitError
|
|
839
|
+
1006: "upstream_unavailable",
|
|
840
|
+
// ServerError
|
|
841
|
+
1007: "price_check_failed",
|
|
842
|
+
// SlippageError
|
|
843
|
+
1008: "upstream_unavailable",
|
|
844
|
+
// ThirdPartyError
|
|
845
|
+
1009: "upstream_unavailable",
|
|
846
|
+
// TimeoutError
|
|
847
|
+
1010: "key_rejected",
|
|
848
|
+
// UnauthorizedError
|
|
849
|
+
1012: "upstream_unavailable"
|
|
850
|
+
// RpcFailure
|
|
851
|
+
};
|
|
852
|
+
var LIFI_MESSAGE_REASONS = [
|
|
853
|
+
[/no available quotes|unable to find a quote|none of the available routes|no route/i, "no_route"],
|
|
854
|
+
[/rate limit|too many requests/i, "rate_limited"],
|
|
855
|
+
[/api[ -]?key|unauthori[sz]ed/i, "key_rejected"],
|
|
856
|
+
[/insufficient liquidity|not enough liquidity/i, "insufficient_liquidity"],
|
|
857
|
+
[/amount (?:is )?too (?:low|small)|below the minimum/i, "amount_too_small"],
|
|
858
|
+
[/amount (?:is )?too (?:high|large)|above the maximum/i, "amount_too_large"],
|
|
859
|
+
[/not supported|unsupported|unknown token|invalid token/i, "unsupported_pair"],
|
|
860
|
+
[/halted|paused|maintenance/i, "chain_halted"]
|
|
861
|
+
];
|
|
862
|
+
function lifiToolCodes(errors) {
|
|
863
|
+
const out = /* @__PURE__ */ new Set();
|
|
864
|
+
const stack = [errors];
|
|
865
|
+
for (let visited = 0; stack.length > 0 && visited < 256; visited++) {
|
|
866
|
+
const node = stack.pop();
|
|
867
|
+
if (Array.isArray(node)) {
|
|
868
|
+
stack.push(...node.slice(0, 64));
|
|
869
|
+
continue;
|
|
870
|
+
}
|
|
871
|
+
if (!node || typeof node !== "object") continue;
|
|
872
|
+
const code = node.code;
|
|
873
|
+
if (typeof code === "string" && Object.prototype.hasOwnProperty.call(LIFI_TOOL_REASONS, code)) out.add(code);
|
|
874
|
+
for (const [key, value] of Object.entries(node).slice(0, 64)) if (key !== "action" && value && typeof value === "object") stack.push(value);
|
|
875
|
+
}
|
|
876
|
+
return [...out];
|
|
877
|
+
}
|
|
878
|
+
function lifiBodyReason(body, filteredOut) {
|
|
879
|
+
const b = body && typeof body === "object" ? body : {};
|
|
880
|
+
const tools = [...new Set(lifiToolCodes(b.errors).map((code) => LIFI_TOOL_REASONS[code]))];
|
|
881
|
+
if (tools.length === 1) return tools[0];
|
|
882
|
+
if (filteredOut.length > 0 && filteredOut.every((r) => /^ROUTE_SLIPPAGE_FLOOR_UNREACHABLE/.test(r))) return "price_check_failed";
|
|
883
|
+
const byCode = typeof b.code === "number" ? LIFI_ERROR_REASONS[b.code] : void 0;
|
|
884
|
+
if (byCode) return byCode;
|
|
885
|
+
const message = typeof b.message === "string" ? b.message : "";
|
|
886
|
+
return LIFI_MESSAGE_REASONS.find(([pattern]) => pattern.test(message))?.[1];
|
|
887
|
+
}
|
|
888
|
+
var LIFI_TRANSIENT_REASONS = /* @__PURE__ */ new Set(["rate_limited", "insufficient_liquidity", "chain_halted"]);
|
|
889
|
+
function lifiHttpRefusal(status, retryAfter, body, filteredOut) {
|
|
890
|
+
const http = httpRefusal(status, retryAfter);
|
|
891
|
+
if (http && http.reason !== "upstream_unavailable") return http;
|
|
892
|
+
const info = (reason) => {
|
|
893
|
+
if (reason === "rate_limited") {
|
|
894
|
+
const wait = parseRetryAfter(retryAfter);
|
|
895
|
+
return { reason, httpStatus: status, ...wait !== void 0 ? { retryAfterMs: wait } : {} };
|
|
896
|
+
}
|
|
897
|
+
return reason === "upstream_unavailable" || reason === "key_rejected" ? { reason, httpStatus: status } : { reason };
|
|
898
|
+
};
|
|
899
|
+
const byBody = lifiBodyReason(body, filteredOut);
|
|
900
|
+
if (http) return byBody !== void 0 && LIFI_TRANSIENT_REASONS.has(byBody) ? info(byBody) : http;
|
|
901
|
+
return byBody !== void 0 ? info(byBody) : { reason: "no_route" };
|
|
902
|
+
}
|
|
810
903
|
function cleanChoice(choice) {
|
|
811
904
|
if (!choice) return null;
|
|
812
905
|
const allow = (choice.allowBridges ?? []).filter((t) => TOOL_KEY.test(t));
|
|
@@ -847,8 +940,10 @@ function lifiProvider(config = {}) {
|
|
|
847
940
|
return !!LIFI_CHAIN[from.chain] && !!LIFI_CHAIN[to.chain];
|
|
848
941
|
},
|
|
849
942
|
async getQuote(params, signal) {
|
|
850
|
-
const reject = (why) => {
|
|
851
|
-
|
|
943
|
+
const reject = (why, reason) => {
|
|
944
|
+
const info = typeof reason === "string" ? { reason } : reason;
|
|
945
|
+
if (info) params.onRefusal?.(`lifi: ${why}`, info);
|
|
946
|
+
else params.onRefusal?.(`lifi: ${why}`);
|
|
852
947
|
return null;
|
|
853
948
|
};
|
|
854
949
|
if (params.flex) return null;
|
|
@@ -940,7 +1035,10 @@ function lifiProvider(config = {}) {
|
|
|
940
1035
|
continue;
|
|
941
1036
|
}
|
|
942
1037
|
const why = reasons.length === 1 ? ` (${reasons[0].slice(0, 200)})` : "";
|
|
943
|
-
params.onRefusal?.(
|
|
1038
|
+
params.onRefusal?.(
|
|
1039
|
+
`lifi: ${typeof body?.message === "string" ? body.message.slice(0, 200) : `HTTP ${res.status}`}${why}`,
|
|
1040
|
+
lifiHttpRefusal(res.status, res.headers?.get?.("retry-after"), body, reasons)
|
|
1041
|
+
);
|
|
944
1042
|
return null;
|
|
945
1043
|
}
|
|
946
1044
|
const q = await res.json();
|
|
@@ -949,7 +1047,7 @@ function lifiProvider(config = {}) {
|
|
|
949
1047
|
const steps = q.includedSteps;
|
|
950
1048
|
const tools = [q.tool, ...Array.isArray(steps) ? steps.map((i) => i?.tool) : []];
|
|
951
1049
|
if (tools.some((t) => typeof t === "string" && SMART_DEPOSITS_TOOL.test(t))) {
|
|
952
|
-
return reject("LI.FI Smart Deposits pay a deposit address whose recipient the transaction does not bind \u2014 refused for a Solana destination");
|
|
1050
|
+
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");
|
|
953
1051
|
}
|
|
954
1052
|
}
|
|
955
1053
|
const action = q.action;
|
|
@@ -958,7 +1056,7 @@ function lifiProvider(config = {}) {
|
|
|
958
1056
|
const expectedToToken = nativeFor(params.destChain, params.destToken);
|
|
959
1057
|
const expectedFromChain = Number(fromChain);
|
|
960
1058
|
const expectedToChain = Number(toChain);
|
|
961
|
-
if (q.type !== "lifi" || !q.id || !action || !est?.toAmount || !est.fromAmount || !tr) return reject("incomplete quote (no action, estimate or transaction)");
|
|
1059
|
+
if (q.type !== "lifi" || !q.id || !action || !est?.toAmount || !est.fromAmount || !tr) return reject("incomplete quote (no action, estimate or transaction)", "upstream_unavailable");
|
|
962
1060
|
const mismatch = [
|
|
963
1061
|
action.fromChainId !== expectedFromChain && "source chain",
|
|
964
1062
|
action.toChainId !== expectedToChain && "destination chain",
|
|
@@ -968,28 +1066,39 @@ function lifiProvider(config = {}) {
|
|
|
968
1066
|
!sameChainAddress(params.sourceChain, action.fromAddress, fromAddress) && "sender",
|
|
969
1067
|
!sameChainAddress(params.destChain, action.toAddress, toAddress) && "recipient"
|
|
970
1068
|
].filter(Boolean);
|
|
971
|
-
if (mismatch.length)
|
|
1069
|
+
if (mismatch.length) {
|
|
1070
|
+
return reject(
|
|
1071
|
+
`quote does not match the request (${mismatch.join(", ")})`,
|
|
1072
|
+
// LI.FI refunds to the sender it was asked for (fromAddress): either address differing pays or refunds someone else.
|
|
1073
|
+
mismatch.includes("recipient") || mismatch.includes("sender") ? "recipient_mismatch" : "policy_refused"
|
|
1074
|
+
);
|
|
1075
|
+
}
|
|
972
1076
|
const bridged = BigInt(est.fromAmount);
|
|
973
1077
|
const spent = BigInt(params.amountIn);
|
|
974
|
-
if (bridged > spent) return reject(`the estimate bridges ${est.fromAmount}, more than the ${params.amountIn} spent
|
|
1078
|
+
if (bridged > spent) return reject(`the estimate bridges ${est.fromAmount}, more than the ${params.amountIn} spent`, "price_check_failed");
|
|
975
1079
|
if ((spent - bridged) * 10000n > spent * BigInt(MAX_INPUT_FEE_BPS)) {
|
|
976
|
-
return reject(`the route takes ${bigintToNumber((spent - bridged) * 10000n / spent) / 100}% of the input as a fee (over ${MAX_INPUT_FEE_BPS / 100}%)
|
|
1080
|
+
return reject(`the route takes ${bigintToNumber((spent - bridged) * 10000n / spent) / 100}% of the input as a fee (over ${MAX_INPUT_FEE_BPS / 100}%)`, "policy_refused");
|
|
977
1081
|
}
|
|
978
1082
|
if (solanaSource) {
|
|
979
|
-
if (!tr.data || tr.data.length < 64 || tr.data.length % 4 !== 0 || !BASE64.test(tr.data)) return reject("the Solana transaction is not base64");
|
|
980
|
-
if (tr.to !== void 0 || tr.value !== void 0 || tr.gasLimit !== void 0) return reject("the Solana transaction carries EVM fields");
|
|
1083
|
+
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");
|
|
1084
|
+
if (tr.to !== void 0 || tr.value !== void 0 || tr.gasLimit !== void 0) return reject("the Solana transaction carries EVM fields", "upstream_unavailable");
|
|
981
1085
|
if (fromChain !== toChain) {
|
|
982
1086
|
const why = solanaBridgeTxMismatch(tr.data, fromAddress);
|
|
983
|
-
if (why) return reject(why);
|
|
1087
|
+
if (why) return reject(why, reasonOf(why));
|
|
984
1088
|
}
|
|
985
|
-
} 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))
|
|
986
|
-
|
|
1089
|
+
} 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)) {
|
|
1090
|
+
return reject(
|
|
1091
|
+
"the transaction is malformed or targets an untrusted contract",
|
|
1092
|
+
!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"
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
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");
|
|
987
1096
|
const sourceIsToken = fromToken !== EVM_ZERO && fromToken !== SOLANA_NATIVE;
|
|
988
1097
|
if (solanaSource) {
|
|
989
1098
|
} else if (sourceIsToken) {
|
|
990
|
-
if (!trustedAddress(params.sourceChain, est.approvalAddress, trustedApprovalAddresses)) return reject("the approval target is not a trusted LI.FI contract");
|
|
1099
|
+
if (!trustedAddress(params.sourceChain, est.approvalAddress, trustedApprovalAddresses)) return reject("the approval target is not a trusted LI.FI contract", "vault_mismatch");
|
|
991
1100
|
} else if (est.approvalAddress !== void 0) {
|
|
992
|
-
if (!trustedAddress(params.sourceChain, est.approvalAddress, trustedApprovalAddresses)) return reject("the approval target is not a trusted LI.FI contract");
|
|
1101
|
+
if (!trustedAddress(params.sourceChain, est.approvalAddress, trustedApprovalAddresses)) return reject("the approval target is not a trusted LI.FI contract", "vault_mismatch");
|
|
993
1102
|
}
|
|
994
1103
|
let txRequest;
|
|
995
1104
|
let nativeFee = 0n;
|
|
@@ -997,13 +1106,13 @@ function lifiProvider(config = {}) {
|
|
|
997
1106
|
txRequest = { serializedTx: tr.data, value: sourceIsToken ? "0" : params.amountIn };
|
|
998
1107
|
} else {
|
|
999
1108
|
try {
|
|
1000
|
-
if (BigInt(tr.value) < 0n || BigInt(tr.gasLimit) <= 0n) return reject("the transaction has a negative value or no gas limit");
|
|
1109
|
+
if (BigInt(tr.value) < 0n || BigInt(tr.gasLimit) <= 0n) return reject("the transaction has a negative value or no gas limit", "upstream_unavailable");
|
|
1001
1110
|
} catch {
|
|
1002
|
-
return reject("the transaction value or gas limit is not a number");
|
|
1111
|
+
return reject("the transaction value or gas limit is not a number", "upstream_unavailable");
|
|
1003
1112
|
}
|
|
1004
1113
|
const value = BigInt(tr.value);
|
|
1005
1114
|
nativeFee = sourceIsToken ? value : value > BigInt(params.amountIn) ? value - BigInt(params.amountIn) : 0n;
|
|
1006
|
-
if (!sourceIsToken && value < BigInt(params.amountIn)) return reject("the transaction sends less native coin than the input");
|
|
1115
|
+
if (!sourceIsToken && value < BigInt(params.amountIn)) return reject("the transaction sends less native coin than the input", "policy_refused");
|
|
1007
1116
|
txRequest = {
|
|
1008
1117
|
to: tr.to,
|
|
1009
1118
|
data: tr.data,
|
|
@@ -1015,7 +1124,7 @@ function lifiProvider(config = {}) {
|
|
|
1015
1124
|
};
|
|
1016
1125
|
}
|
|
1017
1126
|
const feeUsd = (est.feeCosts ?? []).reduce((a, f) => a + Number(f.amountUSD ?? 0), 0);
|
|
1018
|
-
if (!est.toAmountMin) return reject("the quote has no minimum output");
|
|
1127
|
+
if (!est.toAmountMin) return reject("the quote has no minimum output", "upstream_unavailable");
|
|
1019
1128
|
const minDestAmount = est.toAmountMin;
|
|
1020
1129
|
if (sentSlippage !== void 0) {
|
|
1021
1130
|
const dest = BigInt(est.toAmount);
|
|
@@ -1029,18 +1138,22 @@ function lifiProvider(config = {}) {
|
|
|
1029
1138
|
continue;
|
|
1030
1139
|
}
|
|
1031
1140
|
params.onRefusal?.(
|
|
1032
|
-
`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}
|
|
1141
|
+
`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}%`,
|
|
1142
|
+
{ reason: "price_check_failed" }
|
|
1033
1143
|
);
|
|
1034
1144
|
return null;
|
|
1035
1145
|
}
|
|
1036
1146
|
}
|
|
1037
1147
|
if (params.minAmountOut !== void 0 && BigInt(minDestAmount) < BigInt(params.minAmountOut)) {
|
|
1038
|
-
params.onRefusal?.(`lifi: floor breach (min=${minDestAmount} < floor=${params.minAmountOut})
|
|
1148
|
+
params.onRefusal?.(`lifi: floor breach (min=${minDestAmount} < floor=${params.minAmountOut})`, { reason: "price_check_failed" });
|
|
1039
1149
|
return null;
|
|
1040
1150
|
}
|
|
1041
1151
|
const duration = est.executionDuration ?? 0;
|
|
1042
1152
|
if (fromChain === toChain && duration > SAME_CHAIN_MAX_SECONDS) {
|
|
1043
|
-
params.onRefusal?.(
|
|
1153
|
+
params.onRefusal?.(
|
|
1154
|
+
`lifi: same-chain route settles asynchronously (${duration} s via ${q.tool ?? "unknown tool"}) \u2014 a same-chain swap must deliver in the same transaction`,
|
|
1155
|
+
{ reason: "unsupported_pair" }
|
|
1156
|
+
);
|
|
1044
1157
|
return null;
|
|
1045
1158
|
}
|
|
1046
1159
|
if (!solanaSource) {
|
|
@@ -1055,7 +1168,7 @@ function lifiProvider(config = {}) {
|
|
|
1055
1168
|
sameChain: fromChain === toChain,
|
|
1056
1169
|
evmDestination: !isSolana(params.destChain)
|
|
1057
1170
|
});
|
|
1058
|
-
if (why) return reject(`the transaction does not match the quote: ${why}
|
|
1171
|
+
if (why) return reject(`the transaction does not match the quote: ${why}`, reasonOf(why) ?? "policy_refused");
|
|
1059
1172
|
}
|
|
1060
1173
|
return {
|
|
1061
1174
|
provider: "lifi",
|
|
@@ -1080,7 +1193,7 @@ function lifiProvider(config = {}) {
|
|
|
1080
1193
|
validUntilMs: receivedAt + quoteTtlMs
|
|
1081
1194
|
};
|
|
1082
1195
|
} catch {
|
|
1083
|
-
if (deadline?.aborted) params.onRefusal?.("lifi: no quote within 12 s");
|
|
1196
|
+
if (deadline?.aborted) params.onRefusal?.("lifi: no quote within 12 s", { reason: "upstream_unavailable" });
|
|
1084
1197
|
return null;
|
|
1085
1198
|
}
|
|
1086
1199
|
return null;
|
|
@@ -1088,10 +1201,10 @@ function lifiProvider(config = {}) {
|
|
|
1088
1201
|
async buildDeposit(quote) {
|
|
1089
1202
|
const placeholder = quote.providerData?.placeholder;
|
|
1090
1203
|
if (placeholder !== void 0) {
|
|
1091
|
-
throw
|
|
1204
|
+
throw 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`);
|
|
1092
1205
|
}
|
|
1093
1206
|
const tx = quote.txRequest;
|
|
1094
|
-
if (!tx) throw
|
|
1207
|
+
if (!tx) throw refusal("policy_refused", "swap2: LI.FI quote has no transaction request");
|
|
1095
1208
|
const [, fromChain] = quote.intentId.split(":");
|
|
1096
1209
|
return {
|
|
1097
1210
|
chain: tx.serializedTx ? CAIP2_BY_LIFI_CHAIN[fromChain ?? ""] ?? "" : "",
|
|
@@ -1140,23 +1253,23 @@ function lifiProvider(config = {}) {
|
|
|
1140
1253
|
function lifiVerifyQuote(params, quote) {
|
|
1141
1254
|
const fromChain = LIFI_CHAIN[params.sourceChain];
|
|
1142
1255
|
const toChain = LIFI_CHAIN[params.destChain];
|
|
1143
|
-
if (!fromChain || !toChain) return "not a LI.FI chain pair";
|
|
1256
|
+
if (!fromChain || !toChain) return note("not a LI.FI chain pair", "unsupported_pair");
|
|
1144
1257
|
const placeholder = params.dry !== false ? placeholderIn(params) : null;
|
|
1145
1258
|
if (placeholder) {
|
|
1146
|
-
if (quote.txRequest !== void 0) return `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
|
|
1259
|
+
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");
|
|
1147
1260
|
return priceOnlyMismatch(params, quote, `lifi:${fromChain}:${toChain}`);
|
|
1148
1261
|
}
|
|
1149
1262
|
const tx = quote.txRequest;
|
|
1150
|
-
if (!tx) return "the quote carries no transaction";
|
|
1263
|
+
if (!tx) return note("the quote carries no transaction", "policy_refused");
|
|
1151
1264
|
if (isSolana(params.sourceChain)) {
|
|
1152
|
-
if (!tx.serializedTx || tx.data !== void 0 || tx.to !== void 0) return "a Solana source carries a serialized transaction only";
|
|
1265
|
+
if (!tx.serializedTx || tx.data !== void 0 || tx.to !== void 0) return note("a Solana source carries a serialized transaction only", "policy_refused");
|
|
1153
1266
|
return fromChain === toChain ? null : solanaBridgeTxMismatch(tx.serializedTx, params.refundAddress || (params.dry !== false ? SOLANA_PLACEHOLDER : ""));
|
|
1154
1267
|
}
|
|
1155
|
-
if (!isHexData(tx.data) || !tx.to || tx.serializedTx !== void 0) return "the quote carries no Diamond call";
|
|
1156
|
-
if (!sameAddress(tx.to, diamondFor(params.sourceChain))) return `the call targets ${tx.to}, not the LI.FI Diamond
|
|
1157
|
-
if (tx.chainId !== void 0 && String(tx.chainId) !== fromChain) return `the call is for chain ${tx.chainId}, not ${fromChain}
|
|
1268
|
+
if (!isHexData(tx.data) || !tx.to || tx.serializedTx !== void 0) return note("the quote carries no Diamond call", "policy_refused");
|
|
1269
|
+
if (!sameAddress(tx.to, diamondFor(params.sourceChain))) return note(`the call targets ${tx.to}, not the LI.FI Diamond`, "vault_mismatch");
|
|
1270
|
+
if (tx.chainId !== void 0 && String(tx.chainId) !== fromChain) return note(`the call is for chain ${tx.chainId}, not ${fromChain}`, "policy_refused");
|
|
1158
1271
|
const fromToken = lifiToken(params);
|
|
1159
|
-
if (!fromToken) return `unknown source token ${params.sourceToken}
|
|
1272
|
+
if (!fromToken) return note(`unknown source token ${params.sourceToken}`, "unsupported_pair");
|
|
1160
1273
|
const sourceIsToken = fromToken !== EVM_ZERO;
|
|
1161
1274
|
const dry = params.dry !== false;
|
|
1162
1275
|
const fromAddress = params.refundAddress || (dry ? EVM_PLACEHOLDER : "");
|
|
@@ -1169,18 +1282,18 @@ function lifiVerifyQuote(params, quote) {
|
|
|
1169
1282
|
value = BigInt(tx.value ?? "0");
|
|
1170
1283
|
nativeFee = BigInt(tx.nativeFee ?? "0");
|
|
1171
1284
|
} catch {
|
|
1172
|
-
return "the quote's amounts are not integers";
|
|
1285
|
+
return note("the quote's amounts are not integers", "upstream_unavailable");
|
|
1173
1286
|
}
|
|
1174
|
-
if (BigInt(quote.sourceAmount) !== amountIn) return `the quote spends ${quote.sourceAmount}, not the ${amountIn} input
|
|
1175
|
-
if (params.minAmountOut !== void 0 && minOut < BigInt(params.minAmountOut)) return `the quoted minimum ${minOut} is below the caller's floor ${params.minAmountOut}
|
|
1287
|
+
if (BigInt(quote.sourceAmount) !== amountIn) return note(`the quote spends ${quote.sourceAmount}, not the ${amountIn} input`, "policy_refused");
|
|
1288
|
+
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");
|
|
1176
1289
|
if (sourceIsToken) {
|
|
1177
|
-
if (value !== nativeFee) return "a token input attaches only its declared bridge fee";
|
|
1178
|
-
if (!tx.approvalAddress || !sameAddress(tx.approvalAddress, diamondFor(params.sourceChain))) return "a token input approves the LI.FI Diamond";
|
|
1290
|
+
if (value !== nativeFee) return note("a token input attaches only its declared bridge fee", "policy_refused");
|
|
1291
|
+
if (!tx.approvalAddress || !sameAddress(tx.approvalAddress, diamondFor(params.sourceChain))) return note("a token input approves the LI.FI Diamond", "vault_mismatch");
|
|
1179
1292
|
} else {
|
|
1180
|
-
if (value < amountIn || value - amountIn !== nativeFee) return "a native input is paid as the input plus its declared bridge fee";
|
|
1181
|
-
if (tx.approvalAddress !== void 0) return "a native input approves nothing";
|
|
1293
|
+
if (value < amountIn || value - amountIn !== nativeFee) return note("a native input is paid as the input plus its declared bridge fee", "policy_refused");
|
|
1294
|
+
if (tx.approvalAddress !== void 0) return note("a native input approves nothing", "vault_mismatch");
|
|
1182
1295
|
}
|
|
1183
|
-
if (quote.intentId.replace(/~[0-9a-f]{32}$/, "") !== `lifi:${fromChain}:${toChain}`) return "the intent id does not name this route";
|
|
1296
|
+
if (quote.intentId.replace(/~[0-9a-f]{32}$/, "") !== `lifi:${fromChain}:${toChain}`) return note("the intent id does not name this route", "policy_refused");
|
|
1184
1297
|
const why = diamondCallMismatch(tx.data, {
|
|
1185
1298
|
fromToken,
|
|
1186
1299
|
toToken: nativeFor(params.destChain, params.destToken),
|
|
@@ -1192,25 +1305,27 @@ function lifiVerifyQuote(params, quote) {
|
|
|
1192
1305
|
sameChain: fromChain === toChain,
|
|
1193
1306
|
evmDestination: !isSolana(params.destChain)
|
|
1194
1307
|
});
|
|
1195
|
-
return why ? `the transaction does not match the request: ${why}
|
|
1308
|
+
return why ? note(`the transaction does not match the request: ${why}`, reasonOf(why) ?? "policy_refused") : null;
|
|
1196
1309
|
}
|
|
1197
1310
|
function priceOnlyMismatch(params, quote, route) {
|
|
1198
|
-
if (quote.depositAddress !== "") return `a LI.FI quote pays no deposit address, but this one names ${String(quote.depositAddress).slice(0, 64)}
|
|
1311
|
+
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");
|
|
1199
1312
|
const fee = quote.providerData?.nativeFee;
|
|
1200
|
-
if (fee !== void 0 && !/^\d{1,80}$/.test(String(fee))) return `the price-only quote declares a native fee of ${String(fee).slice(0, 40)}, not a base-unit integer
|
|
1313
|
+
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");
|
|
1201
1314
|
try {
|
|
1202
|
-
if (BigInt(quote.sourceAmount) !== BigInt(params.amountIn)) return `the quote spends ${quote.sourceAmount}, not the ${params.amountIn} input
|
|
1315
|
+
if (BigInt(quote.sourceAmount) !== BigInt(params.amountIn)) return note(`the quote spends ${quote.sourceAmount}, not the ${params.amountIn} input`, "policy_refused");
|
|
1203
1316
|
const minOut = BigInt(quote.minDestAmount);
|
|
1204
|
-
if (params.minAmountOut !== void 0 && minOut < BigInt(params.minAmountOut)) return `the quoted minimum ${minOut} is below the caller's floor ${params.minAmountOut}
|
|
1317
|
+
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");
|
|
1205
1318
|
} catch {
|
|
1206
|
-
return "the quote's amounts are not integers";
|
|
1319
|
+
return note("the quote's amounts are not integers", "upstream_unavailable");
|
|
1207
1320
|
}
|
|
1208
|
-
return quote.intentId.replace(/~[0-9a-f]{32}$/, "") === route ? null : "the intent id does not name this route";
|
|
1321
|
+
return quote.intentId.replace(/~[0-9a-f]{32}$/, "") === route ? null : note("the intent id does not name this route", "policy_refused");
|
|
1209
1322
|
}
|
|
1210
1323
|
var lifiPins = Object.freeze({
|
|
1211
1324
|
callTargets: (chain) => /^eip155:[0-9]+$/.test(chain) ? [diamondFor(chain)] : [],
|
|
1212
1325
|
spenders: (chain) => /^eip155:[0-9]+$/.test(chain) ? [diamondFor(chain)] : [],
|
|
1213
|
-
verifyQuote: lifiVerifyQuote
|
|
1326
|
+
verifyQuote: lifiVerifyQuote,
|
|
1327
|
+
/** The reason of a text {@link lifiVerifyQuote} returned (`remoteProvider` puts it on the stub's refusal). */
|
|
1328
|
+
reasonOf: (text) => reasonOf(text)
|
|
1214
1329
|
});
|
|
1215
1330
|
|
|
1216
1331
|
export {
|
|
@@ -1218,4 +1333,4 @@ export {
|
|
|
1218
1333
|
lifiVerifyQuote,
|
|
1219
1334
|
lifiPins
|
|
1220
1335
|
};
|
|
1221
|
-
//# sourceMappingURL=chunk-
|
|
1336
|
+
//# sourceMappingURL=chunk-G26GE46T.js.map
|