@anywalletkit/protocol-swap2-lifi 0.7.0 → 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-HJRWHGMX.js → chunk-GWUGK4SG.js} +212 -32
- package/dist/chunk-GWUGK4SG.js.map +1 -0
- package/dist/{chunk-D42KNYLU.js → chunk-OYGVC7DJ.js} +2 -2
- package/dist/index.cjs +211 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +2 -2
- package/dist/protocol.cjs +211 -31
- package/dist/protocol.cjs.map +1 -1
- package/dist/protocol.js +2 -2
- package/dist/provider.cjs +211 -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 +1 -1
- package/package.json +8 -6
- package/dist/chunk-HJRWHGMX.js.map +0 -1
- /package/dist/{chunk-D42KNYLU.js.map → chunk-OYGVC7DJ.js.map} +0 -0
package/dist/protocol.js
CHANGED
package/dist/provider.cjs
CHANGED
|
@@ -74,6 +74,70 @@ function describeLifiActivity(tx, ctx, isDiamond) {
|
|
|
74
74
|
});
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
// src/solana-address.ts
|
|
78
|
+
var import_ed25519 = require("@noble/curves/ed25519.js");
|
|
79
|
+
var import_sha2 = require("@noble/hashes/sha2.js");
|
|
80
|
+
var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
81
|
+
var BASE58 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
82
|
+
var SOLANA_TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
83
|
+
var ASSOCIATED_TOKEN_PROGRAM = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
|
|
84
|
+
var PDA_MARKER = Uint8Array.from("ProgramDerivedAddress", (c) => c.charCodeAt(0));
|
|
85
|
+
function base58(bytes) {
|
|
86
|
+
let n = 0n;
|
|
87
|
+
for (const b of bytes) n = n << 8n | BigInt(b);
|
|
88
|
+
let out = "";
|
|
89
|
+
while (n > 0n) {
|
|
90
|
+
out = ALPHABET[Number(n % 58n)] + out;
|
|
91
|
+
n /= 58n;
|
|
92
|
+
}
|
|
93
|
+
for (const b of bytes) {
|
|
94
|
+
if (b !== 0) break;
|
|
95
|
+
out = `1${out}`;
|
|
96
|
+
}
|
|
97
|
+
return out;
|
|
98
|
+
}
|
|
99
|
+
function solanaKey(address) {
|
|
100
|
+
if (!BASE58.test(address)) return null;
|
|
101
|
+
let n = 0n;
|
|
102
|
+
for (const ch of address) n = n * 58n + BigInt(ALPHABET.indexOf(ch));
|
|
103
|
+
let zeros = 0;
|
|
104
|
+
while (address[zeros] === "1") zeros++;
|
|
105
|
+
const body = [];
|
|
106
|
+
while (n > 0n) {
|
|
107
|
+
body.unshift(Number(n & 0xffn));
|
|
108
|
+
n >>= 8n;
|
|
109
|
+
}
|
|
110
|
+
if (zeros + body.length !== 32) return null;
|
|
111
|
+
const out = new Uint8Array(32);
|
|
112
|
+
out.set(body, zeros);
|
|
113
|
+
return out;
|
|
114
|
+
}
|
|
115
|
+
var onCurve = (bytes) => {
|
|
116
|
+
try {
|
|
117
|
+
import_ed25519.ed25519.Point.fromBytes(bytes);
|
|
118
|
+
return true;
|
|
119
|
+
} catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
function programAddress(seeds, program) {
|
|
124
|
+
for (let bump = 255; bump >= 0; bump--) {
|
|
125
|
+
const parts = [...seeds, Uint8Array.of(bump), program, PDA_MARKER];
|
|
126
|
+
const buf = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
|
|
127
|
+
let at = 0;
|
|
128
|
+
for (const p of parts) {
|
|
129
|
+
buf.set(p, at);
|
|
130
|
+
at += p.length;
|
|
131
|
+
}
|
|
132
|
+
const hash = (0, import_sha2.sha256)(buf);
|
|
133
|
+
if (!onCurve(hash)) return hash;
|
|
134
|
+
}
|
|
135
|
+
throw new Error("no program address for these seeds");
|
|
136
|
+
}
|
|
137
|
+
function associatedTokenAccount(owner, mint, tokenProgram = solanaKey(SOLANA_TOKEN_PROGRAM)) {
|
|
138
|
+
return programAddress([owner, tokenProgram, mint], solanaKey(ASSOCIATED_TOKEN_PROGRAM));
|
|
139
|
+
}
|
|
140
|
+
|
|
77
141
|
// src/calldata.ts
|
|
78
142
|
var GENERIC_SWAPS = {
|
|
79
143
|
"0x4630a0d8": { single: false },
|
|
@@ -384,8 +448,9 @@ var FACET_DATA = {
|
|
|
384
448
|
Frax: { inline: 5, refunds: [["refundRecipient", 3]], zeroRefused: true },
|
|
385
449
|
Glacis: { inline: 4, refunds: [["refundAddress", 1]], zeroRefused: true },
|
|
386
450
|
LayerSwap: { refunds: [["refundRecipient", 2]], zeroRefused: true },
|
|
451
|
+
// The escrow order's user. Not reached while LI.FI Intents is refused on every EVM-source route (INTENTS_UNPINNED); kept for when
|
|
452
|
+
// its oracles and settler are pinned.
|
|
387
453
|
LiFiIntentEscrowV2: { refunds: [["depositAndRefundAddress", 2]], zeroRefused: true },
|
|
388
|
-
// the escrow order's user
|
|
389
454
|
M0: { inline: 7, refunds: [["refundRecipient", 1], ["orderOwner", 2]], zeroRefused: true, delivery: m0Delivery },
|
|
390
455
|
// orderOwner gets a cancelled order back
|
|
391
456
|
Mayan: { refunds: [["refundRecipient", 7]], nested: mayanRefunds, zeroRefused: true },
|
|
@@ -413,6 +478,86 @@ function refundMismatch(r, bridge, e) {
|
|
|
413
478
|
}
|
|
414
479
|
return null;
|
|
415
480
|
}
|
|
481
|
+
var NON_EVM_UNREAD = "the recipient on a non-EVM destination sits in bridge-specific data and cannot be checked here";
|
|
482
|
+
var LIFI_SOLANA_CHAIN_ID = 1151111081099710n;
|
|
483
|
+
var NON_EVM_ADDRESS = "0x11f111f111f111f111f111f111f111f111f111f1";
|
|
484
|
+
var SOLANA_USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
485
|
+
var WORMHOLE_SOLANA = 1n;
|
|
486
|
+
var CCTP_DOMAIN_SOLANA = 5n;
|
|
487
|
+
var CHAINFLIP_SOLANA_ASSETS = {
|
|
488
|
+
"11111111111111111111111111111111": 9n,
|
|
489
|
+
[SOLANA_USDC]: 10n,
|
|
490
|
+
Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB: 16n
|
|
491
|
+
};
|
|
492
|
+
var DEPOSIT_FACETS = /* @__PURE__ */ new Set(["NEARIntents", "LayerSwap", "Unit"]);
|
|
493
|
+
var MAYAN_SOLANA = {
|
|
494
|
+
// Swift v2 createOrderWithToken(tokenIn, amountIn, (payloadType, trader, destAddr, destChainId, referrerAddr, tokenOut, minAmountOut, …), customPayload)
|
|
495
|
+
"0xa3a30834": { destAddr: 4, chain: 5, cctp: false, payloadType: [2, 1n], tokenOut: 7 },
|
|
496
|
+
// MCTP createOrder((tokenIn, amountIn, gasDrop, destAddr, destChain, tokenOut, minAmountOut, deadline, redeemFee, referrerAddr, referrerBps))
|
|
497
|
+
"0x1c59b7fc": { destAddr: 3, chain: 4, cctp: false, tokenOut: 5 },
|
|
498
|
+
// MCTP bridgeWithFee(tokenIn, amountIn, redeemFee, gasDrop, destAddr, destDomain, payloadType, customPayload): delivers USDC
|
|
499
|
+
"0x2072197f": { destAddr: 4, chain: 5, cctp: true, payloadType: [6, 1n] },
|
|
500
|
+
// Fast MCTP bridge(tokenIn, amountIn, redeemFee, circleMaxFee, gasDrop, destAddr, destDomain, referrerAddress, referrerBps, payloadType, minFinalityThreshold, customPayload): delivers USDC
|
|
501
|
+
"0xf58b6de8": { destAddr: 5, chain: 6, cctp: true, payloadType: [9, 1n] },
|
|
502
|
+
// Fast MCTP createOrder(tokenIn, amountIn, circleMaxFee, destDomain, minFinalityThreshold, (payloadType 3, destAddr, tokenOut, amountOutMin, …))
|
|
503
|
+
"0x2337e236": { destAddr: 6, chain: 3, cctp: true, payloadType: [5, 3n], tokenOut: 7 }
|
|
504
|
+
};
|
|
505
|
+
var hexOf = (b) => `0x${Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("")}`;
|
|
506
|
+
var keyOf = (word) => base58(Uint8Array.from(word.slice(2).match(/../g) ?? [], (h) => Number.parseInt(h, 16)));
|
|
507
|
+
function mayanToSolana(r, at, t) {
|
|
508
|
+
const declared = r.raw(at, 32);
|
|
509
|
+
if (declared !== t.ownerHex) return `Mayan's nonEVMReceiver is ${keyOf(declared)}, not the recipient ${t.e.receiver}`;
|
|
510
|
+
const p = r.bytes(at, at + 32 * 2);
|
|
511
|
+
const selector = p.raw(0, 4);
|
|
512
|
+
const o = MAYAN_SOLANA[selector];
|
|
513
|
+
if (!o) return `the Mayan call ${selector} is an entry point whose Solana recipient cannot be checked here`;
|
|
514
|
+
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`;
|
|
516
|
+
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}`;
|
|
518
|
+
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}`;
|
|
521
|
+
const token = p.raw(word(o.tokenOut), 32);
|
|
522
|
+
return token === t.mintHex ? null : `the Mayan order delivers ${keyOf(token)}, not ${t.e.toToken}`;
|
|
523
|
+
}
|
|
524
|
+
function polymerToSolana(r, at, t) {
|
|
525
|
+
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}`;
|
|
528
|
+
const mintTo = r.raw(at + 32 * 3, 32);
|
|
529
|
+
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)}`;
|
|
531
|
+
}
|
|
532
|
+
function chainflipToSolana(r, at, t) {
|
|
533
|
+
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}`;
|
|
536
|
+
const asset = r.word(at + 32);
|
|
537
|
+
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}`;
|
|
539
|
+
}
|
|
540
|
+
var SOLANA_DELIVERY = {
|
|
541
|
+
Mayan: mayanToSolana,
|
|
542
|
+
PolymerCCTP: polymerToSolana,
|
|
543
|
+
Chainflip: chainflipToSolana
|
|
544
|
+
};
|
|
545
|
+
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";
|
|
546
|
+
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
|
+
var INTENTS_FACET = "LiFiIntentEscrowV2";
|
|
548
|
+
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;
|
|
553
|
+
const read = SOLANA_DELIVERY[bridge.facet];
|
|
554
|
+
if (!read) return NON_EVM_UNREAD;
|
|
555
|
+
const owner = solanaKey(e.receiver);
|
|
556
|
+
if (!owner) return `the recipient ${e.receiver} is not a Solana address`;
|
|
557
|
+
const mint = solanaKey(e.toToken);
|
|
558
|
+
if (!mint) return `the destination token ${e.toToken} is not a Solana mint`;
|
|
559
|
+
return read(r, facetDataAt(r, bridge, FACET_DATA[bridge.facet]), { owner, ownerHex: hexOf(owner), mint, mintHex: hexOf(mint), e });
|
|
560
|
+
}
|
|
416
561
|
function diamondCallMismatch(data, e) {
|
|
417
562
|
let r;
|
|
418
563
|
try {
|
|
@@ -441,6 +586,7 @@ function diamondCallMismatch(data, e) {
|
|
|
441
586
|
if (unbound) return `${unbound} delivers to a recipient the transaction does not bind (an off-chain order or unchecked facet data)`;
|
|
442
587
|
const bridge = BRIDGE_CALLS[selector];
|
|
443
588
|
if (!bridge) return `unknown Diamond call ${selector}`;
|
|
589
|
+
if (bridge.facet === INTENTS_FACET && e.evmDestination) return INTENTS_UNPINNED;
|
|
444
590
|
const head = r.small(4);
|
|
445
591
|
if (head < (bridge.sourceSwaps ? 96 : 64) || head % 32 !== 0 || head > 32 * 32) return `unknown Diamond call ${selector}`;
|
|
446
592
|
const at = 4 + head;
|
|
@@ -454,8 +600,10 @@ function diamondCallMismatch(data, e) {
|
|
|
454
600
|
if (e.sameChain) return "a bridge call on a same-chain route";
|
|
455
601
|
if (destinationChainId !== e.destChainId) return `bridges to chain ${destinationChainId}, not ${e.destChainId}`;
|
|
456
602
|
if (hasDestinationCall) return "the route runs a call on the destination chain, whose recipient cannot be checked here";
|
|
457
|
-
if (!e.evmDestination)
|
|
458
|
-
|
|
603
|
+
if (!e.evmDestination) {
|
|
604
|
+
const why = solanaRecipientMismatch(r, bridge, receiver, e);
|
|
605
|
+
if (why) return why;
|
|
606
|
+
} else if (!same(receiver, e.receiver)) return `pays ${receiver}, not ${e.receiver}`;
|
|
459
607
|
if (hasSourceSwaps !== bridge.sourceSwaps) return `unknown Diamond call ${selector}`;
|
|
460
608
|
const refund = refundMismatch(r, bridge, e);
|
|
461
609
|
if (refund) return refund;
|
|
@@ -475,7 +623,6 @@ function diamondCallMismatch(data, e) {
|
|
|
475
623
|
|
|
476
624
|
// src/solana.ts
|
|
477
625
|
var import_util2 = require("@anywalletkit/protocol-swap2-core/util");
|
|
478
|
-
var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
479
626
|
var MAYAN_ENTRY_PROGRAMS = /* @__PURE__ */ new Set([
|
|
480
627
|
"D8C8iW6zmoKg5TRr8nQ7h14TMWqQX8FiBdj2ju5MF3wa",
|
|
481
628
|
// CPI_PROXY_PROGRAM_ID
|
|
@@ -490,20 +637,6 @@ var MAYAN_ENTRY_PROGRAMS = /* @__PURE__ */ new Set([
|
|
|
490
637
|
]);
|
|
491
638
|
var SYSTEM_PROGRAM = "11111111111111111111111111111111";
|
|
492
639
|
var LIFI_SOLANA_FEE_WALLET = "34FKjAdVcTax2DHqV2XnbXa9J3zmyKcFuFKWbcmgxjgm";
|
|
493
|
-
function base58(bytes) {
|
|
494
|
-
let n = 0n;
|
|
495
|
-
for (const b of bytes) n = n << 8n | BigInt(b);
|
|
496
|
-
let out = "";
|
|
497
|
-
while (n > 0n) {
|
|
498
|
-
out = ALPHABET[Number(n % 58n)] + out;
|
|
499
|
-
n /= 58n;
|
|
500
|
-
}
|
|
501
|
-
for (const b of bytes) {
|
|
502
|
-
if (b !== 0) break;
|
|
503
|
-
out = `1${out}`;
|
|
504
|
-
}
|
|
505
|
-
return out;
|
|
506
|
-
}
|
|
507
640
|
var Cursor = class {
|
|
508
641
|
constructor(b) {
|
|
509
642
|
this.b = b;
|
|
@@ -605,7 +738,8 @@ var LIFI_CHAIN = {
|
|
|
605
738
|
"eip155:2818": "2818",
|
|
606
739
|
"eip155:14": "14",
|
|
607
740
|
// 2026-11-10: Elysium (Kinetiq; li.quest/v1/chains id 99901, key "ely"). LI.FI's bridges there are Relay (refused
|
|
608
|
-
// below: UNBOUND_TOOLS) and LI.FI Intents
|
|
741
|
+
// below: UNBOUND_TOOLS) and LI.FI Intents (refused too: UNPINNED_TOOLS), so it has no cross-chain LI.FI route for now;
|
|
742
|
+
// same-chain swaps go through GenericSwapFacetV3 (KyberSwap, Nordstern).
|
|
609
743
|
"eip155:99901": "99901",
|
|
610
744
|
[LIFI_SOLANA_CAIP2]: "1151111081099710"
|
|
611
745
|
};
|
|
@@ -615,12 +749,12 @@ var SOLANA_NATIVE = "11111111111111111111111111111111";
|
|
|
615
749
|
var EVM_PLACEHOLDER = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
|
|
616
750
|
var SOLANA_PLACEHOLDER = "HAgk14JpMQLgt6rVgv7cBQFJWFto5Dqxi472uT3DKpqk";
|
|
617
751
|
var isSolana = (chain) => chain === LIFI_SOLANA_CAIP2;
|
|
618
|
-
var
|
|
752
|
+
var BASE582 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
619
753
|
var BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
620
754
|
var SAME_CHAIN_MAX_SECONDS = 90;
|
|
621
755
|
var MAX_INPUT_FEE_BPS = 300;
|
|
622
756
|
function sameChainAddress(chain, actual, expected) {
|
|
623
|
-
if (isSolana(chain)) return !!actual &&
|
|
757
|
+
if (isSolana(chain)) return !!actual && BASE582.test(actual) && actual === expected;
|
|
624
758
|
return sameAddress(actual, expected);
|
|
625
759
|
}
|
|
626
760
|
function nativeFor(chain, token) {
|
|
@@ -709,7 +843,16 @@ function headers(key) {
|
|
|
709
843
|
}
|
|
710
844
|
var TOOL_KEY = /^[A-Za-z0-9_-]{1,64}$/;
|
|
711
845
|
var UNBOUND_TOOLS = Object.freeze(["relaydepository", "garden", "squid", "symbiosis"]);
|
|
712
|
-
|
|
846
|
+
var UNPINNED_TOOLS = Object.freeze(["lifiIntents"]);
|
|
847
|
+
var REFUSED_EVM_TOOLS = Object.freeze([...UNBOUND_TOOLS, ...UNPINNED_TOOLS]);
|
|
848
|
+
var SOLANA_DESTINATION_TOOLS = Object.freeze(["mayan", "mayanMCTP", "mayanFastMCTP", "polymer", "polymerStandard", "chainflip"]);
|
|
849
|
+
var SMART_DEPOSITS_TOOL = /^smartdeposits?$/i;
|
|
850
|
+
var dryRecipient = (params, fromAddress) => !isSolana(params.sourceChain) && isSolana(params.destChain) ? SOLANA_PLACEHOLDER : fromAddress;
|
|
851
|
+
function placeholderIn(params) {
|
|
852
|
+
if (!params.refundAddress) return "sender";
|
|
853
|
+
return !params.destAddress && dryRecipient(params, params.refundAddress) !== params.refundAddress ? "recipient" : null;
|
|
854
|
+
}
|
|
855
|
+
function routeOptions(body, excluded, allowed) {
|
|
713
856
|
const routes = body?.routes;
|
|
714
857
|
if (!Array.isArray(routes)) return [];
|
|
715
858
|
const out = [];
|
|
@@ -719,7 +862,7 @@ function routeOptions(body, excluded) {
|
|
|
719
862
|
const cross = Array.isArray(step.includedSteps) ? step.includedSteps.find((i) => i?.type === "cross")?.tool : void 0;
|
|
720
863
|
const tool = typeof cross === "string" ? cross : step.tool;
|
|
721
864
|
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;
|
|
865
|
+
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
866
|
if (typeof r.toAmount !== "string" || typeof r.toAmountMin !== "string" || !/^\d{1,60}$/.test(r.toAmount) || !/^\d{1,60}$/.test(r.toAmountMin)) continue;
|
|
724
867
|
out.push({
|
|
725
868
|
tool,
|
|
@@ -784,13 +927,15 @@ function lifiProvider(config = {}) {
|
|
|
784
927
|
if (!fromToken) return null;
|
|
785
928
|
const solanaSource = isSolana(params.sourceChain);
|
|
786
929
|
const crossChain = params.sourceChain !== params.destChain;
|
|
930
|
+
const evmToSolana = !solanaSource && isSolana(params.destChain);
|
|
787
931
|
const noDestinationCall = crossChain && !solanaSource;
|
|
788
932
|
let routeScope = config.routeWideSlippage === true && !solanaSource && !isSolana(params.destChain);
|
|
789
933
|
const dry = params.dry !== false;
|
|
790
934
|
const fromAddress = params.refundAddress || (dry ? solanaSource ? SOLANA_PLACEHOLDER : EVM_PLACEHOLDER : "");
|
|
791
|
-
const toAddress = params.destAddress || (dry ? fromAddress : "");
|
|
935
|
+
const toAddress = params.destAddress || (dry ? dryRecipient(params, fromAddress) : "");
|
|
792
936
|
if (!fromAddress) return reject("a live quote needs the sender's address");
|
|
793
937
|
if (!toAddress) return reject("a live quote needs a recipient");
|
|
938
|
+
const placeholder = dry ? placeholderIn(params) : null;
|
|
794
939
|
const slippageBps = params.slippageBps ?? defaultSlippageBps;
|
|
795
940
|
let sentSlippage = slippageBps / 1e4;
|
|
796
941
|
let deadline;
|
|
@@ -811,7 +956,7 @@ function lifiProvider(config = {}) {
|
|
|
811
956
|
options: {
|
|
812
957
|
slippage: slippageBps / 1e4,
|
|
813
958
|
...routeScope ? { slippageScope: "route" } : {},
|
|
814
|
-
...solanaSource ? {} : { bridges: { deny:
|
|
959
|
+
...solanaSource ? {} : { bridges: { ...evmToSolana ? { allow: SOLANA_DESTINATION_TOOLS } : {}, deny: REFUSED_EVM_TOOLS } },
|
|
815
960
|
...noDestinationCall ? { allowDestinationCall: false } : {},
|
|
816
961
|
...integrator().trim() ? { integrator: integrator().trim() } : {}
|
|
817
962
|
}
|
|
@@ -819,7 +964,7 @@ function lifiProvider(config = {}) {
|
|
|
819
964
|
signal: signal ?? (0, import_util3.timeoutSignal)(8e3)
|
|
820
965
|
});
|
|
821
966
|
if (res.ok) {
|
|
822
|
-
const options = routeOptions(await res.json(), solanaSource ? [] :
|
|
967
|
+
const options = routeOptions(await res.json(), solanaSource ? [] : REFUSED_EVM_TOOLS, evmToSolana ? SOLANA_DESTINATION_TOOLS : void 0);
|
|
823
968
|
if (options.length) choice = cleanChoice(config.chooseRoute(options, params));
|
|
824
969
|
}
|
|
825
970
|
} catch {
|
|
@@ -841,8 +986,10 @@ function lifiProvider(config = {}) {
|
|
|
841
986
|
}
|
|
842
987
|
if (routeScope) url.searchParams.set("slippageScope", "route");
|
|
843
988
|
if (noDestinationCall) url.searchParams.set("allowDestinationCall", "false");
|
|
844
|
-
const unbound = crossChain && !solanaSource ?
|
|
845
|
-
|
|
989
|
+
const unbound = crossChain && !solanaSource ? REFUSED_EVM_TOOLS : [];
|
|
990
|
+
const allow = (choice?.allowBridges ?? []).filter((b) => solanaSource || !REFUSED_EVM_TOOLS.includes(b));
|
|
991
|
+
const solanaAllow = evmToSolana ? allow.filter((b) => SOLANA_DESTINATION_TOOLS.includes(b)) : [];
|
|
992
|
+
for (const t of evmToSolana ? solanaAllow.length ? solanaAllow : SOLANA_DESTINATION_TOOLS : allow) url.searchParams.append("allowBridges", t);
|
|
846
993
|
for (const t of /* @__PURE__ */ new Set([...choice?.denyBridges ?? [], ...unbound])) url.searchParams.append("denyBridges", t);
|
|
847
994
|
if (choice?.order) url.searchParams.set("order", choice.order);
|
|
848
995
|
deadline = signal ? void 0 : (0, import_util3.timeoutSignal)(12e3);
|
|
@@ -866,6 +1013,13 @@ function lifiProvider(config = {}) {
|
|
|
866
1013
|
}
|
|
867
1014
|
const q = await res.json();
|
|
868
1015
|
const receivedAt = Date.now();
|
|
1016
|
+
if (evmToSolana) {
|
|
1017
|
+
const steps = q.includedSteps;
|
|
1018
|
+
const tools = [q.tool, ...Array.isArray(steps) ? steps.map((i) => i?.tool) : []];
|
|
1019
|
+
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");
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
869
1023
|
const action = q.action;
|
|
870
1024
|
const est = q.estimate;
|
|
871
1025
|
const tr = q.transactionRequest;
|
|
@@ -906,6 +1060,7 @@ function lifiProvider(config = {}) {
|
|
|
906
1060
|
if (!trustedAddress(params.sourceChain, est.approvalAddress, trustedApprovalAddresses)) return reject("the approval target is not a trusted LI.FI contract");
|
|
907
1061
|
}
|
|
908
1062
|
let txRequest;
|
|
1063
|
+
let nativeFee = 0n;
|
|
909
1064
|
if (solanaSource) {
|
|
910
1065
|
txRequest = { serializedTx: tr.data, value: sourceIsToken ? "0" : params.amountIn };
|
|
911
1066
|
} else {
|
|
@@ -915,7 +1070,7 @@ function lifiProvider(config = {}) {
|
|
|
915
1070
|
return reject("the transaction value or gas limit is not a number");
|
|
916
1071
|
}
|
|
917
1072
|
const value = BigInt(tr.value);
|
|
918
|
-
|
|
1073
|
+
nativeFee = sourceIsToken ? value : value > BigInt(params.amountIn) ? value - BigInt(params.amountIn) : 0n;
|
|
919
1074
|
if (!sourceIsToken && value < BigInt(params.amountIn)) return reject("the transaction sends less native coin than the input");
|
|
920
1075
|
txRequest = {
|
|
921
1076
|
to: tr.to,
|
|
@@ -976,7 +1131,10 @@ function lifiProvider(config = {}) {
|
|
|
976
1131
|
intentId: `lifi:${fromChain}:${toChain}`,
|
|
977
1132
|
depositAddress: "",
|
|
978
1133
|
depositMemo: null,
|
|
979
|
-
|
|
1134
|
+
// Every check above ran on LI.FI's transaction; one built for a placeholder is not handed out (placeholderIn).
|
|
1135
|
+
// Its bridge fee on top of the input still is, as `providerData.nativeFee`, so the router's value nets it out
|
|
1136
|
+
// (usdValue reads it when there is no txRequest) instead of ranking the route on its gross output.
|
|
1137
|
+
...placeholder ? { providerData: { placeholder, ...nativeFee > 0n ? { nativeFee: nativeFee.toString() } : {} } } : { txRequest },
|
|
980
1138
|
// What the wallet spends (action.fromAmount, = the input); an input-side bridge fee is inside destAmount.
|
|
981
1139
|
sourceAmount: params.amountIn,
|
|
982
1140
|
destAmount: est.toAmount,
|
|
@@ -996,6 +1154,10 @@ function lifiProvider(config = {}) {
|
|
|
996
1154
|
return null;
|
|
997
1155
|
},
|
|
998
1156
|
async buildDeposit(quote) {
|
|
1157
|
+
const placeholder = quote.providerData?.placeholder;
|
|
1158
|
+
if (placeholder !== void 0) {
|
|
1159
|
+
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`);
|
|
1160
|
+
}
|
|
999
1161
|
const tx = quote.txRequest;
|
|
1000
1162
|
if (!tx) throw new Error("swap2: LI.FI quote has no transaction request");
|
|
1001
1163
|
const [, fromChain] = quote.intentId.split(":");
|
|
@@ -1047,6 +1209,11 @@ function lifiVerifyQuote(params, quote) {
|
|
|
1047
1209
|
const fromChain = LIFI_CHAIN[params.sourceChain];
|
|
1048
1210
|
const toChain = LIFI_CHAIN[params.destChain];
|
|
1049
1211
|
if (!fromChain || !toChain) return "not a LI.FI chain pair";
|
|
1212
|
+
const placeholder = params.dry !== false ? placeholderIn(params) : null;
|
|
1213
|
+
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`;
|
|
1215
|
+
return priceOnlyMismatch(params, quote, `lifi:${fromChain}:${toChain}`);
|
|
1216
|
+
}
|
|
1050
1217
|
const tx = quote.txRequest;
|
|
1051
1218
|
if (!tx) return "the quote carries no transaction";
|
|
1052
1219
|
if (isSolana(params.sourceChain)) {
|
|
@@ -1061,7 +1228,7 @@ function lifiVerifyQuote(params, quote) {
|
|
|
1061
1228
|
const sourceIsToken = fromToken !== EVM_ZERO;
|
|
1062
1229
|
const dry = params.dry !== false;
|
|
1063
1230
|
const fromAddress = params.refundAddress || (dry ? EVM_PLACEHOLDER : "");
|
|
1064
|
-
const toAddress = params.destAddress || (dry ? fromAddress : "");
|
|
1231
|
+
const toAddress = params.destAddress || (dry ? dryRecipient(params, fromAddress) : "");
|
|
1065
1232
|
if (!fromAddress || !toAddress) return "a live quote needs the sender and the recipient";
|
|
1066
1233
|
let amountIn, minOut, value, nativeFee;
|
|
1067
1234
|
try {
|
|
@@ -1095,6 +1262,19 @@ function lifiVerifyQuote(params, quote) {
|
|
|
1095
1262
|
});
|
|
1096
1263
|
return why ? `the transaction does not match the request: ${why}` : null;
|
|
1097
1264
|
}
|
|
1265
|
+
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)}`;
|
|
1267
|
+
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`;
|
|
1269
|
+
try {
|
|
1270
|
+
if (BigInt(quote.sourceAmount) !== BigInt(params.amountIn)) return `the quote spends ${quote.sourceAmount}, not the ${params.amountIn} input`;
|
|
1271
|
+
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}`;
|
|
1273
|
+
} catch {
|
|
1274
|
+
return "the quote's amounts are not integers";
|
|
1275
|
+
}
|
|
1276
|
+
return quote.intentId.replace(/~[0-9a-f]{32}$/, "") === route ? null : "the intent id does not name this route";
|
|
1277
|
+
}
|
|
1098
1278
|
var lifiPins = Object.freeze({
|
|
1099
1279
|
callTargets: (chain) => /^eip155:[0-9]+$/.test(chain) ? [diamondFor(chain)] : [],
|
|
1100
1280
|
spenders: (chain) => /^eip155:[0-9]+$/.test(chain) ? [diamondFor(chain)] : [],
|