@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.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {
|
|
2
2
|
LifiSwap2Protocol
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-OYGVC7DJ.js";
|
|
4
4
|
import {
|
|
5
5
|
lifiPins,
|
|
6
6
|
lifiProvider,
|
|
7
7
|
lifiVerifyQuote
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-GWUGK4SG.js";
|
|
9
9
|
import {
|
|
10
10
|
LIFI_SOLANA_CAIP2,
|
|
11
11
|
lifi
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-AARB4MBP.js";
|
|
13
13
|
import {
|
|
14
14
|
LIFI_MODULE,
|
|
15
15
|
LIFI_SAME_CHAIN_SELECTORS,
|
package/dist/protocol.cjs
CHANGED
|
@@ -28,7 +28,7 @@ var import_single = require("@anywalletkit/protocol-swap2-core/swap2/single");
|
|
|
28
28
|
// src/config.ts
|
|
29
29
|
var import_util = require("@anywalletkit/protocol-swap2-core/util");
|
|
30
30
|
var LIFI_SOLANA_CAIP2 = "solana:mainnet";
|
|
31
|
-
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]);
|
|
31
|
+
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]);
|
|
32
32
|
var lifi = {
|
|
33
33
|
enabled: () => true,
|
|
34
34
|
apiKey: () => void 0,
|
|
@@ -73,6 +73,70 @@ function describeLifiActivity(tx, ctx, isDiamond) {
|
|
|
73
73
|
});
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
// src/solana-address.ts
|
|
77
|
+
var import_ed25519 = require("@noble/curves/ed25519.js");
|
|
78
|
+
var import_sha2 = require("@noble/hashes/sha2.js");
|
|
79
|
+
var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
80
|
+
var BASE58 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
81
|
+
var SOLANA_TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
82
|
+
var ASSOCIATED_TOKEN_PROGRAM = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
|
|
83
|
+
var PDA_MARKER = Uint8Array.from("ProgramDerivedAddress", (c) => c.charCodeAt(0));
|
|
84
|
+
function base58(bytes) {
|
|
85
|
+
let n = 0n;
|
|
86
|
+
for (const b of bytes) n = n << 8n | BigInt(b);
|
|
87
|
+
let out = "";
|
|
88
|
+
while (n > 0n) {
|
|
89
|
+
out = ALPHABET[Number(n % 58n)] + out;
|
|
90
|
+
n /= 58n;
|
|
91
|
+
}
|
|
92
|
+
for (const b of bytes) {
|
|
93
|
+
if (b !== 0) break;
|
|
94
|
+
out = `1${out}`;
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
function solanaKey(address) {
|
|
99
|
+
if (!BASE58.test(address)) return null;
|
|
100
|
+
let n = 0n;
|
|
101
|
+
for (const ch of address) n = n * 58n + BigInt(ALPHABET.indexOf(ch));
|
|
102
|
+
let zeros = 0;
|
|
103
|
+
while (address[zeros] === "1") zeros++;
|
|
104
|
+
const body = [];
|
|
105
|
+
while (n > 0n) {
|
|
106
|
+
body.unshift(Number(n & 0xffn));
|
|
107
|
+
n >>= 8n;
|
|
108
|
+
}
|
|
109
|
+
if (zeros + body.length !== 32) return null;
|
|
110
|
+
const out = new Uint8Array(32);
|
|
111
|
+
out.set(body, zeros);
|
|
112
|
+
return out;
|
|
113
|
+
}
|
|
114
|
+
var onCurve = (bytes) => {
|
|
115
|
+
try {
|
|
116
|
+
import_ed25519.ed25519.Point.fromBytes(bytes);
|
|
117
|
+
return true;
|
|
118
|
+
} catch {
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
function programAddress(seeds, program) {
|
|
123
|
+
for (let bump = 255; bump >= 0; bump--) {
|
|
124
|
+
const parts = [...seeds, Uint8Array.of(bump), program, PDA_MARKER];
|
|
125
|
+
const buf = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
|
|
126
|
+
let at = 0;
|
|
127
|
+
for (const p of parts) {
|
|
128
|
+
buf.set(p, at);
|
|
129
|
+
at += p.length;
|
|
130
|
+
}
|
|
131
|
+
const hash = (0, import_sha2.sha256)(buf);
|
|
132
|
+
if (!onCurve(hash)) return hash;
|
|
133
|
+
}
|
|
134
|
+
throw new Error("no program address for these seeds");
|
|
135
|
+
}
|
|
136
|
+
function associatedTokenAccount(owner, mint, tokenProgram = solanaKey(SOLANA_TOKEN_PROGRAM)) {
|
|
137
|
+
return programAddress([owner, tokenProgram, mint], solanaKey(ASSOCIATED_TOKEN_PROGRAM));
|
|
138
|
+
}
|
|
139
|
+
|
|
76
140
|
// src/calldata.ts
|
|
77
141
|
var GENERIC_SWAPS = {
|
|
78
142
|
"0x4630a0d8": { single: false },
|
|
@@ -383,8 +447,9 @@ var FACET_DATA = {
|
|
|
383
447
|
Frax: { inline: 5, refunds: [["refundRecipient", 3]], zeroRefused: true },
|
|
384
448
|
Glacis: { inline: 4, refunds: [["refundAddress", 1]], zeroRefused: true },
|
|
385
449
|
LayerSwap: { refunds: [["refundRecipient", 2]], zeroRefused: true },
|
|
450
|
+
// The escrow order's user. Not reached while LI.FI Intents is refused on every EVM-source route (INTENTS_UNPINNED); kept for when
|
|
451
|
+
// its oracles and settler are pinned.
|
|
386
452
|
LiFiIntentEscrowV2: { refunds: [["depositAndRefundAddress", 2]], zeroRefused: true },
|
|
387
|
-
// the escrow order's user
|
|
388
453
|
M0: { inline: 7, refunds: [["refundRecipient", 1], ["orderOwner", 2]], zeroRefused: true, delivery: m0Delivery },
|
|
389
454
|
// orderOwner gets a cancelled order back
|
|
390
455
|
Mayan: { refunds: [["refundRecipient", 7]], nested: mayanRefunds, zeroRefused: true },
|
|
@@ -412,6 +477,86 @@ function refundMismatch(r, bridge, e) {
|
|
|
412
477
|
}
|
|
413
478
|
return null;
|
|
414
479
|
}
|
|
480
|
+
var NON_EVM_UNREAD = "the recipient on a non-EVM destination sits in bridge-specific data and cannot be checked here";
|
|
481
|
+
var LIFI_SOLANA_CHAIN_ID = 1151111081099710n;
|
|
482
|
+
var NON_EVM_ADDRESS = "0x11f111f111f111f111f111f111f111f111f111f1";
|
|
483
|
+
var SOLANA_USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
484
|
+
var WORMHOLE_SOLANA = 1n;
|
|
485
|
+
var CCTP_DOMAIN_SOLANA = 5n;
|
|
486
|
+
var CHAINFLIP_SOLANA_ASSETS = {
|
|
487
|
+
"11111111111111111111111111111111": 9n,
|
|
488
|
+
[SOLANA_USDC]: 10n,
|
|
489
|
+
Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB: 16n
|
|
490
|
+
};
|
|
491
|
+
var DEPOSIT_FACETS = /* @__PURE__ */ new Set(["NEARIntents", "LayerSwap", "Unit"]);
|
|
492
|
+
var MAYAN_SOLANA = {
|
|
493
|
+
// Swift v2 createOrderWithToken(tokenIn, amountIn, (payloadType, trader, destAddr, destChainId, referrerAddr, tokenOut, minAmountOut, …), customPayload)
|
|
494
|
+
"0xa3a30834": { destAddr: 4, chain: 5, cctp: false, payloadType: [2, 1n], tokenOut: 7 },
|
|
495
|
+
// MCTP createOrder((tokenIn, amountIn, gasDrop, destAddr, destChain, tokenOut, minAmountOut, deadline, redeemFee, referrerAddr, referrerBps))
|
|
496
|
+
"0x1c59b7fc": { destAddr: 3, chain: 4, cctp: false, tokenOut: 5 },
|
|
497
|
+
// MCTP bridgeWithFee(tokenIn, amountIn, redeemFee, gasDrop, destAddr, destDomain, payloadType, customPayload): delivers USDC
|
|
498
|
+
"0x2072197f": { destAddr: 4, chain: 5, cctp: true, payloadType: [6, 1n] },
|
|
499
|
+
// Fast MCTP bridge(tokenIn, amountIn, redeemFee, circleMaxFee, gasDrop, destAddr, destDomain, referrerAddress, referrerBps, payloadType, minFinalityThreshold, customPayload): delivers USDC
|
|
500
|
+
"0xf58b6de8": { destAddr: 5, chain: 6, cctp: true, payloadType: [9, 1n] },
|
|
501
|
+
// Fast MCTP createOrder(tokenIn, amountIn, circleMaxFee, destDomain, minFinalityThreshold, (payloadType 3, destAddr, tokenOut, amountOutMin, …))
|
|
502
|
+
"0x2337e236": { destAddr: 6, chain: 3, cctp: true, payloadType: [5, 3n], tokenOut: 7 }
|
|
503
|
+
};
|
|
504
|
+
var hexOf = (b) => `0x${Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("")}`;
|
|
505
|
+
var keyOf = (word) => base58(Uint8Array.from(word.slice(2).match(/../g) ?? [], (h) => Number.parseInt(h, 16)));
|
|
506
|
+
function mayanToSolana(r, at, t) {
|
|
507
|
+
const declared = r.raw(at, 32);
|
|
508
|
+
if (declared !== t.ownerHex) return `Mayan's nonEVMReceiver is ${keyOf(declared)}, not the recipient ${t.e.receiver}`;
|
|
509
|
+
const p = r.bytes(at, at + 32 * 2);
|
|
510
|
+
const selector = p.raw(0, 4);
|
|
511
|
+
const o = MAYAN_SOLANA[selector];
|
|
512
|
+
if (!o) return `the Mayan call ${selector} is an entry point whose Solana recipient cannot be checked here`;
|
|
513
|
+
const word = (i) => 4 + 32 * i;
|
|
514
|
+
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`;
|
|
515
|
+
const dest = p.raw(word(o.destAddr), 32);
|
|
516
|
+
if (dest !== t.ownerHex) return `the Mayan order pays ${keyOf(dest)} on Solana, not the recipient ${t.e.receiver}`;
|
|
517
|
+
const chain = p.word(word(o.chain));
|
|
518
|
+
if (chain !== (o.cctp ? CCTP_DOMAIN_SOLANA : WORMHOLE_SOLANA)) return `the Mayan order goes to ${o.cctp ? "CCTP domain" : "Wormhole chain"} ${chain}, not Solana`;
|
|
519
|
+
if (o.tokenOut === void 0) return t.e.toToken === SOLANA_USDC ? null : `a Mayan CCTP transfer delivers USDC, not ${t.e.toToken}`;
|
|
520
|
+
const token = p.raw(word(o.tokenOut), 32);
|
|
521
|
+
return token === t.mintHex ? null : `the Mayan order delivers ${keyOf(token)}, not ${t.e.toToken}`;
|
|
522
|
+
}
|
|
523
|
+
function polymerToSolana(r, at, t) {
|
|
524
|
+
const declared = r.raw(at + 32 * 2, 32);
|
|
525
|
+
if (declared !== t.ownerHex) return `Polymer CCTP's nonEVMReceiver is ${keyOf(declared)}, not the recipient ${t.e.receiver}`;
|
|
526
|
+
if (t.e.toToken !== SOLANA_USDC) return `Polymer CCTP delivers USDC, not ${t.e.toToken}`;
|
|
527
|
+
const mintTo = r.raw(at + 32 * 3, 32);
|
|
528
|
+
const account = hexOf(associatedTokenAccount(t.owner, t.mint));
|
|
529
|
+
return mintTo === account ? null : `the Polymer CCTP transfer mints to ${keyOf(mintTo)}, not the recipient's USDC account ${keyOf(account)}`;
|
|
530
|
+
}
|
|
531
|
+
function chainflipToSolana(r, at, t) {
|
|
532
|
+
const dst = r.bytes(at, at);
|
|
533
|
+
if (dst.length !== 32) return `Chainflip pays a ${dst.length}-byte address on Solana, not the recipient ${t.e.receiver}`;
|
|
534
|
+
if (dst.raw(0, 32) !== t.ownerHex) return `Chainflip pays ${keyOf(dst.raw(0, 32))} on Solana, not the recipient ${t.e.receiver}`;
|
|
535
|
+
const asset = r.word(at + 32);
|
|
536
|
+
const want = CHAINFLIP_SOLANA_ASSETS[t.e.toToken];
|
|
537
|
+
return want !== void 0 && asset === want ? null : `Chainflip delivers its asset ${asset}, not ${t.e.toToken}`;
|
|
538
|
+
}
|
|
539
|
+
var SOLANA_DELIVERY = {
|
|
540
|
+
Mayan: mayanToSolana,
|
|
541
|
+
PolymerCCTP: polymerToSolana,
|
|
542
|
+
Chainflip: chainflipToSolana
|
|
543
|
+
};
|
|
544
|
+
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";
|
|
545
|
+
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";
|
|
546
|
+
var INTENTS_FACET = "LiFiIntentEscrowV2";
|
|
547
|
+
function solanaRecipientMismatch(r, bridge, receiver, e) {
|
|
548
|
+
if (e.destChainId !== LIFI_SOLANA_CHAIN_ID) return NON_EVM_UNREAD;
|
|
549
|
+
if (!same(receiver, NON_EVM_ADDRESS)) return `BridgeData pays ${receiver}, not LI.FI's non-EVM placeholder ${NON_EVM_ADDRESS}, on a Solana route`;
|
|
550
|
+
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`;
|
|
551
|
+
if (bridge.facet === INTENTS_FACET) return INTENTS_TO_SOLANA;
|
|
552
|
+
const read = SOLANA_DELIVERY[bridge.facet];
|
|
553
|
+
if (!read) return NON_EVM_UNREAD;
|
|
554
|
+
const owner = solanaKey(e.receiver);
|
|
555
|
+
if (!owner) return `the recipient ${e.receiver} is not a Solana address`;
|
|
556
|
+
const mint = solanaKey(e.toToken);
|
|
557
|
+
if (!mint) return `the destination token ${e.toToken} is not a Solana mint`;
|
|
558
|
+
return read(r, facetDataAt(r, bridge, FACET_DATA[bridge.facet]), { owner, ownerHex: hexOf(owner), mint, mintHex: hexOf(mint), e });
|
|
559
|
+
}
|
|
415
560
|
function diamondCallMismatch(data, e) {
|
|
416
561
|
let r;
|
|
417
562
|
try {
|
|
@@ -440,6 +585,7 @@ function diamondCallMismatch(data, e) {
|
|
|
440
585
|
if (unbound) return `${unbound} delivers to a recipient the transaction does not bind (an off-chain order or unchecked facet data)`;
|
|
441
586
|
const bridge = BRIDGE_CALLS[selector];
|
|
442
587
|
if (!bridge) return `unknown Diamond call ${selector}`;
|
|
588
|
+
if (bridge.facet === INTENTS_FACET && e.evmDestination) return INTENTS_UNPINNED;
|
|
443
589
|
const head = r.small(4);
|
|
444
590
|
if (head < (bridge.sourceSwaps ? 96 : 64) || head % 32 !== 0 || head > 32 * 32) return `unknown Diamond call ${selector}`;
|
|
445
591
|
const at = 4 + head;
|
|
@@ -453,8 +599,10 @@ function diamondCallMismatch(data, e) {
|
|
|
453
599
|
if (e.sameChain) return "a bridge call on a same-chain route";
|
|
454
600
|
if (destinationChainId !== e.destChainId) return `bridges to chain ${destinationChainId}, not ${e.destChainId}`;
|
|
455
601
|
if (hasDestinationCall) return "the route runs a call on the destination chain, whose recipient cannot be checked here";
|
|
456
|
-
if (!e.evmDestination)
|
|
457
|
-
|
|
602
|
+
if (!e.evmDestination) {
|
|
603
|
+
const why = solanaRecipientMismatch(r, bridge, receiver, e);
|
|
604
|
+
if (why) return why;
|
|
605
|
+
} else if (!same(receiver, e.receiver)) return `pays ${receiver}, not ${e.receiver}`;
|
|
458
606
|
if (hasSourceSwaps !== bridge.sourceSwaps) return `unknown Diamond call ${selector}`;
|
|
459
607
|
const refund = refundMismatch(r, bridge, e);
|
|
460
608
|
if (refund) return refund;
|
|
@@ -474,7 +622,6 @@ function diamondCallMismatch(data, e) {
|
|
|
474
622
|
|
|
475
623
|
// src/solana.ts
|
|
476
624
|
var import_util2 = require("@anywalletkit/protocol-swap2-core/util");
|
|
477
|
-
var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
478
625
|
var MAYAN_ENTRY_PROGRAMS = /* @__PURE__ */ new Set([
|
|
479
626
|
"D8C8iW6zmoKg5TRr8nQ7h14TMWqQX8FiBdj2ju5MF3wa",
|
|
480
627
|
// CPI_PROXY_PROGRAM_ID
|
|
@@ -489,20 +636,6 @@ var MAYAN_ENTRY_PROGRAMS = /* @__PURE__ */ new Set([
|
|
|
489
636
|
]);
|
|
490
637
|
var SYSTEM_PROGRAM = "11111111111111111111111111111111";
|
|
491
638
|
var LIFI_SOLANA_FEE_WALLET = "34FKjAdVcTax2DHqV2XnbXa9J3zmyKcFuFKWbcmgxjgm";
|
|
492
|
-
function base58(bytes) {
|
|
493
|
-
let n = 0n;
|
|
494
|
-
for (const b of bytes) n = n << 8n | BigInt(b);
|
|
495
|
-
let out = "";
|
|
496
|
-
while (n > 0n) {
|
|
497
|
-
out = ALPHABET[Number(n % 58n)] + out;
|
|
498
|
-
n /= 58n;
|
|
499
|
-
}
|
|
500
|
-
for (const b of bytes) {
|
|
501
|
-
if (b !== 0) break;
|
|
502
|
-
out = `1${out}`;
|
|
503
|
-
}
|
|
504
|
-
return out;
|
|
505
|
-
}
|
|
506
639
|
var Cursor = class {
|
|
507
640
|
constructor(b) {
|
|
508
641
|
this.b = b;
|
|
@@ -603,6 +736,10 @@ var LIFI_CHAIN = {
|
|
|
603
736
|
"eip155:747474": "747474",
|
|
604
737
|
"eip155:2818": "2818",
|
|
605
738
|
"eip155:14": "14",
|
|
739
|
+
// 2026-11-10: Elysium (Kinetiq; li.quest/v1/chains id 99901, key "ely"). LI.FI's bridges there are Relay (refused
|
|
740
|
+
// below: UNBOUND_TOOLS) and LI.FI Intents (refused too: UNPINNED_TOOLS), so it has no cross-chain LI.FI route for now;
|
|
741
|
+
// same-chain swaps go through GenericSwapFacetV3 (KyberSwap, Nordstern).
|
|
742
|
+
"eip155:99901": "99901",
|
|
606
743
|
[LIFI_SOLANA_CAIP2]: "1151111081099710"
|
|
607
744
|
};
|
|
608
745
|
var CAIP2_BY_LIFI_CHAIN = Object.fromEntries(Object.entries(LIFI_CHAIN).map(([caip2, id]) => [id, caip2]));
|
|
@@ -611,12 +748,12 @@ var SOLANA_NATIVE = "11111111111111111111111111111111";
|
|
|
611
748
|
var EVM_PLACEHOLDER = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
|
|
612
749
|
var SOLANA_PLACEHOLDER = "HAgk14JpMQLgt6rVgv7cBQFJWFto5Dqxi472uT3DKpqk";
|
|
613
750
|
var isSolana = (chain) => chain === LIFI_SOLANA_CAIP2;
|
|
614
|
-
var
|
|
751
|
+
var BASE582 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
615
752
|
var BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
616
753
|
var SAME_CHAIN_MAX_SECONDS = 90;
|
|
617
754
|
var MAX_INPUT_FEE_BPS = 300;
|
|
618
755
|
function sameChainAddress(chain, actual, expected) {
|
|
619
|
-
if (isSolana(chain)) return !!actual &&
|
|
756
|
+
if (isSolana(chain)) return !!actual && BASE582.test(actual) && actual === expected;
|
|
620
757
|
return sameAddress(actual, expected);
|
|
621
758
|
}
|
|
622
759
|
function nativeFor(chain, token) {
|
|
@@ -655,6 +792,10 @@ var LIFI_DIAMONDS = {
|
|
|
655
792
|
// Morph
|
|
656
793
|
"eip155:14": "0x198FC70Dfe05E755C81e54bd67Bff3F729344B9b",
|
|
657
794
|
// Flare (li.quest diamondAddress 2026-10-14; the canonical 0x1231… has no code there)
|
|
795
|
+
// Elysium: li.quest diamondAddress = deployments/elysium.json LiFiDiamond; code on 99901, DiamondLoupe facets() serves
|
|
796
|
+
// GenericSwapFacetV3 + LiFiIntentEscrowFacetV2; the canonical 0x1231… has no code there (all read 2026-11-10).
|
|
797
|
+
"eip155:99901": "0x24E6dA91AA4A8FEe2447F542c92E006243bd6Dc5",
|
|
798
|
+
// Elysium
|
|
658
799
|
"eip155:4326": "0x026F252016A7C47CDEf1F05a3Fc9E20C92a49C37"
|
|
659
800
|
// MegaETH (deployments/megaeth.json, matches a live quote 2026-10-12)
|
|
660
801
|
};
|
|
@@ -701,7 +842,16 @@ function headers(key) {
|
|
|
701
842
|
}
|
|
702
843
|
var TOOL_KEY = /^[A-Za-z0-9_-]{1,64}$/;
|
|
703
844
|
var UNBOUND_TOOLS = Object.freeze(["relaydepository", "garden", "squid", "symbiosis"]);
|
|
704
|
-
|
|
845
|
+
var UNPINNED_TOOLS = Object.freeze(["lifiIntents"]);
|
|
846
|
+
var REFUSED_EVM_TOOLS = Object.freeze([...UNBOUND_TOOLS, ...UNPINNED_TOOLS]);
|
|
847
|
+
var SOLANA_DESTINATION_TOOLS = Object.freeze(["mayan", "mayanMCTP", "mayanFastMCTP", "polymer", "polymerStandard", "chainflip"]);
|
|
848
|
+
var SMART_DEPOSITS_TOOL = /^smartdeposits?$/i;
|
|
849
|
+
var dryRecipient = (params, fromAddress) => !isSolana(params.sourceChain) && isSolana(params.destChain) ? SOLANA_PLACEHOLDER : fromAddress;
|
|
850
|
+
function placeholderIn(params) {
|
|
851
|
+
if (!params.refundAddress) return "sender";
|
|
852
|
+
return !params.destAddress && dryRecipient(params, params.refundAddress) !== params.refundAddress ? "recipient" : null;
|
|
853
|
+
}
|
|
854
|
+
function routeOptions(body, excluded, allowed) {
|
|
705
855
|
const routes = body?.routes;
|
|
706
856
|
if (!Array.isArray(routes)) return [];
|
|
707
857
|
const out = [];
|
|
@@ -711,7 +861,7 @@ function routeOptions(body, excluded) {
|
|
|
711
861
|
const cross = Array.isArray(step.includedSteps) ? step.includedSteps.find((i) => i?.type === "cross")?.tool : void 0;
|
|
712
862
|
const tool = typeof cross === "string" ? cross : step.tool;
|
|
713
863
|
const eta = step.estimate?.executionDuration;
|
|
714
|
-
if (typeof tool !== "string" || !TOOL_KEY.test(tool) || excluded.includes(tool) || typeof eta !== "number" || !Number.isFinite(eta) || eta < 0) continue;
|
|
864
|
+
if (typeof tool !== "string" || !TOOL_KEY.test(tool) || excluded.includes(tool) || allowed && !allowed.includes(tool) || typeof eta !== "number" || !Number.isFinite(eta) || eta < 0) continue;
|
|
715
865
|
if (typeof r.toAmount !== "string" || typeof r.toAmountMin !== "string" || !/^\d{1,60}$/.test(r.toAmount) || !/^\d{1,60}$/.test(r.toAmountMin)) continue;
|
|
716
866
|
out.push({
|
|
717
867
|
tool,
|
|
@@ -776,13 +926,15 @@ function lifiProvider(config = {}) {
|
|
|
776
926
|
if (!fromToken) return null;
|
|
777
927
|
const solanaSource = isSolana(params.sourceChain);
|
|
778
928
|
const crossChain = params.sourceChain !== params.destChain;
|
|
929
|
+
const evmToSolana = !solanaSource && isSolana(params.destChain);
|
|
779
930
|
const noDestinationCall = crossChain && !solanaSource;
|
|
780
931
|
let routeScope = config.routeWideSlippage === true && !solanaSource && !isSolana(params.destChain);
|
|
781
932
|
const dry = params.dry !== false;
|
|
782
933
|
const fromAddress = params.refundAddress || (dry ? solanaSource ? SOLANA_PLACEHOLDER : EVM_PLACEHOLDER : "");
|
|
783
|
-
const toAddress = params.destAddress || (dry ? fromAddress : "");
|
|
934
|
+
const toAddress = params.destAddress || (dry ? dryRecipient(params, fromAddress) : "");
|
|
784
935
|
if (!fromAddress) return reject("a live quote needs the sender's address");
|
|
785
936
|
if (!toAddress) return reject("a live quote needs a recipient");
|
|
937
|
+
const placeholder = dry ? placeholderIn(params) : null;
|
|
786
938
|
const slippageBps = params.slippageBps ?? defaultSlippageBps;
|
|
787
939
|
let sentSlippage = slippageBps / 1e4;
|
|
788
940
|
let deadline;
|
|
@@ -803,7 +955,7 @@ function lifiProvider(config = {}) {
|
|
|
803
955
|
options: {
|
|
804
956
|
slippage: slippageBps / 1e4,
|
|
805
957
|
...routeScope ? { slippageScope: "route" } : {},
|
|
806
|
-
...solanaSource ? {} : { bridges: { deny:
|
|
958
|
+
...solanaSource ? {} : { bridges: { ...evmToSolana ? { allow: SOLANA_DESTINATION_TOOLS } : {}, deny: REFUSED_EVM_TOOLS } },
|
|
807
959
|
...noDestinationCall ? { allowDestinationCall: false } : {},
|
|
808
960
|
...integrator().trim() ? { integrator: integrator().trim() } : {}
|
|
809
961
|
}
|
|
@@ -811,7 +963,7 @@ function lifiProvider(config = {}) {
|
|
|
811
963
|
signal: signal ?? (0, import_util3.timeoutSignal)(8e3)
|
|
812
964
|
});
|
|
813
965
|
if (res.ok) {
|
|
814
|
-
const options = routeOptions(await res.json(), solanaSource ? [] :
|
|
966
|
+
const options = routeOptions(await res.json(), solanaSource ? [] : REFUSED_EVM_TOOLS, evmToSolana ? SOLANA_DESTINATION_TOOLS : void 0);
|
|
815
967
|
if (options.length) choice = cleanChoice(config.chooseRoute(options, params));
|
|
816
968
|
}
|
|
817
969
|
} catch {
|
|
@@ -833,8 +985,10 @@ function lifiProvider(config = {}) {
|
|
|
833
985
|
}
|
|
834
986
|
if (routeScope) url.searchParams.set("slippageScope", "route");
|
|
835
987
|
if (noDestinationCall) url.searchParams.set("allowDestinationCall", "false");
|
|
836
|
-
const unbound = crossChain && !solanaSource ?
|
|
837
|
-
|
|
988
|
+
const unbound = crossChain && !solanaSource ? REFUSED_EVM_TOOLS : [];
|
|
989
|
+
const allow = (choice?.allowBridges ?? []).filter((b) => solanaSource || !REFUSED_EVM_TOOLS.includes(b));
|
|
990
|
+
const solanaAllow = evmToSolana ? allow.filter((b) => SOLANA_DESTINATION_TOOLS.includes(b)) : [];
|
|
991
|
+
for (const t of evmToSolana ? solanaAllow.length ? solanaAllow : SOLANA_DESTINATION_TOOLS : allow) url.searchParams.append("allowBridges", t);
|
|
838
992
|
for (const t of /* @__PURE__ */ new Set([...choice?.denyBridges ?? [], ...unbound])) url.searchParams.append("denyBridges", t);
|
|
839
993
|
if (choice?.order) url.searchParams.set("order", choice.order);
|
|
840
994
|
deadline = signal ? void 0 : (0, import_util3.timeoutSignal)(12e3);
|
|
@@ -858,6 +1012,13 @@ function lifiProvider(config = {}) {
|
|
|
858
1012
|
}
|
|
859
1013
|
const q = await res.json();
|
|
860
1014
|
const receivedAt = Date.now();
|
|
1015
|
+
if (evmToSolana) {
|
|
1016
|
+
const steps = q.includedSteps;
|
|
1017
|
+
const tools = [q.tool, ...Array.isArray(steps) ? steps.map((i) => i?.tool) : []];
|
|
1018
|
+
if (tools.some((t) => typeof t === "string" && SMART_DEPOSITS_TOOL.test(t))) {
|
|
1019
|
+
return reject("LI.FI Smart Deposits pay a deposit address whose recipient the transaction does not bind \u2014 refused for a Solana destination");
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
861
1022
|
const action = q.action;
|
|
862
1023
|
const est = q.estimate;
|
|
863
1024
|
const tr = q.transactionRequest;
|
|
@@ -898,6 +1059,7 @@ function lifiProvider(config = {}) {
|
|
|
898
1059
|
if (!trustedAddress(params.sourceChain, est.approvalAddress, trustedApprovalAddresses)) return reject("the approval target is not a trusted LI.FI contract");
|
|
899
1060
|
}
|
|
900
1061
|
let txRequest;
|
|
1062
|
+
let nativeFee = 0n;
|
|
901
1063
|
if (solanaSource) {
|
|
902
1064
|
txRequest = { serializedTx: tr.data, value: sourceIsToken ? "0" : params.amountIn };
|
|
903
1065
|
} else {
|
|
@@ -907,7 +1069,7 @@ function lifiProvider(config = {}) {
|
|
|
907
1069
|
return reject("the transaction value or gas limit is not a number");
|
|
908
1070
|
}
|
|
909
1071
|
const value = BigInt(tr.value);
|
|
910
|
-
|
|
1072
|
+
nativeFee = sourceIsToken ? value : value > BigInt(params.amountIn) ? value - BigInt(params.amountIn) : 0n;
|
|
911
1073
|
if (!sourceIsToken && value < BigInt(params.amountIn)) return reject("the transaction sends less native coin than the input");
|
|
912
1074
|
txRequest = {
|
|
913
1075
|
to: tr.to,
|
|
@@ -968,7 +1130,10 @@ function lifiProvider(config = {}) {
|
|
|
968
1130
|
intentId: `lifi:${fromChain}:${toChain}`,
|
|
969
1131
|
depositAddress: "",
|
|
970
1132
|
depositMemo: null,
|
|
971
|
-
|
|
1133
|
+
// Every check above ran on LI.FI's transaction; one built for a placeholder is not handed out (placeholderIn).
|
|
1134
|
+
// Its bridge fee on top of the input still is, as `providerData.nativeFee`, so the router's value nets it out
|
|
1135
|
+
// (usdValue reads it when there is no txRequest) instead of ranking the route on its gross output.
|
|
1136
|
+
...placeholder ? { providerData: { placeholder, ...nativeFee > 0n ? { nativeFee: nativeFee.toString() } : {} } } : { txRequest },
|
|
972
1137
|
// What the wallet spends (action.fromAmount, = the input); an input-side bridge fee is inside destAmount.
|
|
973
1138
|
sourceAmount: params.amountIn,
|
|
974
1139
|
destAmount: est.toAmount,
|
|
@@ -988,6 +1153,10 @@ function lifiProvider(config = {}) {
|
|
|
988
1153
|
return null;
|
|
989
1154
|
},
|
|
990
1155
|
async buildDeposit(quote) {
|
|
1156
|
+
const placeholder = quote.providerData?.placeholder;
|
|
1157
|
+
if (placeholder !== void 0) {
|
|
1158
|
+
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`);
|
|
1159
|
+
}
|
|
991
1160
|
const tx = quote.txRequest;
|
|
992
1161
|
if (!tx) throw new Error("swap2: LI.FI quote has no transaction request");
|
|
993
1162
|
const [, fromChain] = quote.intentId.split(":");
|
|
@@ -1039,6 +1208,11 @@ function lifiVerifyQuote(params, quote) {
|
|
|
1039
1208
|
const fromChain = LIFI_CHAIN[params.sourceChain];
|
|
1040
1209
|
const toChain = LIFI_CHAIN[params.destChain];
|
|
1041
1210
|
if (!fromChain || !toChain) return "not a LI.FI chain pair";
|
|
1211
|
+
const placeholder = params.dry !== false ? placeholderIn(params) : null;
|
|
1212
|
+
if (placeholder) {
|
|
1213
|
+
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`;
|
|
1214
|
+
return priceOnlyMismatch(params, quote, `lifi:${fromChain}:${toChain}`);
|
|
1215
|
+
}
|
|
1042
1216
|
const tx = quote.txRequest;
|
|
1043
1217
|
if (!tx) return "the quote carries no transaction";
|
|
1044
1218
|
if (isSolana(params.sourceChain)) {
|
|
@@ -1053,7 +1227,7 @@ function lifiVerifyQuote(params, quote) {
|
|
|
1053
1227
|
const sourceIsToken = fromToken !== EVM_ZERO;
|
|
1054
1228
|
const dry = params.dry !== false;
|
|
1055
1229
|
const fromAddress = params.refundAddress || (dry ? EVM_PLACEHOLDER : "");
|
|
1056
|
-
const toAddress = params.destAddress || (dry ? fromAddress : "");
|
|
1230
|
+
const toAddress = params.destAddress || (dry ? dryRecipient(params, fromAddress) : "");
|
|
1057
1231
|
if (!fromAddress || !toAddress) return "a live quote needs the sender and the recipient";
|
|
1058
1232
|
let amountIn, minOut, value, nativeFee;
|
|
1059
1233
|
try {
|
|
@@ -1087,6 +1261,19 @@ function lifiVerifyQuote(params, quote) {
|
|
|
1087
1261
|
});
|
|
1088
1262
|
return why ? `the transaction does not match the request: ${why}` : null;
|
|
1089
1263
|
}
|
|
1264
|
+
function priceOnlyMismatch(params, quote, route) {
|
|
1265
|
+
if (quote.depositAddress !== "") return `a LI.FI quote pays no deposit address, but this one names ${String(quote.depositAddress).slice(0, 64)}`;
|
|
1266
|
+
const fee = quote.providerData?.nativeFee;
|
|
1267
|
+
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`;
|
|
1268
|
+
try {
|
|
1269
|
+
if (BigInt(quote.sourceAmount) !== BigInt(params.amountIn)) return `the quote spends ${quote.sourceAmount}, not the ${params.amountIn} input`;
|
|
1270
|
+
const minOut = BigInt(quote.minDestAmount);
|
|
1271
|
+
if (params.minAmountOut !== void 0 && minOut < BigInt(params.minAmountOut)) return `the quoted minimum ${minOut} is below the caller's floor ${params.minAmountOut}`;
|
|
1272
|
+
} catch {
|
|
1273
|
+
return "the quote's amounts are not integers";
|
|
1274
|
+
}
|
|
1275
|
+
return quote.intentId.replace(/~[0-9a-f]{32}$/, "") === route ? null : "the intent id does not name this route";
|
|
1276
|
+
}
|
|
1090
1277
|
var lifiPins = Object.freeze({
|
|
1091
1278
|
callTargets: (chain) => /^eip155:[0-9]+$/.test(chain) ? [diamondFor(chain)] : [],
|
|
1092
1279
|
spenders: (chain) => /^eip155:[0-9]+$/.test(chain) ? [diamondFor(chain)] : [],
|