@anywalletkit/protocol-swap2-lifi 0.8.0 → 0.8.2
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
package/dist/provider.cjs
CHANGED
|
@@ -138,6 +138,12 @@ function associatedTokenAccount(owner, mint, tokenProgram = solanaKey(SOLANA_TOK
|
|
|
138
138
|
return programAddress([owner, tokenProgram, mint], solanaKey(ASSOCIATED_TOKEN_PROGRAM));
|
|
139
139
|
}
|
|
140
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
|
+
|
|
141
147
|
// src/calldata.ts
|
|
142
148
|
var GENERIC_SWAPS = {
|
|
143
149
|
"0x4630a0d8": { single: false },
|
|
@@ -357,18 +363,18 @@ function swapsAt(r, base, head) {
|
|
|
357
363
|
function checkSteps(steps, e, finalAsset) {
|
|
358
364
|
const first = steps[0];
|
|
359
365
|
const last = steps[steps.length - 1];
|
|
360
|
-
if (!same(first.sendingAssetId, e.fromToken)) return `spends ${first.sendingAssetId}, not ${e.fromToken}
|
|
361
|
-
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");
|
|
362
368
|
let deposited = 0n;
|
|
363
369
|
for (let i = 0; i < steps.length; i++) {
|
|
364
370
|
const step = steps[i];
|
|
365
|
-
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");
|
|
366
372
|
if (!step.requiresDeposit) continue;
|
|
367
|
-
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");
|
|
368
374
|
deposited += step.fromAmount;
|
|
369
375
|
}
|
|
370
|
-
if (deposited > e.amountIn) return `the steps pull ${deposited} from the wallet, more than the ${e.amountIn} spent
|
|
371
|
-
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");
|
|
372
378
|
return null;
|
|
373
379
|
}
|
|
374
380
|
var MAYAN_REFUND_AT = {
|
|
@@ -409,32 +415,32 @@ function acrossSwapRefunds(r, at) {
|
|
|
409
415
|
}
|
|
410
416
|
var ERC20_TRANSFER = "0xa9059cbb";
|
|
411
417
|
function ecoDelivery(r, at, e) {
|
|
412
|
-
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");
|
|
413
419
|
const destination = r.word(at + 32 * 3);
|
|
414
|
-
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");
|
|
415
421
|
const route = r.bytes(at, at + 32 * 4);
|
|
416
422
|
const tuple = route.small(0);
|
|
417
423
|
const calls = tuple + route.small(tuple + 32 * 5);
|
|
418
424
|
const n = route.small(calls);
|
|
419
|
-
if (n === 0) return "the Eco route makes no calls on the destination chain";
|
|
420
|
-
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");
|
|
421
427
|
const last = calls + 32 + route.small(calls + 32 + 32 * (n - 1));
|
|
422
428
|
const target = route.address(last);
|
|
423
429
|
const data = route.bytes(last, last + 32);
|
|
424
|
-
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");
|
|
425
431
|
const to = data.address(4);
|
|
426
432
|
const amount = data.word(36);
|
|
427
|
-
if (!same(target, e.toToken)) return `the Eco route delivers ${target}, not ${e.toToken}
|
|
428
|
-
if (!same(to, e.receiver)) return `the Eco route pays ${to}, not ${e.receiver}
|
|
429
|
-
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");
|
|
430
436
|
return null;
|
|
431
437
|
}
|
|
432
438
|
function m0Delivery(r, at, e) {
|
|
433
|
-
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");
|
|
434
440
|
const tokenOut = r.address(at + 32 * 3);
|
|
435
441
|
const amountOut = r.word(at + 32 * 5);
|
|
436
|
-
if (!same(tokenOut, e.toToken)) return `the M0 order asks for ${tokenOut}, not ${e.toToken}
|
|
437
|
-
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");
|
|
438
444
|
return null;
|
|
439
445
|
}
|
|
440
446
|
var FACET_DATA = {
|
|
@@ -468,13 +474,13 @@ function facetDataAt(r, bridge, spec) {
|
|
|
468
474
|
}
|
|
469
475
|
function refundMismatch(r, bridge, e) {
|
|
470
476
|
const spec = FACET_DATA[bridge.facet];
|
|
471
|
-
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");
|
|
472
478
|
const at = facetDataAt(r, bridge, spec);
|
|
473
479
|
const paid = (spec.refunds ?? []).map(([field, word]) => [`${bridge.facet} ${field}`, r.address(at + 32 * word), spec.zeroRefused === true]);
|
|
474
480
|
paid.push(...spec.nested?.(r, at) ?? []);
|
|
475
481
|
for (const [what, to, zeroRefused] of paid) {
|
|
476
482
|
if (zeroRefused && /^0x0{40}$/.test(to)) continue;
|
|
477
|
-
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");
|
|
478
484
|
}
|
|
479
485
|
return null;
|
|
480
486
|
}
|
|
@@ -506,36 +512,36 @@ var hexOf = (b) => `0x${Array.from(b, (x) => x.toString(16).padStart(2, "0")).jo
|
|
|
506
512
|
var keyOf = (word) => base58(Uint8Array.from(word.slice(2).match(/../g) ?? [], (h) => Number.parseInt(h, 16)));
|
|
507
513
|
function mayanToSolana(r, at, t) {
|
|
508
514
|
const declared = r.raw(at, 32);
|
|
509
|
-
if (declared !== t.ownerHex) return `Mayan's nonEVMReceiver is ${keyOf(declared)}, not the recipient ${t.e.receiver}
|
|
515
|
+
if (declared !== t.ownerHex) return note(`Mayan's nonEVMReceiver is ${keyOf(declared)}, not the recipient ${t.e.receiver}`, "recipient_mismatch");
|
|
510
516
|
const p = r.bytes(at, at + 32 * 2);
|
|
511
517
|
const selector = p.raw(0, 4);
|
|
512
518
|
const o = MAYAN_SOLANA[selector];
|
|
513
|
-
if (!o) return `the Mayan call ${selector} is an entry point whose Solana recipient cannot be checked here
|
|
519
|
+
if (!o) return note(`the Mayan call ${selector} is an entry point whose Solana recipient cannot be checked here`, "recipient_unverifiable");
|
|
514
520
|
const word = (i) => 4 + 32 * i;
|
|
515
|
-
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
|
|
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");
|
|
516
522
|
const dest = p.raw(word(o.destAddr), 32);
|
|
517
|
-
if (dest !== t.ownerHex) return `the Mayan order pays ${keyOf(dest)} on Solana, not the recipient ${t.e.receiver}
|
|
523
|
+
if (dest !== t.ownerHex) return note(`the Mayan order pays ${keyOf(dest)} on Solana, not the recipient ${t.e.receiver}`, "recipient_mismatch");
|
|
518
524
|
const chain = p.word(word(o.chain));
|
|
519
|
-
if (chain !== (o.cctp ? CCTP_DOMAIN_SOLANA : WORMHOLE_SOLANA)) return `the Mayan order goes to ${o.cctp ? "CCTP domain" : "Wormhole chain"} ${chain}, not Solana
|
|
520
|
-
if (o.tokenOut === void 0) return t.e.toToken === SOLANA_USDC ? null : `a Mayan CCTP transfer delivers USDC, not ${t.e.toToken}
|
|
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");
|
|
521
527
|
const token = p.raw(word(o.tokenOut), 32);
|
|
522
|
-
return token === t.mintHex ? null : `the Mayan order delivers ${keyOf(token)}, not ${t.e.toToken}
|
|
528
|
+
return token === t.mintHex ? null : note(`the Mayan order delivers ${keyOf(token)}, not ${t.e.toToken}`, "policy_refused");
|
|
523
529
|
}
|
|
524
530
|
function polymerToSolana(r, at, t) {
|
|
525
531
|
const declared = r.raw(at + 32 * 2, 32);
|
|
526
|
-
if (declared !== t.ownerHex) return `Polymer CCTP's nonEVMReceiver is ${keyOf(declared)}, not the recipient ${t.e.receiver}
|
|
527
|
-
if (t.e.toToken !== SOLANA_USDC) return `Polymer CCTP delivers USDC, not ${t.e.toToken}
|
|
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");
|
|
528
534
|
const mintTo = r.raw(at + 32 * 3, 32);
|
|
529
535
|
const account = hexOf(associatedTokenAccount(t.owner, t.mint));
|
|
530
|
-
return mintTo === account ? null : `the Polymer CCTP transfer mints to ${keyOf(mintTo)}, not the recipient's USDC account ${keyOf(account)}
|
|
536
|
+
return mintTo === account ? null : note(`the Polymer CCTP transfer mints to ${keyOf(mintTo)}, not the recipient's USDC account ${keyOf(account)}`, "recipient_mismatch");
|
|
531
537
|
}
|
|
532
538
|
function chainflipToSolana(r, at, t) {
|
|
533
539
|
const dst = r.bytes(at, at);
|
|
534
|
-
if (dst.length !== 32) return `Chainflip pays a ${dst.length}-byte address on Solana, not the recipient ${t.e.receiver}
|
|
535
|
-
if (dst.raw(0, 32) !== t.ownerHex) return `Chainflip pays ${keyOf(dst.raw(0, 32))} on Solana, not the recipient ${t.e.receiver}
|
|
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");
|
|
536
542
|
const asset = r.word(at + 32);
|
|
537
543
|
const want = CHAINFLIP_SOLANA_ASSETS[t.e.toToken];
|
|
538
|
-
return want !== void 0 && asset === want ? null : `Chainflip delivers its asset ${asset}, not ${t.e.toToken}
|
|
544
|
+
return want !== void 0 && asset === want ? null : note(`Chainflip delivers its asset ${asset}, not ${t.e.toToken}`, "policy_refused");
|
|
539
545
|
}
|
|
540
546
|
var SOLANA_DELIVERY = {
|
|
541
547
|
Mayan: mayanToSolana,
|
|
@@ -546,16 +552,16 @@ var INTENTS_TO_SOLANA = "a LI.FI Intents order to Solana names its own oracles,
|
|
|
546
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";
|
|
547
553
|
var INTENTS_FACET = "LiFiIntentEscrowV2";
|
|
548
554
|
function solanaRecipientMismatch(r, bridge, receiver, e) {
|
|
549
|
-
if (e.destChainId !== LIFI_SOLANA_CHAIN_ID) return NON_EVM_UNREAD;
|
|
550
|
-
if (!same(receiver, NON_EVM_ADDRESS)) return `BridgeData pays ${receiver}, not LI.FI's non-EVM placeholder ${NON_EVM_ADDRESS}, on a Solana route
|
|
551
|
-
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
|
|
552
|
-
if (bridge.facet === INTENTS_FACET) return INTENTS_TO_SOLANA;
|
|
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");
|
|
553
559
|
const read = SOLANA_DELIVERY[bridge.facet];
|
|
554
|
-
if (!read) return NON_EVM_UNREAD;
|
|
560
|
+
if (!read) return note(NON_EVM_UNREAD, "recipient_unverifiable");
|
|
555
561
|
const owner = solanaKey(e.receiver);
|
|
556
|
-
if (!owner) return `the recipient ${e.receiver} is not a Solana address
|
|
562
|
+
if (!owner) return note(`the recipient ${e.receiver} is not a Solana address`, "recipient_mismatch");
|
|
557
563
|
const mint = solanaKey(e.toToken);
|
|
558
|
-
if (!mint) return `the destination token ${e.toToken} is not a Solana mint
|
|
564
|
+
if (!mint) return note(`the destination token ${e.toToken} is not a Solana mint`, "unsupported_pair");
|
|
559
565
|
return read(r, facetDataAt(r, bridge, FACET_DATA[bridge.facet]), { owner, ownerHex: hexOf(owner), mint, mintHex: hexOf(mint), e });
|
|
560
566
|
}
|
|
561
567
|
function diamondCallMismatch(data, e) {
|
|
@@ -563,61 +569,61 @@ function diamondCallMismatch(data, e) {
|
|
|
563
569
|
try {
|
|
564
570
|
r = new Reader(data);
|
|
565
571
|
} catch {
|
|
566
|
-
return "the calldata is not hex";
|
|
572
|
+
return note("the calldata is not hex", "upstream_unavailable");
|
|
567
573
|
}
|
|
568
|
-
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");
|
|
569
575
|
const selector = data.slice(0, 10).toLowerCase();
|
|
570
576
|
const generic = GENERIC_SWAPS[selector];
|
|
571
577
|
try {
|
|
572
578
|
if (generic) {
|
|
573
|
-
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");
|
|
574
580
|
const receiver2 = r.address(4 + 96);
|
|
575
581
|
const minAmount2 = r.word(4 + 128);
|
|
576
582
|
const steps = generic.single ? [swapAt(r, 4 + r.small(4 + 160))] : swapsAt(r, 4, 4 + 160);
|
|
577
|
-
if (!e.evmDestination || !same(receiver2, e.receiver)) return `pays ${receiver2}, not ${e.receiver}
|
|
578
|
-
if (minAmount2 < e.minOut) return `accepts ${minAmount2}, below the ${e.minOut} shown
|
|
579
|
-
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");
|
|
580
586
|
if (generic.to && (kindOf(e.toToken) !== generic.to || kindOf(steps[steps.length - 1].receivingAssetId) !== generic.to)) {
|
|
581
|
-
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");
|
|
582
588
|
}
|
|
583
589
|
return checkSteps(steps, e, e.toToken);
|
|
584
590
|
}
|
|
585
591
|
const unbound = UNBOUND_BRIDGES[selector];
|
|
586
|
-
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");
|
|
587
593
|
const bridge = BRIDGE_CALLS[selector];
|
|
588
|
-
if (!bridge) return `unknown Diamond call ${selector}
|
|
589
|
-
if (bridge.facet === INTENTS_FACET && e.evmDestination) return INTENTS_UNPINNED;
|
|
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");
|
|
590
596
|
const head = r.small(4);
|
|
591
|
-
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");
|
|
592
598
|
const at = 4 + head;
|
|
593
|
-
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");
|
|
594
600
|
const sendingAssetId = r.address(at + 128);
|
|
595
601
|
const receiver = r.address(at + 160);
|
|
596
602
|
const minAmount = r.word(at + 192);
|
|
597
603
|
const destinationChainId = r.word(at + 224);
|
|
598
604
|
const hasSourceSwaps = r.bool(at + 256);
|
|
599
605
|
const hasDestinationCall = r.bool(at + 288);
|
|
600
|
-
if (e.sameChain) return "a bridge call on a same-chain route";
|
|
601
|
-
if (destinationChainId !== e.destChainId) return `bridges to chain ${destinationChainId}, not ${e.destChainId}
|
|
602
|
-
if (hasDestinationCall) return "the route runs a call on the destination chain, whose recipient cannot be checked here";
|
|
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");
|
|
603
609
|
if (!e.evmDestination) {
|
|
604
610
|
const why = solanaRecipientMismatch(r, bridge, receiver, e);
|
|
605
611
|
if (why) return why;
|
|
606
|
-
} else if (!same(receiver, e.receiver)) return `pays ${receiver}, not ${e.receiver}
|
|
607
|
-
if (hasSourceSwaps !== bridge.sourceSwaps) return `unknown Diamond call ${selector}
|
|
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");
|
|
608
614
|
const refund = refundMismatch(r, bridge, e);
|
|
609
615
|
if (refund) return refund;
|
|
610
616
|
const spec = FACET_DATA[bridge.facet];
|
|
611
617
|
const delivery = spec?.delivery?.(r, facetDataAt(r, bridge, spec), e);
|
|
612
618
|
if (delivery) return delivery;
|
|
613
619
|
if (hasSourceSwaps) {
|
|
614
|
-
if (minAmount === 0n) return "bridges nothing after the source swaps";
|
|
620
|
+
if (minAmount === 0n) return note("bridges nothing after the source swaps", "policy_refused");
|
|
615
621
|
return checkSteps(swapsAt(r, 4, 4 + 32), e, sendingAssetId);
|
|
616
622
|
}
|
|
617
|
-
if (minAmount > e.amountIn || minAmount * 10000n < e.amountIn * (10000n - BRIDGE_INPUT_FEE_MAX_BPS)) return `bridges ${minAmount}, not the ${e.amountIn} spent
|
|
618
|
-
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");
|
|
619
625
|
} catch (err) {
|
|
620
|
-
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");
|
|
621
627
|
}
|
|
622
628
|
}
|
|
623
629
|
|
|
@@ -666,7 +672,7 @@ var Cursor = class {
|
|
|
666
672
|
};
|
|
667
673
|
function solanaBridgeTxMismatch(base64, sender) {
|
|
668
674
|
const bytes = (0, import_util2.base64Bytes)(base64);
|
|
669
|
-
if (!bytes) return "the Solana transaction is not base64";
|
|
675
|
+
if (!bytes) return note("the Solana transaction is not base64", "upstream_unavailable");
|
|
670
676
|
const c = new Cursor(bytes);
|
|
671
677
|
try {
|
|
672
678
|
c.bytes(64 * c.short());
|
|
@@ -674,7 +680,7 @@ function solanaBridgeTxMismatch(base64, sender) {
|
|
|
674
680
|
if ((first & 128) === 0) c.at--;
|
|
675
681
|
c.bytes(3);
|
|
676
682
|
const keys = Array.from({ length: c.short() }, () => base58(c.bytes(32)));
|
|
677
|
-
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");
|
|
678
684
|
c.bytes(32);
|
|
679
685
|
let mayan = false;
|
|
680
686
|
const payees = [];
|
|
@@ -689,15 +695,16 @@ function solanaBridgeTxMismatch(base64, sender) {
|
|
|
689
695
|
if (to !== void 0 && to !== sender && to !== LIFI_SOLANA_FEE_WALLET) payees.push(to);
|
|
690
696
|
}
|
|
691
697
|
}
|
|
692
|
-
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;
|
|
693
699
|
} catch (err) {
|
|
694
|
-
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");
|
|
695
701
|
}
|
|
696
702
|
}
|
|
697
703
|
|
|
698
704
|
// src/provider.ts
|
|
699
705
|
var import_util3 = require("@anywalletkit/protocol-swap2-core/util");
|
|
700
706
|
var import_validate = require("@anywalletkit/protocol-swap2-core/validate");
|
|
707
|
+
var import_errors2 = require("@anywalletkit/protocol-swap2-core/errors");
|
|
701
708
|
var import_core = require("@anywalletkit/core");
|
|
702
709
|
var DEFAULT_LIFI_BASE = "https://li.quest/v1";
|
|
703
710
|
var LIFI_CHAIN = {
|
|
@@ -875,6 +882,92 @@ function routeOptions(body, excluded, allowed) {
|
|
|
875
882
|
}
|
|
876
883
|
return out;
|
|
877
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
|
+
}
|
|
878
971
|
function cleanChoice(choice) {
|
|
879
972
|
if (!choice) return null;
|
|
880
973
|
const allow = (choice.allowBridges ?? []).filter((t) => TOOL_KEY.test(t));
|
|
@@ -915,8 +1008,10 @@ function lifiProvider(config = {}) {
|
|
|
915
1008
|
return !!LIFI_CHAIN[from.chain] && !!LIFI_CHAIN[to.chain];
|
|
916
1009
|
},
|
|
917
1010
|
async getQuote(params, signal) {
|
|
918
|
-
const reject = (why) => {
|
|
919
|
-
|
|
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}`);
|
|
920
1015
|
return null;
|
|
921
1016
|
};
|
|
922
1017
|
if (params.flex) return null;
|
|
@@ -1008,7 +1103,10 @@ function lifiProvider(config = {}) {
|
|
|
1008
1103
|
continue;
|
|
1009
1104
|
}
|
|
1010
1105
|
const why = reasons.length === 1 ? ` (${reasons[0].slice(0, 200)})` : "";
|
|
1011
|
-
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
|
+
);
|
|
1012
1110
|
return null;
|
|
1013
1111
|
}
|
|
1014
1112
|
const q = await res.json();
|
|
@@ -1017,7 +1115,7 @@ function lifiProvider(config = {}) {
|
|
|
1017
1115
|
const steps = q.includedSteps;
|
|
1018
1116
|
const tools = [q.tool, ...Array.isArray(steps) ? steps.map((i) => i?.tool) : []];
|
|
1019
1117
|
if (tools.some((t) => typeof t === "string" && SMART_DEPOSITS_TOOL.test(t))) {
|
|
1020
|
-
return reject("LI.FI Smart Deposits pay a deposit address whose recipient the transaction does not bind \u2014 refused for a Solana destination");
|
|
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");
|
|
1021
1119
|
}
|
|
1022
1120
|
}
|
|
1023
1121
|
const action = q.action;
|
|
@@ -1026,7 +1124,7 @@ function lifiProvider(config = {}) {
|
|
|
1026
1124
|
const expectedToToken = nativeFor(params.destChain, params.destToken);
|
|
1027
1125
|
const expectedFromChain = Number(fromChain);
|
|
1028
1126
|
const expectedToChain = Number(toChain);
|
|
1029
|
-
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");
|
|
1030
1128
|
const mismatch = [
|
|
1031
1129
|
action.fromChainId !== expectedFromChain && "source chain",
|
|
1032
1130
|
action.toChainId !== expectedToChain && "destination chain",
|
|
@@ -1036,28 +1134,39 @@ function lifiProvider(config = {}) {
|
|
|
1036
1134
|
!sameChainAddress(params.sourceChain, action.fromAddress, fromAddress) && "sender",
|
|
1037
1135
|
!sameChainAddress(params.destChain, action.toAddress, toAddress) && "recipient"
|
|
1038
1136
|
].filter(Boolean);
|
|
1039
|
-
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
|
+
}
|
|
1040
1144
|
const bridged = BigInt(est.fromAmount);
|
|
1041
1145
|
const spent = BigInt(params.amountIn);
|
|
1042
|
-
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");
|
|
1043
1147
|
if ((spent - bridged) * 10000n > spent * BigInt(MAX_INPUT_FEE_BPS)) {
|
|
1044
|
-
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");
|
|
1045
1149
|
}
|
|
1046
1150
|
if (solanaSource) {
|
|
1047
|
-
if (!tr.data || tr.data.length < 64 || tr.data.length % 4 !== 0 || !BASE64.test(tr.data)) return reject("the Solana transaction is not base64");
|
|
1048
|
-
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");
|
|
1049
1153
|
if (fromChain !== toChain) {
|
|
1050
1154
|
const why = solanaBridgeTxMismatch(tr.data, fromAddress);
|
|
1051
|
-
if (why) return reject(why);
|
|
1155
|
+
if (why) return reject(why, reasonOf(why));
|
|
1052
1156
|
}
|
|
1053
|
-
} 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))
|
|
1054
|
-
|
|
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");
|
|
1055
1164
|
const sourceIsToken = fromToken !== EVM_ZERO && fromToken !== SOLANA_NATIVE;
|
|
1056
1165
|
if (solanaSource) {
|
|
1057
1166
|
} else if (sourceIsToken) {
|
|
1058
|
-
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");
|
|
1059
1168
|
} else if (est.approvalAddress !== void 0) {
|
|
1060
|
-
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");
|
|
1061
1170
|
}
|
|
1062
1171
|
let txRequest;
|
|
1063
1172
|
let nativeFee = 0n;
|
|
@@ -1065,13 +1174,13 @@ function lifiProvider(config = {}) {
|
|
|
1065
1174
|
txRequest = { serializedTx: tr.data, value: sourceIsToken ? "0" : params.amountIn };
|
|
1066
1175
|
} else {
|
|
1067
1176
|
try {
|
|
1068
|
-
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");
|
|
1069
1178
|
} catch {
|
|
1070
|
-
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");
|
|
1071
1180
|
}
|
|
1072
1181
|
const value = BigInt(tr.value);
|
|
1073
1182
|
nativeFee = sourceIsToken ? value : value > BigInt(params.amountIn) ? value - BigInt(params.amountIn) : 0n;
|
|
1074
|
-
if (!sourceIsToken && value < BigInt(params.amountIn)) return reject("the transaction sends less native coin than the input");
|
|
1183
|
+
if (!sourceIsToken && value < BigInt(params.amountIn)) return reject("the transaction sends less native coin than the input", "policy_refused");
|
|
1075
1184
|
txRequest = {
|
|
1076
1185
|
to: tr.to,
|
|
1077
1186
|
data: tr.data,
|
|
@@ -1083,7 +1192,7 @@ function lifiProvider(config = {}) {
|
|
|
1083
1192
|
};
|
|
1084
1193
|
}
|
|
1085
1194
|
const feeUsd = (est.feeCosts ?? []).reduce((a, f) => a + Number(f.amountUSD ?? 0), 0);
|
|
1086
|
-
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");
|
|
1087
1196
|
const minDestAmount = est.toAmountMin;
|
|
1088
1197
|
if (sentSlippage !== void 0) {
|
|
1089
1198
|
const dest = BigInt(est.toAmount);
|
|
@@ -1097,18 +1206,22 @@ function lifiProvider(config = {}) {
|
|
|
1097
1206
|
continue;
|
|
1098
1207
|
}
|
|
1099
1208
|
params.onRefusal?.(
|
|
1100
|
-
`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" }
|
|
1101
1211
|
);
|
|
1102
1212
|
return null;
|
|
1103
1213
|
}
|
|
1104
1214
|
}
|
|
1105
1215
|
if (params.minAmountOut !== void 0 && BigInt(minDestAmount) < BigInt(params.minAmountOut)) {
|
|
1106
|
-
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" });
|
|
1107
1217
|
return null;
|
|
1108
1218
|
}
|
|
1109
1219
|
const duration = est.executionDuration ?? 0;
|
|
1110
1220
|
if (fromChain === toChain && duration > SAME_CHAIN_MAX_SECONDS) {
|
|
1111
|
-
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
|
+
);
|
|
1112
1225
|
return null;
|
|
1113
1226
|
}
|
|
1114
1227
|
if (!solanaSource) {
|
|
@@ -1123,7 +1236,7 @@ function lifiProvider(config = {}) {
|
|
|
1123
1236
|
sameChain: fromChain === toChain,
|
|
1124
1237
|
evmDestination: !isSolana(params.destChain)
|
|
1125
1238
|
});
|
|
1126
|
-
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");
|
|
1127
1240
|
}
|
|
1128
1241
|
return {
|
|
1129
1242
|
provider: "lifi",
|
|
@@ -1148,7 +1261,7 @@ function lifiProvider(config = {}) {
|
|
|
1148
1261
|
validUntilMs: receivedAt + quoteTtlMs
|
|
1149
1262
|
};
|
|
1150
1263
|
} catch {
|
|
1151
|
-
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" });
|
|
1152
1265
|
return null;
|
|
1153
1266
|
}
|
|
1154
1267
|
return null;
|
|
@@ -1156,10 +1269,10 @@ function lifiProvider(config = {}) {
|
|
|
1156
1269
|
async buildDeposit(quote) {
|
|
1157
1270
|
const placeholder = quote.providerData?.placeholder;
|
|
1158
1271
|
if (placeholder !== void 0) {
|
|
1159
|
-
throw
|
|
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`);
|
|
1160
1273
|
}
|
|
1161
1274
|
const tx = quote.txRequest;
|
|
1162
|
-
if (!tx) throw
|
|
1275
|
+
if (!tx) throw (0, import_errors2.refusal)("policy_refused", "swap2: LI.FI quote has no transaction request");
|
|
1163
1276
|
const [, fromChain] = quote.intentId.split(":");
|
|
1164
1277
|
return {
|
|
1165
1278
|
chain: tx.serializedTx ? CAIP2_BY_LIFI_CHAIN[fromChain ?? ""] ?? "" : "",
|
|
@@ -1208,23 +1321,23 @@ function lifiProvider(config = {}) {
|
|
|
1208
1321
|
function lifiVerifyQuote(params, quote) {
|
|
1209
1322
|
const fromChain = LIFI_CHAIN[params.sourceChain];
|
|
1210
1323
|
const toChain = LIFI_CHAIN[params.destChain];
|
|
1211
|
-
if (!fromChain || !toChain) return "not a LI.FI chain pair";
|
|
1324
|
+
if (!fromChain || !toChain) return note("not a LI.FI chain pair", "unsupported_pair");
|
|
1212
1325
|
const placeholder = params.dry !== false ? placeholderIn(params) : null;
|
|
1213
1326
|
if (placeholder) {
|
|
1214
|
-
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
|
|
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");
|
|
1215
1328
|
return priceOnlyMismatch(params, quote, `lifi:${fromChain}:${toChain}`);
|
|
1216
1329
|
}
|
|
1217
1330
|
const tx = quote.txRequest;
|
|
1218
|
-
if (!tx) return "the quote carries no transaction";
|
|
1331
|
+
if (!tx) return note("the quote carries no transaction", "policy_refused");
|
|
1219
1332
|
if (isSolana(params.sourceChain)) {
|
|
1220
|
-
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");
|
|
1221
1334
|
return fromChain === toChain ? null : solanaBridgeTxMismatch(tx.serializedTx, params.refundAddress || (params.dry !== false ? SOLANA_PLACEHOLDER : ""));
|
|
1222
1335
|
}
|
|
1223
|
-
if (!isHexData(tx.data) || !tx.to || tx.serializedTx !== void 0) return "the quote carries no Diamond call";
|
|
1224
|
-
if (!sameAddress(tx.to, diamondFor(params.sourceChain))) return `the call targets ${tx.to}, not the LI.FI Diamond
|
|
1225
|
-
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");
|
|
1226
1339
|
const fromToken = lifiToken(params);
|
|
1227
|
-
if (!fromToken) return `unknown source token ${params.sourceToken}
|
|
1340
|
+
if (!fromToken) return note(`unknown source token ${params.sourceToken}`, "unsupported_pair");
|
|
1228
1341
|
const sourceIsToken = fromToken !== EVM_ZERO;
|
|
1229
1342
|
const dry = params.dry !== false;
|
|
1230
1343
|
const fromAddress = params.refundAddress || (dry ? EVM_PLACEHOLDER : "");
|
|
@@ -1237,18 +1350,18 @@ function lifiVerifyQuote(params, quote) {
|
|
|
1237
1350
|
value = BigInt(tx.value ?? "0");
|
|
1238
1351
|
nativeFee = BigInt(tx.nativeFee ?? "0");
|
|
1239
1352
|
} catch {
|
|
1240
|
-
return "the quote's amounts are not integers";
|
|
1353
|
+
return note("the quote's amounts are not integers", "upstream_unavailable");
|
|
1241
1354
|
}
|
|
1242
|
-
if (BigInt(quote.sourceAmount) !== amountIn) return `the quote spends ${quote.sourceAmount}, not the ${amountIn} input
|
|
1243
|
-
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");
|
|
1244
1357
|
if (sourceIsToken) {
|
|
1245
|
-
if (value !== nativeFee) return "a token input attaches only its declared bridge fee";
|
|
1246
|
-
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");
|
|
1247
1360
|
} else {
|
|
1248
|
-
if (value < amountIn || value - amountIn !== nativeFee) return "a native input is paid as the input plus its declared bridge fee";
|
|
1249
|
-
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");
|
|
1250
1363
|
}
|
|
1251
|
-
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");
|
|
1252
1365
|
const why = diamondCallMismatch(tx.data, {
|
|
1253
1366
|
fromToken,
|
|
1254
1367
|
toToken: nativeFor(params.destChain, params.destToken),
|
|
@@ -1260,24 +1373,26 @@ function lifiVerifyQuote(params, quote) {
|
|
|
1260
1373
|
sameChain: fromChain === toChain,
|
|
1261
1374
|
evmDestination: !isSolana(params.destChain)
|
|
1262
1375
|
});
|
|
1263
|
-
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;
|
|
1264
1377
|
}
|
|
1265
1378
|
function priceOnlyMismatch(params, quote, route) {
|
|
1266
|
-
if (quote.depositAddress !== "") return `a LI.FI quote pays no deposit address, but this one names ${String(quote.depositAddress).slice(0, 64)}
|
|
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");
|
|
1267
1380
|
const fee = quote.providerData?.nativeFee;
|
|
1268
|
-
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
|
|
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");
|
|
1269
1382
|
try {
|
|
1270
|
-
if (BigInt(quote.sourceAmount) !== BigInt(params.amountIn)) return `the quote spends ${quote.sourceAmount}, not the ${params.amountIn} input
|
|
1383
|
+
if (BigInt(quote.sourceAmount) !== BigInt(params.amountIn)) return note(`the quote spends ${quote.sourceAmount}, not the ${params.amountIn} input`, "policy_refused");
|
|
1271
1384
|
const minOut = BigInt(quote.minDestAmount);
|
|
1272
|
-
if (params.minAmountOut !== void 0 && minOut < BigInt(params.minAmountOut)) return `the quoted minimum ${minOut} is below the caller's floor ${params.minAmountOut}
|
|
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");
|
|
1273
1386
|
} catch {
|
|
1274
|
-
return "the quote's amounts are not integers";
|
|
1387
|
+
return note("the quote's amounts are not integers", "upstream_unavailable");
|
|
1275
1388
|
}
|
|
1276
|
-
return quote.intentId.replace(/~[0-9a-f]{32}$/, "") === route ? null : "the intent id does not name this route";
|
|
1389
|
+
return quote.intentId.replace(/~[0-9a-f]{32}$/, "") === route ? null : note("the intent id does not name this route", "policy_refused");
|
|
1277
1390
|
}
|
|
1278
1391
|
var lifiPins = Object.freeze({
|
|
1279
1392
|
callTargets: (chain) => /^eip155:[0-9]+$/.test(chain) ? [diamondFor(chain)] : [],
|
|
1280
1393
|
spenders: (chain) => /^eip155:[0-9]+$/.test(chain) ? [diamondFor(chain)] : [],
|
|
1281
|
-
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)
|
|
1282
1397
|
});
|
|
1283
1398
|
//# sourceMappingURL=provider.cjs.map
|