@anywalletkit/protocol-swap2-lifi 0.6.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -2
- package/dist/{chunk-6N4HZ5XX.js → chunk-AARB4MBP.js} +2 -2
- package/dist/chunk-AARB4MBP.js.map +1 -0
- package/dist/{chunk-IZ7TP4KT.js → chunk-GWUGK4SG.js} +219 -32
- package/dist/chunk-GWUGK4SG.js.map +1 -0
- package/dist/{chunk-YABKNR7N.js → chunk-OYGVC7DJ.js} +3 -3
- package/dist/config.cjs +1 -1
- package/dist/config.cjs.map +1 -1
- package/dist/config.js +1 -1
- package/dist/index.cjs +218 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -3
- package/dist/protocol.cjs +218 -31
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.js +3 -3
- package/dist/provider.cjs +218 -31
- package/dist/provider.cjs.map +1 -1
- package/dist/provider.d.cts +3 -1
- package/dist/provider.d.ts +3 -1
- package/dist/provider.js +2 -2
- package/package.json +7 -5
- package/dist/chunk-6N4HZ5XX.js.map +0 -1
- package/dist/chunk-IZ7TP4KT.js.map +0 -1
- /package/dist/{chunk-YABKNR7N.js.map → chunk-OYGVC7DJ.js.map} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -36,7 +36,7 @@ module.exports = __toCommonJS(src_exports);
|
|
|
36
36
|
// src/config.ts
|
|
37
37
|
var import_util = require("@anywalletkit/protocol-swap2-core/util");
|
|
38
38
|
var LIFI_SOLANA_CAIP2 = "solana:mainnet";
|
|
39
|
-
var LIFI_EVM = /* @__PURE__ */ new Set([1, 8453, 56, 137, 42161, 10, 100, 143, 130, 999, 4663, 43114, 534352, 80094, 324, 42220, 4326, 9745, 59144, 146, 480, 1329, 57073, 98866, 196, 5042, 5e3, 747474, 2818, 14]);
|
|
39
|
+
var LIFI_EVM = /* @__PURE__ */ new Set([1, 8453, 56, 137, 42161, 10, 100, 143, 130, 999, 4663, 43114, 534352, 80094, 324, 42220, 4326, 9745, 59144, 146, 480, 1329, 57073, 98866, 196, 5042, 5e3, 747474, 2818, 14, 99901]);
|
|
40
40
|
var lifi = {
|
|
41
41
|
enabled: () => true,
|
|
42
42
|
apiKey: () => void 0,
|
|
@@ -81,6 +81,70 @@ function describeLifiActivity(tx, ctx, isDiamond) {
|
|
|
81
81
|
});
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
+
// src/solana-address.ts
|
|
85
|
+
var import_ed25519 = require("@noble/curves/ed25519.js");
|
|
86
|
+
var import_sha2 = require("@noble/hashes/sha2.js");
|
|
87
|
+
var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
88
|
+
var BASE58 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
89
|
+
var SOLANA_TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
90
|
+
var ASSOCIATED_TOKEN_PROGRAM = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
|
|
91
|
+
var PDA_MARKER = Uint8Array.from("ProgramDerivedAddress", (c) => c.charCodeAt(0));
|
|
92
|
+
function base58(bytes) {
|
|
93
|
+
let n = 0n;
|
|
94
|
+
for (const b of bytes) n = n << 8n | BigInt(b);
|
|
95
|
+
let out = "";
|
|
96
|
+
while (n > 0n) {
|
|
97
|
+
out = ALPHABET[Number(n % 58n)] + out;
|
|
98
|
+
n /= 58n;
|
|
99
|
+
}
|
|
100
|
+
for (const b of bytes) {
|
|
101
|
+
if (b !== 0) break;
|
|
102
|
+
out = `1${out}`;
|
|
103
|
+
}
|
|
104
|
+
return out;
|
|
105
|
+
}
|
|
106
|
+
function solanaKey(address) {
|
|
107
|
+
if (!BASE58.test(address)) return null;
|
|
108
|
+
let n = 0n;
|
|
109
|
+
for (const ch of address) n = n * 58n + BigInt(ALPHABET.indexOf(ch));
|
|
110
|
+
let zeros = 0;
|
|
111
|
+
while (address[zeros] === "1") zeros++;
|
|
112
|
+
const body = [];
|
|
113
|
+
while (n > 0n) {
|
|
114
|
+
body.unshift(Number(n & 0xffn));
|
|
115
|
+
n >>= 8n;
|
|
116
|
+
}
|
|
117
|
+
if (zeros + body.length !== 32) return null;
|
|
118
|
+
const out = new Uint8Array(32);
|
|
119
|
+
out.set(body, zeros);
|
|
120
|
+
return out;
|
|
121
|
+
}
|
|
122
|
+
var onCurve = (bytes) => {
|
|
123
|
+
try {
|
|
124
|
+
import_ed25519.ed25519.Point.fromBytes(bytes);
|
|
125
|
+
return true;
|
|
126
|
+
} catch {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
function programAddress(seeds, program) {
|
|
131
|
+
for (let bump = 255; bump >= 0; bump--) {
|
|
132
|
+
const parts = [...seeds, Uint8Array.of(bump), program, PDA_MARKER];
|
|
133
|
+
const buf = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
|
|
134
|
+
let at = 0;
|
|
135
|
+
for (const p of parts) {
|
|
136
|
+
buf.set(p, at);
|
|
137
|
+
at += p.length;
|
|
138
|
+
}
|
|
139
|
+
const hash = (0, import_sha2.sha256)(buf);
|
|
140
|
+
if (!onCurve(hash)) return hash;
|
|
141
|
+
}
|
|
142
|
+
throw new Error("no program address for these seeds");
|
|
143
|
+
}
|
|
144
|
+
function associatedTokenAccount(owner, mint, tokenProgram = solanaKey(SOLANA_TOKEN_PROGRAM)) {
|
|
145
|
+
return programAddress([owner, tokenProgram, mint], solanaKey(ASSOCIATED_TOKEN_PROGRAM));
|
|
146
|
+
}
|
|
147
|
+
|
|
84
148
|
// src/calldata.ts
|
|
85
149
|
var GENERIC_SWAPS = {
|
|
86
150
|
"0x4630a0d8": { single: false },
|
|
@@ -391,8 +455,9 @@ var FACET_DATA = {
|
|
|
391
455
|
Frax: { inline: 5, refunds: [["refundRecipient", 3]], zeroRefused: true },
|
|
392
456
|
Glacis: { inline: 4, refunds: [["refundAddress", 1]], zeroRefused: true },
|
|
393
457
|
LayerSwap: { refunds: [["refundRecipient", 2]], zeroRefused: true },
|
|
458
|
+
// The escrow order's user. Not reached while LI.FI Intents is refused on every EVM-source route (INTENTS_UNPINNED); kept for when
|
|
459
|
+
// its oracles and settler are pinned.
|
|
394
460
|
LiFiIntentEscrowV2: { refunds: [["depositAndRefundAddress", 2]], zeroRefused: true },
|
|
395
|
-
// the escrow order's user
|
|
396
461
|
M0: { inline: 7, refunds: [["refundRecipient", 1], ["orderOwner", 2]], zeroRefused: true, delivery: m0Delivery },
|
|
397
462
|
// orderOwner gets a cancelled order back
|
|
398
463
|
Mayan: { refunds: [["refundRecipient", 7]], nested: mayanRefunds, zeroRefused: true },
|
|
@@ -420,6 +485,86 @@ function refundMismatch(r, bridge, e) {
|
|
|
420
485
|
}
|
|
421
486
|
return null;
|
|
422
487
|
}
|
|
488
|
+
var NON_EVM_UNREAD = "the recipient on a non-EVM destination sits in bridge-specific data and cannot be checked here";
|
|
489
|
+
var LIFI_SOLANA_CHAIN_ID = 1151111081099710n;
|
|
490
|
+
var NON_EVM_ADDRESS = "0x11f111f111f111f111f111f111f111f111f111f1";
|
|
491
|
+
var SOLANA_USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
492
|
+
var WORMHOLE_SOLANA = 1n;
|
|
493
|
+
var CCTP_DOMAIN_SOLANA = 5n;
|
|
494
|
+
var CHAINFLIP_SOLANA_ASSETS = {
|
|
495
|
+
"11111111111111111111111111111111": 9n,
|
|
496
|
+
[SOLANA_USDC]: 10n,
|
|
497
|
+
Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB: 16n
|
|
498
|
+
};
|
|
499
|
+
var DEPOSIT_FACETS = /* @__PURE__ */ new Set(["NEARIntents", "LayerSwap", "Unit"]);
|
|
500
|
+
var MAYAN_SOLANA = {
|
|
501
|
+
// Swift v2 createOrderWithToken(tokenIn, amountIn, (payloadType, trader, destAddr, destChainId, referrerAddr, tokenOut, minAmountOut, …), customPayload)
|
|
502
|
+
"0xa3a30834": { destAddr: 4, chain: 5, cctp: false, payloadType: [2, 1n], tokenOut: 7 },
|
|
503
|
+
// MCTP createOrder((tokenIn, amountIn, gasDrop, destAddr, destChain, tokenOut, minAmountOut, deadline, redeemFee, referrerAddr, referrerBps))
|
|
504
|
+
"0x1c59b7fc": { destAddr: 3, chain: 4, cctp: false, tokenOut: 5 },
|
|
505
|
+
// MCTP bridgeWithFee(tokenIn, amountIn, redeemFee, gasDrop, destAddr, destDomain, payloadType, customPayload): delivers USDC
|
|
506
|
+
"0x2072197f": { destAddr: 4, chain: 5, cctp: true, payloadType: [6, 1n] },
|
|
507
|
+
// Fast MCTP bridge(tokenIn, amountIn, redeemFee, circleMaxFee, gasDrop, destAddr, destDomain, referrerAddress, referrerBps, payloadType, minFinalityThreshold, customPayload): delivers USDC
|
|
508
|
+
"0xf58b6de8": { destAddr: 5, chain: 6, cctp: true, payloadType: [9, 1n] },
|
|
509
|
+
// Fast MCTP createOrder(tokenIn, amountIn, circleMaxFee, destDomain, minFinalityThreshold, (payloadType 3, destAddr, tokenOut, amountOutMin, …))
|
|
510
|
+
"0x2337e236": { destAddr: 6, chain: 3, cctp: true, payloadType: [5, 3n], tokenOut: 7 }
|
|
511
|
+
};
|
|
512
|
+
var hexOf = (b) => `0x${Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("")}`;
|
|
513
|
+
var keyOf = (word) => base58(Uint8Array.from(word.slice(2).match(/../g) ?? [], (h) => Number.parseInt(h, 16)));
|
|
514
|
+
function mayanToSolana(r, at, t) {
|
|
515
|
+
const declared = r.raw(at, 32);
|
|
516
|
+
if (declared !== t.ownerHex) return `Mayan's nonEVMReceiver is ${keyOf(declared)}, not the recipient ${t.e.receiver}`;
|
|
517
|
+
const p = r.bytes(at, at + 32 * 2);
|
|
518
|
+
const selector = p.raw(0, 4);
|
|
519
|
+
const o = MAYAN_SOLANA[selector];
|
|
520
|
+
if (!o) return `the Mayan call ${selector} is an entry point whose Solana recipient cannot be checked here`;
|
|
521
|
+
const word = (i) => 4 + 32 * i;
|
|
522
|
+
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`;
|
|
523
|
+
const dest = p.raw(word(o.destAddr), 32);
|
|
524
|
+
if (dest !== t.ownerHex) return `the Mayan order pays ${keyOf(dest)} on Solana, not the recipient ${t.e.receiver}`;
|
|
525
|
+
const chain = p.word(word(o.chain));
|
|
526
|
+
if (chain !== (o.cctp ? CCTP_DOMAIN_SOLANA : WORMHOLE_SOLANA)) return `the Mayan order goes to ${o.cctp ? "CCTP domain" : "Wormhole chain"} ${chain}, not Solana`;
|
|
527
|
+
if (o.tokenOut === void 0) return t.e.toToken === SOLANA_USDC ? null : `a Mayan CCTP transfer delivers USDC, not ${t.e.toToken}`;
|
|
528
|
+
const token = p.raw(word(o.tokenOut), 32);
|
|
529
|
+
return token === t.mintHex ? null : `the Mayan order delivers ${keyOf(token)}, not ${t.e.toToken}`;
|
|
530
|
+
}
|
|
531
|
+
function polymerToSolana(r, at, t) {
|
|
532
|
+
const declared = r.raw(at + 32 * 2, 32);
|
|
533
|
+
if (declared !== t.ownerHex) return `Polymer CCTP's nonEVMReceiver is ${keyOf(declared)}, not the recipient ${t.e.receiver}`;
|
|
534
|
+
if (t.e.toToken !== SOLANA_USDC) return `Polymer CCTP delivers USDC, not ${t.e.toToken}`;
|
|
535
|
+
const mintTo = r.raw(at + 32 * 3, 32);
|
|
536
|
+
const account = hexOf(associatedTokenAccount(t.owner, t.mint));
|
|
537
|
+
return mintTo === account ? null : `the Polymer CCTP transfer mints to ${keyOf(mintTo)}, not the recipient's USDC account ${keyOf(account)}`;
|
|
538
|
+
}
|
|
539
|
+
function chainflipToSolana(r, at, t) {
|
|
540
|
+
const dst = r.bytes(at, at);
|
|
541
|
+
if (dst.length !== 32) return `Chainflip pays a ${dst.length}-byte address on Solana, not the recipient ${t.e.receiver}`;
|
|
542
|
+
if (dst.raw(0, 32) !== t.ownerHex) return `Chainflip pays ${keyOf(dst.raw(0, 32))} on Solana, not the recipient ${t.e.receiver}`;
|
|
543
|
+
const asset = r.word(at + 32);
|
|
544
|
+
const want = CHAINFLIP_SOLANA_ASSETS[t.e.toToken];
|
|
545
|
+
return want !== void 0 && asset === want ? null : `Chainflip delivers its asset ${asset}, not ${t.e.toToken}`;
|
|
546
|
+
}
|
|
547
|
+
var SOLANA_DELIVERY = {
|
|
548
|
+
Mayan: mayanToSolana,
|
|
549
|
+
PolymerCCTP: polymerToSolana,
|
|
550
|
+
Chainflip: chainflipToSolana
|
|
551
|
+
};
|
|
552
|
+
var INTENTS_TO_SOLANA = "a LI.FI Intents order to Solana names its own oracles, output settler and output amount, which cannot be checked here \u2014 its escrow could release the input without the delivery";
|
|
553
|
+
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";
|
|
554
|
+
var INTENTS_FACET = "LiFiIntentEscrowV2";
|
|
555
|
+
function solanaRecipientMismatch(r, bridge, receiver, e) {
|
|
556
|
+
if (e.destChainId !== LIFI_SOLANA_CHAIN_ID) return NON_EVM_UNREAD;
|
|
557
|
+
if (!same(receiver, NON_EVM_ADDRESS)) return `BridgeData pays ${receiver}, not LI.FI's non-EVM placeholder ${NON_EVM_ADDRESS}, on a Solana route`;
|
|
558
|
+
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`;
|
|
559
|
+
if (bridge.facet === INTENTS_FACET) return INTENTS_TO_SOLANA;
|
|
560
|
+
const read = SOLANA_DELIVERY[bridge.facet];
|
|
561
|
+
if (!read) return NON_EVM_UNREAD;
|
|
562
|
+
const owner = solanaKey(e.receiver);
|
|
563
|
+
if (!owner) return `the recipient ${e.receiver} is not a Solana address`;
|
|
564
|
+
const mint = solanaKey(e.toToken);
|
|
565
|
+
if (!mint) return `the destination token ${e.toToken} is not a Solana mint`;
|
|
566
|
+
return read(r, facetDataAt(r, bridge, FACET_DATA[bridge.facet]), { owner, ownerHex: hexOf(owner), mint, mintHex: hexOf(mint), e });
|
|
567
|
+
}
|
|
423
568
|
function diamondCallMismatch(data, e) {
|
|
424
569
|
let r;
|
|
425
570
|
try {
|
|
@@ -448,6 +593,7 @@ function diamondCallMismatch(data, e) {
|
|
|
448
593
|
if (unbound) return `${unbound} delivers to a recipient the transaction does not bind (an off-chain order or unchecked facet data)`;
|
|
449
594
|
const bridge = BRIDGE_CALLS[selector];
|
|
450
595
|
if (!bridge) return `unknown Diamond call ${selector}`;
|
|
596
|
+
if (bridge.facet === INTENTS_FACET && e.evmDestination) return INTENTS_UNPINNED;
|
|
451
597
|
const head = r.small(4);
|
|
452
598
|
if (head < (bridge.sourceSwaps ? 96 : 64) || head % 32 !== 0 || head > 32 * 32) return `unknown Diamond call ${selector}`;
|
|
453
599
|
const at = 4 + head;
|
|
@@ -461,8 +607,10 @@ function diamondCallMismatch(data, e) {
|
|
|
461
607
|
if (e.sameChain) return "a bridge call on a same-chain route";
|
|
462
608
|
if (destinationChainId !== e.destChainId) return `bridges to chain ${destinationChainId}, not ${e.destChainId}`;
|
|
463
609
|
if (hasDestinationCall) return "the route runs a call on the destination chain, whose recipient cannot be checked here";
|
|
464
|
-
if (!e.evmDestination)
|
|
465
|
-
|
|
610
|
+
if (!e.evmDestination) {
|
|
611
|
+
const why = solanaRecipientMismatch(r, bridge, receiver, e);
|
|
612
|
+
if (why) return why;
|
|
613
|
+
} else if (!same(receiver, e.receiver)) return `pays ${receiver}, not ${e.receiver}`;
|
|
466
614
|
if (hasSourceSwaps !== bridge.sourceSwaps) return `unknown Diamond call ${selector}`;
|
|
467
615
|
const refund = refundMismatch(r, bridge, e);
|
|
468
616
|
if (refund) return refund;
|
|
@@ -482,7 +630,6 @@ function diamondCallMismatch(data, e) {
|
|
|
482
630
|
|
|
483
631
|
// src/solana.ts
|
|
484
632
|
var import_util2 = require("@anywalletkit/protocol-swap2-core/util");
|
|
485
|
-
var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
486
633
|
var MAYAN_ENTRY_PROGRAMS = /* @__PURE__ */ new Set([
|
|
487
634
|
"D8C8iW6zmoKg5TRr8nQ7h14TMWqQX8FiBdj2ju5MF3wa",
|
|
488
635
|
// CPI_PROXY_PROGRAM_ID
|
|
@@ -497,20 +644,6 @@ var MAYAN_ENTRY_PROGRAMS = /* @__PURE__ */ new Set([
|
|
|
497
644
|
]);
|
|
498
645
|
var SYSTEM_PROGRAM = "11111111111111111111111111111111";
|
|
499
646
|
var LIFI_SOLANA_FEE_WALLET = "34FKjAdVcTax2DHqV2XnbXa9J3zmyKcFuFKWbcmgxjgm";
|
|
500
|
-
function base58(bytes) {
|
|
501
|
-
let n = 0n;
|
|
502
|
-
for (const b of bytes) n = n << 8n | BigInt(b);
|
|
503
|
-
let out = "";
|
|
504
|
-
while (n > 0n) {
|
|
505
|
-
out = ALPHABET[Number(n % 58n)] + out;
|
|
506
|
-
n /= 58n;
|
|
507
|
-
}
|
|
508
|
-
for (const b of bytes) {
|
|
509
|
-
if (b !== 0) break;
|
|
510
|
-
out = `1${out}`;
|
|
511
|
-
}
|
|
512
|
-
return out;
|
|
513
|
-
}
|
|
514
647
|
var Cursor = class {
|
|
515
648
|
constructor(b) {
|
|
516
649
|
this.b = b;
|
|
@@ -611,6 +744,10 @@ var LIFI_CHAIN = {
|
|
|
611
744
|
"eip155:747474": "747474",
|
|
612
745
|
"eip155:2818": "2818",
|
|
613
746
|
"eip155:14": "14",
|
|
747
|
+
// 2026-11-10: Elysium (Kinetiq; li.quest/v1/chains id 99901, key "ely"). LI.FI's bridges there are Relay (refused
|
|
748
|
+
// below: UNBOUND_TOOLS) and LI.FI Intents (refused too: UNPINNED_TOOLS), so it has no cross-chain LI.FI route for now;
|
|
749
|
+
// same-chain swaps go through GenericSwapFacetV3 (KyberSwap, Nordstern).
|
|
750
|
+
"eip155:99901": "99901",
|
|
614
751
|
[LIFI_SOLANA_CAIP2]: "1151111081099710"
|
|
615
752
|
};
|
|
616
753
|
var CAIP2_BY_LIFI_CHAIN = Object.fromEntries(Object.entries(LIFI_CHAIN).map(([caip2, id]) => [id, caip2]));
|
|
@@ -619,12 +756,12 @@ var SOLANA_NATIVE = "11111111111111111111111111111111";
|
|
|
619
756
|
var EVM_PLACEHOLDER = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
|
|
620
757
|
var SOLANA_PLACEHOLDER = "HAgk14JpMQLgt6rVgv7cBQFJWFto5Dqxi472uT3DKpqk";
|
|
621
758
|
var isSolana = (chain) => chain === LIFI_SOLANA_CAIP2;
|
|
622
|
-
var
|
|
759
|
+
var BASE582 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
623
760
|
var BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
624
761
|
var SAME_CHAIN_MAX_SECONDS = 90;
|
|
625
762
|
var MAX_INPUT_FEE_BPS = 300;
|
|
626
763
|
function sameChainAddress(chain, actual, expected) {
|
|
627
|
-
if (isSolana(chain)) return !!actual &&
|
|
764
|
+
if (isSolana(chain)) return !!actual && BASE582.test(actual) && actual === expected;
|
|
628
765
|
return sameAddress(actual, expected);
|
|
629
766
|
}
|
|
630
767
|
function nativeFor(chain, token) {
|
|
@@ -663,6 +800,10 @@ var LIFI_DIAMONDS = {
|
|
|
663
800
|
// Morph
|
|
664
801
|
"eip155:14": "0x198FC70Dfe05E755C81e54bd67Bff3F729344B9b",
|
|
665
802
|
// Flare (li.quest diamondAddress 2026-10-14; the canonical 0x1231… has no code there)
|
|
803
|
+
// Elysium: li.quest diamondAddress = deployments/elysium.json LiFiDiamond; code on 99901, DiamondLoupe facets() serves
|
|
804
|
+
// GenericSwapFacetV3 + LiFiIntentEscrowFacetV2; the canonical 0x1231… has no code there (all read 2026-11-10).
|
|
805
|
+
"eip155:99901": "0x24E6dA91AA4A8FEe2447F542c92E006243bd6Dc5",
|
|
806
|
+
// Elysium
|
|
666
807
|
"eip155:4326": "0x026F252016A7C47CDEf1F05a3Fc9E20C92a49C37"
|
|
667
808
|
// MegaETH (deployments/megaeth.json, matches a live quote 2026-10-12)
|
|
668
809
|
};
|
|
@@ -709,7 +850,16 @@ function headers(key) {
|
|
|
709
850
|
}
|
|
710
851
|
var TOOL_KEY = /^[A-Za-z0-9_-]{1,64}$/;
|
|
711
852
|
var UNBOUND_TOOLS = Object.freeze(["relaydepository", "garden", "squid", "symbiosis"]);
|
|
712
|
-
|
|
853
|
+
var UNPINNED_TOOLS = Object.freeze(["lifiIntents"]);
|
|
854
|
+
var REFUSED_EVM_TOOLS = Object.freeze([...UNBOUND_TOOLS, ...UNPINNED_TOOLS]);
|
|
855
|
+
var SOLANA_DESTINATION_TOOLS = Object.freeze(["mayan", "mayanMCTP", "mayanFastMCTP", "polymer", "polymerStandard", "chainflip"]);
|
|
856
|
+
var SMART_DEPOSITS_TOOL = /^smartdeposits?$/i;
|
|
857
|
+
var dryRecipient = (params, fromAddress) => !isSolana(params.sourceChain) && isSolana(params.destChain) ? SOLANA_PLACEHOLDER : fromAddress;
|
|
858
|
+
function placeholderIn(params) {
|
|
859
|
+
if (!params.refundAddress) return "sender";
|
|
860
|
+
return !params.destAddress && dryRecipient(params, params.refundAddress) !== params.refundAddress ? "recipient" : null;
|
|
861
|
+
}
|
|
862
|
+
function routeOptions(body, excluded, allowed) {
|
|
713
863
|
const routes = body?.routes;
|
|
714
864
|
if (!Array.isArray(routes)) return [];
|
|
715
865
|
const out = [];
|
|
@@ -719,7 +869,7 @@ function routeOptions(body, excluded) {
|
|
|
719
869
|
const cross = Array.isArray(step.includedSteps) ? step.includedSteps.find((i) => i?.type === "cross")?.tool : void 0;
|
|
720
870
|
const tool = typeof cross === "string" ? cross : step.tool;
|
|
721
871
|
const eta = step.estimate?.executionDuration;
|
|
722
|
-
if (typeof tool !== "string" || !TOOL_KEY.test(tool) || excluded.includes(tool) || typeof eta !== "number" || !Number.isFinite(eta) || eta < 0) continue;
|
|
872
|
+
if (typeof tool !== "string" || !TOOL_KEY.test(tool) || excluded.includes(tool) || allowed && !allowed.includes(tool) || typeof eta !== "number" || !Number.isFinite(eta) || eta < 0) continue;
|
|
723
873
|
if (typeof r.toAmount !== "string" || typeof r.toAmountMin !== "string" || !/^\d{1,60}$/.test(r.toAmount) || !/^\d{1,60}$/.test(r.toAmountMin)) continue;
|
|
724
874
|
out.push({
|
|
725
875
|
tool,
|
|
@@ -784,13 +934,15 @@ function lifiProvider(config = {}) {
|
|
|
784
934
|
if (!fromToken) return null;
|
|
785
935
|
const solanaSource = isSolana(params.sourceChain);
|
|
786
936
|
const crossChain = params.sourceChain !== params.destChain;
|
|
937
|
+
const evmToSolana = !solanaSource && isSolana(params.destChain);
|
|
787
938
|
const noDestinationCall = crossChain && !solanaSource;
|
|
788
939
|
let routeScope = config.routeWideSlippage === true && !solanaSource && !isSolana(params.destChain);
|
|
789
940
|
const dry = params.dry !== false;
|
|
790
941
|
const fromAddress = params.refundAddress || (dry ? solanaSource ? SOLANA_PLACEHOLDER : EVM_PLACEHOLDER : "");
|
|
791
|
-
const toAddress = params.destAddress || (dry ? fromAddress : "");
|
|
942
|
+
const toAddress = params.destAddress || (dry ? dryRecipient(params, fromAddress) : "");
|
|
792
943
|
if (!fromAddress) return reject("a live quote needs the sender's address");
|
|
793
944
|
if (!toAddress) return reject("a live quote needs a recipient");
|
|
945
|
+
const placeholder = dry ? placeholderIn(params) : null;
|
|
794
946
|
const slippageBps = params.slippageBps ?? defaultSlippageBps;
|
|
795
947
|
let sentSlippage = slippageBps / 1e4;
|
|
796
948
|
let deadline;
|
|
@@ -811,7 +963,7 @@ function lifiProvider(config = {}) {
|
|
|
811
963
|
options: {
|
|
812
964
|
slippage: slippageBps / 1e4,
|
|
813
965
|
...routeScope ? { slippageScope: "route" } : {},
|
|
814
|
-
...solanaSource ? {} : { bridges: { deny:
|
|
966
|
+
...solanaSource ? {} : { bridges: { ...evmToSolana ? { allow: SOLANA_DESTINATION_TOOLS } : {}, deny: REFUSED_EVM_TOOLS } },
|
|
815
967
|
...noDestinationCall ? { allowDestinationCall: false } : {},
|
|
816
968
|
...integrator().trim() ? { integrator: integrator().trim() } : {}
|
|
817
969
|
}
|
|
@@ -819,7 +971,7 @@ function lifiProvider(config = {}) {
|
|
|
819
971
|
signal: signal ?? (0, import_util3.timeoutSignal)(8e3)
|
|
820
972
|
});
|
|
821
973
|
if (res.ok) {
|
|
822
|
-
const options = routeOptions(await res.json(), solanaSource ? [] :
|
|
974
|
+
const options = routeOptions(await res.json(), solanaSource ? [] : REFUSED_EVM_TOOLS, evmToSolana ? SOLANA_DESTINATION_TOOLS : void 0);
|
|
823
975
|
if (options.length) choice = cleanChoice(config.chooseRoute(options, params));
|
|
824
976
|
}
|
|
825
977
|
} catch {
|
|
@@ -841,8 +993,10 @@ function lifiProvider(config = {}) {
|
|
|
841
993
|
}
|
|
842
994
|
if (routeScope) url.searchParams.set("slippageScope", "route");
|
|
843
995
|
if (noDestinationCall) url.searchParams.set("allowDestinationCall", "false");
|
|
844
|
-
const unbound = crossChain && !solanaSource ?
|
|
845
|
-
|
|
996
|
+
const unbound = crossChain && !solanaSource ? REFUSED_EVM_TOOLS : [];
|
|
997
|
+
const allow = (choice?.allowBridges ?? []).filter((b) => solanaSource || !REFUSED_EVM_TOOLS.includes(b));
|
|
998
|
+
const solanaAllow = evmToSolana ? allow.filter((b) => SOLANA_DESTINATION_TOOLS.includes(b)) : [];
|
|
999
|
+
for (const t of evmToSolana ? solanaAllow.length ? solanaAllow : SOLANA_DESTINATION_TOOLS : allow) url.searchParams.append("allowBridges", t);
|
|
846
1000
|
for (const t of /* @__PURE__ */ new Set([...choice?.denyBridges ?? [], ...unbound])) url.searchParams.append("denyBridges", t);
|
|
847
1001
|
if (choice?.order) url.searchParams.set("order", choice.order);
|
|
848
1002
|
deadline = signal ? void 0 : (0, import_util3.timeoutSignal)(12e3);
|
|
@@ -866,6 +1020,13 @@ function lifiProvider(config = {}) {
|
|
|
866
1020
|
}
|
|
867
1021
|
const q = await res.json();
|
|
868
1022
|
const receivedAt = Date.now();
|
|
1023
|
+
if (evmToSolana) {
|
|
1024
|
+
const steps = q.includedSteps;
|
|
1025
|
+
const tools = [q.tool, ...Array.isArray(steps) ? steps.map((i) => i?.tool) : []];
|
|
1026
|
+
if (tools.some((t) => typeof t === "string" && SMART_DEPOSITS_TOOL.test(t))) {
|
|
1027
|
+
return reject("LI.FI Smart Deposits pay a deposit address whose recipient the transaction does not bind \u2014 refused for a Solana destination");
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
869
1030
|
const action = q.action;
|
|
870
1031
|
const est = q.estimate;
|
|
871
1032
|
const tr = q.transactionRequest;
|
|
@@ -906,6 +1067,7 @@ function lifiProvider(config = {}) {
|
|
|
906
1067
|
if (!trustedAddress(params.sourceChain, est.approvalAddress, trustedApprovalAddresses)) return reject("the approval target is not a trusted LI.FI contract");
|
|
907
1068
|
}
|
|
908
1069
|
let txRequest;
|
|
1070
|
+
let nativeFee = 0n;
|
|
909
1071
|
if (solanaSource) {
|
|
910
1072
|
txRequest = { serializedTx: tr.data, value: sourceIsToken ? "0" : params.amountIn };
|
|
911
1073
|
} else {
|
|
@@ -915,7 +1077,7 @@ function lifiProvider(config = {}) {
|
|
|
915
1077
|
return reject("the transaction value or gas limit is not a number");
|
|
916
1078
|
}
|
|
917
1079
|
const value = BigInt(tr.value);
|
|
918
|
-
|
|
1080
|
+
nativeFee = sourceIsToken ? value : value > BigInt(params.amountIn) ? value - BigInt(params.amountIn) : 0n;
|
|
919
1081
|
if (!sourceIsToken && value < BigInt(params.amountIn)) return reject("the transaction sends less native coin than the input");
|
|
920
1082
|
txRequest = {
|
|
921
1083
|
to: tr.to,
|
|
@@ -976,7 +1138,10 @@ function lifiProvider(config = {}) {
|
|
|
976
1138
|
intentId: `lifi:${fromChain}:${toChain}`,
|
|
977
1139
|
depositAddress: "",
|
|
978
1140
|
depositMemo: null,
|
|
979
|
-
|
|
1141
|
+
// Every check above ran on LI.FI's transaction; one built for a placeholder is not handed out (placeholderIn).
|
|
1142
|
+
// Its bridge fee on top of the input still is, as `providerData.nativeFee`, so the router's value nets it out
|
|
1143
|
+
// (usdValue reads it when there is no txRequest) instead of ranking the route on its gross output.
|
|
1144
|
+
...placeholder ? { providerData: { placeholder, ...nativeFee > 0n ? { nativeFee: nativeFee.toString() } : {} } } : { txRequest },
|
|
980
1145
|
// What the wallet spends (action.fromAmount, = the input); an input-side bridge fee is inside destAmount.
|
|
981
1146
|
sourceAmount: params.amountIn,
|
|
982
1147
|
destAmount: est.toAmount,
|
|
@@ -996,6 +1161,10 @@ function lifiProvider(config = {}) {
|
|
|
996
1161
|
return null;
|
|
997
1162
|
},
|
|
998
1163
|
async buildDeposit(quote) {
|
|
1164
|
+
const placeholder = quote.providerData?.placeholder;
|
|
1165
|
+
if (placeholder !== void 0) {
|
|
1166
|
+
throw new Error(`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`);
|
|
1167
|
+
}
|
|
999
1168
|
const tx = quote.txRequest;
|
|
1000
1169
|
if (!tx) throw new Error("swap2: LI.FI quote has no transaction request");
|
|
1001
1170
|
const [, fromChain] = quote.intentId.split(":");
|
|
@@ -1047,6 +1216,11 @@ function lifiVerifyQuote(params, quote) {
|
|
|
1047
1216
|
const fromChain = LIFI_CHAIN[params.sourceChain];
|
|
1048
1217
|
const toChain = LIFI_CHAIN[params.destChain];
|
|
1049
1218
|
if (!fromChain || !toChain) return "not a LI.FI chain pair";
|
|
1219
|
+
const placeholder = params.dry !== false ? placeholderIn(params) : null;
|
|
1220
|
+
if (placeholder) {
|
|
1221
|
+
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`;
|
|
1222
|
+
return priceOnlyMismatch(params, quote, `lifi:${fromChain}:${toChain}`);
|
|
1223
|
+
}
|
|
1050
1224
|
const tx = quote.txRequest;
|
|
1051
1225
|
if (!tx) return "the quote carries no transaction";
|
|
1052
1226
|
if (isSolana(params.sourceChain)) {
|
|
@@ -1061,7 +1235,7 @@ function lifiVerifyQuote(params, quote) {
|
|
|
1061
1235
|
const sourceIsToken = fromToken !== EVM_ZERO;
|
|
1062
1236
|
const dry = params.dry !== false;
|
|
1063
1237
|
const fromAddress = params.refundAddress || (dry ? EVM_PLACEHOLDER : "");
|
|
1064
|
-
const toAddress = params.destAddress || (dry ? fromAddress : "");
|
|
1238
|
+
const toAddress = params.destAddress || (dry ? dryRecipient(params, fromAddress) : "");
|
|
1065
1239
|
if (!fromAddress || !toAddress) return "a live quote needs the sender and the recipient";
|
|
1066
1240
|
let amountIn, minOut, value, nativeFee;
|
|
1067
1241
|
try {
|
|
@@ -1095,6 +1269,19 @@ function lifiVerifyQuote(params, quote) {
|
|
|
1095
1269
|
});
|
|
1096
1270
|
return why ? `the transaction does not match the request: ${why}` : null;
|
|
1097
1271
|
}
|
|
1272
|
+
function priceOnlyMismatch(params, quote, route) {
|
|
1273
|
+
if (quote.depositAddress !== "") return `a LI.FI quote pays no deposit address, but this one names ${String(quote.depositAddress).slice(0, 64)}`;
|
|
1274
|
+
const fee = quote.providerData?.nativeFee;
|
|
1275
|
+
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`;
|
|
1276
|
+
try {
|
|
1277
|
+
if (BigInt(quote.sourceAmount) !== BigInt(params.amountIn)) return `the quote spends ${quote.sourceAmount}, not the ${params.amountIn} input`;
|
|
1278
|
+
const minOut = BigInt(quote.minDestAmount);
|
|
1279
|
+
if (params.minAmountOut !== void 0 && minOut < BigInt(params.minAmountOut)) return `the quoted minimum ${minOut} is below the caller's floor ${params.minAmountOut}`;
|
|
1280
|
+
} catch {
|
|
1281
|
+
return "the quote's amounts are not integers";
|
|
1282
|
+
}
|
|
1283
|
+
return quote.intentId.replace(/~[0-9a-f]{32}$/, "") === route ? null : "the intent id does not name this route";
|
|
1284
|
+
}
|
|
1098
1285
|
var lifiPins = Object.freeze({
|
|
1099
1286
|
callTargets: (chain) => /^eip155:[0-9]+$/.test(chain) ? [diamondFor(chain)] : [],
|
|
1100
1287
|
spenders: (chain) => /^eip155:[0-9]+$/.test(chain) ? [diamondFor(chain)] : [],
|