@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/README.md
CHANGED
|
@@ -27,11 +27,47 @@ calls are refused) and its `BridgeData` must name the reviewed recipient and cha
|
|
|
27
27
|
input token (or what its source swaps make of it). Every refund, depositor or fallback address a
|
|
28
28
|
bridge carries in its own data (Stargate's LayerZero fee refund, an expired Across deposit or LI.FI
|
|
29
29
|
intent, a Mayan Swift order's trader, Chainflip's refund address, …) must be the sender or the
|
|
30
|
-
recipient. Refused, because the recipient cannot be read from the calldata: routes with a destination call
|
|
30
|
+
recipient. Refused, because the recipient cannot be read from the calldata: routes with a destination call and the
|
|
31
31
|
bridges whose facet does not bind the delivery to `BridgeData` (Relay, Garden, deBridge DLN,
|
|
32
32
|
ThorSwap, Squid, Symbiosis) — cross-chain requests ask LI.FI to route around Relay, Garden, Squid
|
|
33
33
|
and Symbiosis, and not to offer destination calls (`allowDestinationCall=false`), so their pairs get
|
|
34
|
-
another bridge.
|
|
34
|
+
another bridge. **LI.FI Intents** (`lifiIntents`) is refused on every route from an EVM chain and denied in every
|
|
35
|
+
cross-chain request from an EVM chain (Solana-source requests are unchanged): its escrowed order names its own input oracle, output oracle, output settler and
|
|
36
|
+
output amount multiplier, none of which is pinned here, so a matching recipient would not bind the delivery (the
|
|
37
|
+
escrow releases the input on the oracle's word). LI.FI serves those pairs through another bridge; where LI.FI Intents
|
|
38
|
+
is its only one (Elysium, 2026-11-13) LI.FI has no route. Unknown calldata is refused.
|
|
39
|
+
|
|
40
|
+
**EVM → Solana.** `BridgeData` names LI.FI's Solana chain id and, as its receiver, only LI.FI's non-EVM
|
|
41
|
+
placeholder (`0x11f1…11F1`, required exactly); the recipient sits in the bridge's own data and is read there for
|
|
42
|
+
the bridges whose delivery the transaction binds, then held to the requested owner (`destAddress`) and token:
|
|
43
|
+
|
|
44
|
+
- **Mayan** (`mayan`, `mayanMCTP`, `mayanFastMCTP`): the facet's `nonEVMReceiver` and the destination address in
|
|
45
|
+
the Mayan order or transfer it forwards (Swift v2, MCTP `createOrder` / `bridgeWithFee`, Fast MCTP `createOrder`
|
|
46
|
+
/ `bridge`) must both be the owner; the order must go to Solana (Wormhole chain 1 / CCTP domain 5), carry no
|
|
47
|
+
custom payload and name the requested mint (32 zero bytes for SOL); a plain CCTP transfer delivers USDC. Other
|
|
48
|
+
Mayan entry points are refused.
|
|
49
|
+
- **Polymer CCTP** (`polymer`, `polymerStandard`): CCTP mints to the token account the calldata names, which the
|
|
50
|
+
facet cannot check; it must be the owner's USDC associated token account, derived on the device.
|
|
51
|
+
- **Chainflip**: the vault swap's 32-byte destination address must be the owner and its asset the requested one
|
|
52
|
+
(SOL, USDC or USDT on Solana).
|
|
53
|
+
|
|
54
|
+
These requests ask LI.FI for those bridges only (`allowBridges`; a `chooseRoute` pick is kept among them). NEAR
|
|
55
|
+
Intents and LayerSwap pay a deposit address whose off-chain order names the recipient, and Smart Deposits a
|
|
56
|
+
deposit address outright: deposits the device cannot verify, refused for this direction. LI.FI Intents to
|
|
57
|
+
Solana is refused like everywhere else (see above). Any other bridge to Solana is refused as before. Solana-source
|
|
58
|
+
requests are unchanged.
|
|
59
|
+
|
|
60
|
+
**Dry quotes without an address.** A dry quote (`dry` not `false`) without `refundAddress` is priced for a
|
|
61
|
+
placeholder sender (vitalik.eth on EVM, the public BIP-39 "abandon" account on Solana), and a dry EVM → Solana quote
|
|
62
|
+
without `destAddress` for the "abandon" account as the recipient. Such a quote prices the route only: it carries no
|
|
63
|
+
`txRequest` (its `providerData.placeholder` names the stand-in), `buildDeposit` refuses it, and `lifiVerifyQuote` /
|
|
64
|
+
`lifiPins` refuse a quote for such a request that carries a transaction — LI.FI's transaction for a placeholder would
|
|
65
|
+
pay or refund that public account from whichever wallet signs it. Quote again with both addresses to build. A dry
|
|
66
|
+
EVM → EVM quote with a `refundAddress` but no recipient prices the sender's own address and keeps its transaction.
|
|
67
|
+
A bridge's native fee on top of the input (LayerZero's on Stargate, for instance) stays on the price-only quote as
|
|
68
|
+
`providerData.nativeFee` (base units of the source chain's native coin), which the router's `usdValue` and
|
|
69
|
+
`quoteValues` net out of its value as they do `txRequest.nativeFee`; `lifiVerifyQuote` refuses one that is not a
|
|
70
|
+
base-unit integer.
|
|
35
71
|
|
|
36
72
|
Every swap step is read, not only the ends: each must spend what the step before delivered, only
|
|
37
73
|
steps that spend the input token may pull from the wallet (`requiresDeposit`), and together at most
|
|
@@ -6,6 +6,70 @@ import {
|
|
|
6
6
|
describeLifiActivity
|
|
7
7
|
} from "./chunk-EU7XQVZQ.js";
|
|
8
8
|
|
|
9
|
+
// src/solana-address.ts
|
|
10
|
+
import { ed25519 } from "@noble/curves/ed25519.js";
|
|
11
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
12
|
+
var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
13
|
+
var BASE58 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
14
|
+
var SOLANA_TOKEN_PROGRAM = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
|
|
15
|
+
var ASSOCIATED_TOKEN_PROGRAM = "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL";
|
|
16
|
+
var PDA_MARKER = Uint8Array.from("ProgramDerivedAddress", (c) => c.charCodeAt(0));
|
|
17
|
+
function base58(bytes) {
|
|
18
|
+
let n = 0n;
|
|
19
|
+
for (const b of bytes) n = n << 8n | BigInt(b);
|
|
20
|
+
let out = "";
|
|
21
|
+
while (n > 0n) {
|
|
22
|
+
out = ALPHABET[Number(n % 58n)] + out;
|
|
23
|
+
n /= 58n;
|
|
24
|
+
}
|
|
25
|
+
for (const b of bytes) {
|
|
26
|
+
if (b !== 0) break;
|
|
27
|
+
out = `1${out}`;
|
|
28
|
+
}
|
|
29
|
+
return out;
|
|
30
|
+
}
|
|
31
|
+
function solanaKey(address) {
|
|
32
|
+
if (!BASE58.test(address)) return null;
|
|
33
|
+
let n = 0n;
|
|
34
|
+
for (const ch of address) n = n * 58n + BigInt(ALPHABET.indexOf(ch));
|
|
35
|
+
let zeros = 0;
|
|
36
|
+
while (address[zeros] === "1") zeros++;
|
|
37
|
+
const body = [];
|
|
38
|
+
while (n > 0n) {
|
|
39
|
+
body.unshift(Number(n & 0xffn));
|
|
40
|
+
n >>= 8n;
|
|
41
|
+
}
|
|
42
|
+
if (zeros + body.length !== 32) return null;
|
|
43
|
+
const out = new Uint8Array(32);
|
|
44
|
+
out.set(body, zeros);
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
var onCurve = (bytes) => {
|
|
48
|
+
try {
|
|
49
|
+
ed25519.Point.fromBytes(bytes);
|
|
50
|
+
return true;
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
function programAddress(seeds, program) {
|
|
56
|
+
for (let bump = 255; bump >= 0; bump--) {
|
|
57
|
+
const parts = [...seeds, Uint8Array.of(bump), program, PDA_MARKER];
|
|
58
|
+
const buf = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
|
|
59
|
+
let at = 0;
|
|
60
|
+
for (const p of parts) {
|
|
61
|
+
buf.set(p, at);
|
|
62
|
+
at += p.length;
|
|
63
|
+
}
|
|
64
|
+
const hash = sha256(buf);
|
|
65
|
+
if (!onCurve(hash)) return hash;
|
|
66
|
+
}
|
|
67
|
+
throw new Error("no program address for these seeds");
|
|
68
|
+
}
|
|
69
|
+
function associatedTokenAccount(owner, mint, tokenProgram = solanaKey(SOLANA_TOKEN_PROGRAM)) {
|
|
70
|
+
return programAddress([owner, tokenProgram, mint], solanaKey(ASSOCIATED_TOKEN_PROGRAM));
|
|
71
|
+
}
|
|
72
|
+
|
|
9
73
|
// src/calldata.ts
|
|
10
74
|
var GENERIC_SWAPS = {
|
|
11
75
|
"0x4630a0d8": { single: false },
|
|
@@ -316,8 +380,9 @@ var FACET_DATA = {
|
|
|
316
380
|
Frax: { inline: 5, refunds: [["refundRecipient", 3]], zeroRefused: true },
|
|
317
381
|
Glacis: { inline: 4, refunds: [["refundAddress", 1]], zeroRefused: true },
|
|
318
382
|
LayerSwap: { refunds: [["refundRecipient", 2]], zeroRefused: true },
|
|
383
|
+
// The escrow order's user. Not reached while LI.FI Intents is refused on every EVM-source route (INTENTS_UNPINNED); kept for when
|
|
384
|
+
// its oracles and settler are pinned.
|
|
319
385
|
LiFiIntentEscrowV2: { refunds: [["depositAndRefundAddress", 2]], zeroRefused: true },
|
|
320
|
-
// the escrow order's user
|
|
321
386
|
M0: { inline: 7, refunds: [["refundRecipient", 1], ["orderOwner", 2]], zeroRefused: true, delivery: m0Delivery },
|
|
322
387
|
// orderOwner gets a cancelled order back
|
|
323
388
|
Mayan: { refunds: [["refundRecipient", 7]], nested: mayanRefunds, zeroRefused: true },
|
|
@@ -345,6 +410,86 @@ function refundMismatch(r, bridge, e) {
|
|
|
345
410
|
}
|
|
346
411
|
return null;
|
|
347
412
|
}
|
|
413
|
+
var NON_EVM_UNREAD = "the recipient on a non-EVM destination sits in bridge-specific data and cannot be checked here";
|
|
414
|
+
var LIFI_SOLANA_CHAIN_ID = 1151111081099710n;
|
|
415
|
+
var NON_EVM_ADDRESS = "0x11f111f111f111f111f111f111f111f111f111f1";
|
|
416
|
+
var SOLANA_USDC = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
|
|
417
|
+
var WORMHOLE_SOLANA = 1n;
|
|
418
|
+
var CCTP_DOMAIN_SOLANA = 5n;
|
|
419
|
+
var CHAINFLIP_SOLANA_ASSETS = {
|
|
420
|
+
"11111111111111111111111111111111": 9n,
|
|
421
|
+
[SOLANA_USDC]: 10n,
|
|
422
|
+
Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB: 16n
|
|
423
|
+
};
|
|
424
|
+
var DEPOSIT_FACETS = /* @__PURE__ */ new Set(["NEARIntents", "LayerSwap", "Unit"]);
|
|
425
|
+
var MAYAN_SOLANA = {
|
|
426
|
+
// Swift v2 createOrderWithToken(tokenIn, amountIn, (payloadType, trader, destAddr, destChainId, referrerAddr, tokenOut, minAmountOut, …), customPayload)
|
|
427
|
+
"0xa3a30834": { destAddr: 4, chain: 5, cctp: false, payloadType: [2, 1n], tokenOut: 7 },
|
|
428
|
+
// MCTP createOrder((tokenIn, amountIn, gasDrop, destAddr, destChain, tokenOut, minAmountOut, deadline, redeemFee, referrerAddr, referrerBps))
|
|
429
|
+
"0x1c59b7fc": { destAddr: 3, chain: 4, cctp: false, tokenOut: 5 },
|
|
430
|
+
// MCTP bridgeWithFee(tokenIn, amountIn, redeemFee, gasDrop, destAddr, destDomain, payloadType, customPayload): delivers USDC
|
|
431
|
+
"0x2072197f": { destAddr: 4, chain: 5, cctp: true, payloadType: [6, 1n] },
|
|
432
|
+
// Fast MCTP bridge(tokenIn, amountIn, redeemFee, circleMaxFee, gasDrop, destAddr, destDomain, referrerAddress, referrerBps, payloadType, minFinalityThreshold, customPayload): delivers USDC
|
|
433
|
+
"0xf58b6de8": { destAddr: 5, chain: 6, cctp: true, payloadType: [9, 1n] },
|
|
434
|
+
// Fast MCTP createOrder(tokenIn, amountIn, circleMaxFee, destDomain, minFinalityThreshold, (payloadType 3, destAddr, tokenOut, amountOutMin, …))
|
|
435
|
+
"0x2337e236": { destAddr: 6, chain: 3, cctp: true, payloadType: [5, 3n], tokenOut: 7 }
|
|
436
|
+
};
|
|
437
|
+
var hexOf = (b) => `0x${Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("")}`;
|
|
438
|
+
var keyOf = (word) => base58(Uint8Array.from(word.slice(2).match(/../g) ?? [], (h) => Number.parseInt(h, 16)));
|
|
439
|
+
function mayanToSolana(r, at, t) {
|
|
440
|
+
const declared = r.raw(at, 32);
|
|
441
|
+
if (declared !== t.ownerHex) return `Mayan's nonEVMReceiver is ${keyOf(declared)}, not the recipient ${t.e.receiver}`;
|
|
442
|
+
const p = r.bytes(at, at + 32 * 2);
|
|
443
|
+
const selector = p.raw(0, 4);
|
|
444
|
+
const o = MAYAN_SOLANA[selector];
|
|
445
|
+
if (!o) return `the Mayan call ${selector} is an entry point whose Solana recipient cannot be checked here`;
|
|
446
|
+
const word = (i) => 4 + 32 * i;
|
|
447
|
+
if (o.payloadType && p.word(word(o.payloadType[0])) !== o.payloadType[1]) return `the Mayan call ${selector} carries payload type ${p.word(word(o.payloadType[0]))}, not a plain delivery`;
|
|
448
|
+
const dest = p.raw(word(o.destAddr), 32);
|
|
449
|
+
if (dest !== t.ownerHex) return `the Mayan order pays ${keyOf(dest)} on Solana, not the recipient ${t.e.receiver}`;
|
|
450
|
+
const chain = p.word(word(o.chain));
|
|
451
|
+
if (chain !== (o.cctp ? CCTP_DOMAIN_SOLANA : WORMHOLE_SOLANA)) return `the Mayan order goes to ${o.cctp ? "CCTP domain" : "Wormhole chain"} ${chain}, not Solana`;
|
|
452
|
+
if (o.tokenOut === void 0) return t.e.toToken === SOLANA_USDC ? null : `a Mayan CCTP transfer delivers USDC, not ${t.e.toToken}`;
|
|
453
|
+
const token = p.raw(word(o.tokenOut), 32);
|
|
454
|
+
return token === t.mintHex ? null : `the Mayan order delivers ${keyOf(token)}, not ${t.e.toToken}`;
|
|
455
|
+
}
|
|
456
|
+
function polymerToSolana(r, at, t) {
|
|
457
|
+
const declared = r.raw(at + 32 * 2, 32);
|
|
458
|
+
if (declared !== t.ownerHex) return `Polymer CCTP's nonEVMReceiver is ${keyOf(declared)}, not the recipient ${t.e.receiver}`;
|
|
459
|
+
if (t.e.toToken !== SOLANA_USDC) return `Polymer CCTP delivers USDC, not ${t.e.toToken}`;
|
|
460
|
+
const mintTo = r.raw(at + 32 * 3, 32);
|
|
461
|
+
const account = hexOf(associatedTokenAccount(t.owner, t.mint));
|
|
462
|
+
return mintTo === account ? null : `the Polymer CCTP transfer mints to ${keyOf(mintTo)}, not the recipient's USDC account ${keyOf(account)}`;
|
|
463
|
+
}
|
|
464
|
+
function chainflipToSolana(r, at, t) {
|
|
465
|
+
const dst = r.bytes(at, at);
|
|
466
|
+
if (dst.length !== 32) return `Chainflip pays a ${dst.length}-byte address on Solana, not the recipient ${t.e.receiver}`;
|
|
467
|
+
if (dst.raw(0, 32) !== t.ownerHex) return `Chainflip pays ${keyOf(dst.raw(0, 32))} on Solana, not the recipient ${t.e.receiver}`;
|
|
468
|
+
const asset = r.word(at + 32);
|
|
469
|
+
const want = CHAINFLIP_SOLANA_ASSETS[t.e.toToken];
|
|
470
|
+
return want !== void 0 && asset === want ? null : `Chainflip delivers its asset ${asset}, not ${t.e.toToken}`;
|
|
471
|
+
}
|
|
472
|
+
var SOLANA_DELIVERY = {
|
|
473
|
+
Mayan: mayanToSolana,
|
|
474
|
+
PolymerCCTP: polymerToSolana,
|
|
475
|
+
Chainflip: chainflipToSolana
|
|
476
|
+
};
|
|
477
|
+
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";
|
|
478
|
+
var INTENTS_UNPINNED = "a LI.FI Intents order to an EVM chain names its own oracles, output settler and output amount, which cannot be checked here \u2014 its escrow could release the input without the delivery";
|
|
479
|
+
var INTENTS_FACET = "LiFiIntentEscrowV2";
|
|
480
|
+
function solanaRecipientMismatch(r, bridge, receiver, e) {
|
|
481
|
+
if (e.destChainId !== LIFI_SOLANA_CHAIN_ID) return NON_EVM_UNREAD;
|
|
482
|
+
if (!same(receiver, NON_EVM_ADDRESS)) return `BridgeData pays ${receiver}, not LI.FI's non-EVM placeholder ${NON_EVM_ADDRESS}, on a Solana route`;
|
|
483
|
+
if (DEPOSIT_FACETS.has(bridge.facet)) return `${bridge.facet} pays a deposit address whose off-chain order names the Solana recipient \u2014 the transaction does not bind it`;
|
|
484
|
+
if (bridge.facet === INTENTS_FACET) return INTENTS_TO_SOLANA;
|
|
485
|
+
const read = SOLANA_DELIVERY[bridge.facet];
|
|
486
|
+
if (!read) return NON_EVM_UNREAD;
|
|
487
|
+
const owner = solanaKey(e.receiver);
|
|
488
|
+
if (!owner) return `the recipient ${e.receiver} is not a Solana address`;
|
|
489
|
+
const mint = solanaKey(e.toToken);
|
|
490
|
+
if (!mint) return `the destination token ${e.toToken} is not a Solana mint`;
|
|
491
|
+
return read(r, facetDataAt(r, bridge, FACET_DATA[bridge.facet]), { owner, ownerHex: hexOf(owner), mint, mintHex: hexOf(mint), e });
|
|
492
|
+
}
|
|
348
493
|
function diamondCallMismatch(data, e) {
|
|
349
494
|
let r;
|
|
350
495
|
try {
|
|
@@ -373,6 +518,7 @@ function diamondCallMismatch(data, e) {
|
|
|
373
518
|
if (unbound) return `${unbound} delivers to a recipient the transaction does not bind (an off-chain order or unchecked facet data)`;
|
|
374
519
|
const bridge = BRIDGE_CALLS[selector];
|
|
375
520
|
if (!bridge) return `unknown Diamond call ${selector}`;
|
|
521
|
+
if (bridge.facet === INTENTS_FACET && e.evmDestination) return INTENTS_UNPINNED;
|
|
376
522
|
const head = r.small(4);
|
|
377
523
|
if (head < (bridge.sourceSwaps ? 96 : 64) || head % 32 !== 0 || head > 32 * 32) return `unknown Diamond call ${selector}`;
|
|
378
524
|
const at = 4 + head;
|
|
@@ -386,8 +532,10 @@ function diamondCallMismatch(data, e) {
|
|
|
386
532
|
if (e.sameChain) return "a bridge call on a same-chain route";
|
|
387
533
|
if (destinationChainId !== e.destChainId) return `bridges to chain ${destinationChainId}, not ${e.destChainId}`;
|
|
388
534
|
if (hasDestinationCall) return "the route runs a call on the destination chain, whose recipient cannot be checked here";
|
|
389
|
-
if (!e.evmDestination)
|
|
390
|
-
|
|
535
|
+
if (!e.evmDestination) {
|
|
536
|
+
const why = solanaRecipientMismatch(r, bridge, receiver, e);
|
|
537
|
+
if (why) return why;
|
|
538
|
+
} else if (!same(receiver, e.receiver)) return `pays ${receiver}, not ${e.receiver}`;
|
|
391
539
|
if (hasSourceSwaps !== bridge.sourceSwaps) return `unknown Diamond call ${selector}`;
|
|
392
540
|
const refund = refundMismatch(r, bridge, e);
|
|
393
541
|
if (refund) return refund;
|
|
@@ -407,7 +555,6 @@ function diamondCallMismatch(data, e) {
|
|
|
407
555
|
|
|
408
556
|
// src/solana.ts
|
|
409
557
|
import { base64Bytes } from "@anywalletkit/protocol-swap2-core/util";
|
|
410
|
-
var ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
|
411
558
|
var MAYAN_ENTRY_PROGRAMS = /* @__PURE__ */ new Set([
|
|
412
559
|
"D8C8iW6zmoKg5TRr8nQ7h14TMWqQX8FiBdj2ju5MF3wa",
|
|
413
560
|
// CPI_PROXY_PROGRAM_ID
|
|
@@ -422,20 +569,6 @@ var MAYAN_ENTRY_PROGRAMS = /* @__PURE__ */ new Set([
|
|
|
422
569
|
]);
|
|
423
570
|
var SYSTEM_PROGRAM = "11111111111111111111111111111111";
|
|
424
571
|
var LIFI_SOLANA_FEE_WALLET = "34FKjAdVcTax2DHqV2XnbXa9J3zmyKcFuFKWbcmgxjgm";
|
|
425
|
-
function base58(bytes) {
|
|
426
|
-
let n = 0n;
|
|
427
|
-
for (const b of bytes) n = n << 8n | BigInt(b);
|
|
428
|
-
let out = "";
|
|
429
|
-
while (n > 0n) {
|
|
430
|
-
out = ALPHABET[Number(n % 58n)] + out;
|
|
431
|
-
n /= 58n;
|
|
432
|
-
}
|
|
433
|
-
for (const b of bytes) {
|
|
434
|
-
if (b !== 0) break;
|
|
435
|
-
out = `1${out}`;
|
|
436
|
-
}
|
|
437
|
-
return out;
|
|
438
|
-
}
|
|
439
572
|
var Cursor = class {
|
|
440
573
|
constructor(b) {
|
|
441
574
|
this.b = b;
|
|
@@ -537,7 +670,8 @@ var LIFI_CHAIN = {
|
|
|
537
670
|
"eip155:2818": "2818",
|
|
538
671
|
"eip155:14": "14",
|
|
539
672
|
// 2026-11-10: Elysium (Kinetiq; li.quest/v1/chains id 99901, key "ely"). LI.FI's bridges there are Relay (refused
|
|
540
|
-
// below: UNBOUND_TOOLS) and LI.FI Intents
|
|
673
|
+
// below: UNBOUND_TOOLS) and LI.FI Intents (refused too: UNPINNED_TOOLS), so it has no cross-chain LI.FI route for now;
|
|
674
|
+
// same-chain swaps go through GenericSwapFacetV3 (KyberSwap, Nordstern).
|
|
541
675
|
"eip155:99901": "99901",
|
|
542
676
|
[LIFI_SOLANA_CAIP2]: "1151111081099710"
|
|
543
677
|
};
|
|
@@ -547,12 +681,12 @@ var SOLANA_NATIVE = "11111111111111111111111111111111";
|
|
|
547
681
|
var EVM_PLACEHOLDER = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
|
|
548
682
|
var SOLANA_PLACEHOLDER = "HAgk14JpMQLgt6rVgv7cBQFJWFto5Dqxi472uT3DKpqk";
|
|
549
683
|
var isSolana = (chain) => chain === LIFI_SOLANA_CAIP2;
|
|
550
|
-
var
|
|
684
|
+
var BASE582 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
551
685
|
var BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
552
686
|
var SAME_CHAIN_MAX_SECONDS = 90;
|
|
553
687
|
var MAX_INPUT_FEE_BPS = 300;
|
|
554
688
|
function sameChainAddress(chain, actual, expected) {
|
|
555
|
-
if (isSolana(chain)) return !!actual &&
|
|
689
|
+
if (isSolana(chain)) return !!actual && BASE582.test(actual) && actual === expected;
|
|
556
690
|
return sameAddress(actual, expected);
|
|
557
691
|
}
|
|
558
692
|
function nativeFor(chain, token) {
|
|
@@ -641,7 +775,16 @@ function headers(key) {
|
|
|
641
775
|
}
|
|
642
776
|
var TOOL_KEY = /^[A-Za-z0-9_-]{1,64}$/;
|
|
643
777
|
var UNBOUND_TOOLS = Object.freeze(["relaydepository", "garden", "squid", "symbiosis"]);
|
|
644
|
-
|
|
778
|
+
var UNPINNED_TOOLS = Object.freeze(["lifiIntents"]);
|
|
779
|
+
var REFUSED_EVM_TOOLS = Object.freeze([...UNBOUND_TOOLS, ...UNPINNED_TOOLS]);
|
|
780
|
+
var SOLANA_DESTINATION_TOOLS = Object.freeze(["mayan", "mayanMCTP", "mayanFastMCTP", "polymer", "polymerStandard", "chainflip"]);
|
|
781
|
+
var SMART_DEPOSITS_TOOL = /^smartdeposits?$/i;
|
|
782
|
+
var dryRecipient = (params, fromAddress) => !isSolana(params.sourceChain) && isSolana(params.destChain) ? SOLANA_PLACEHOLDER : fromAddress;
|
|
783
|
+
function placeholderIn(params) {
|
|
784
|
+
if (!params.refundAddress) return "sender";
|
|
785
|
+
return !params.destAddress && dryRecipient(params, params.refundAddress) !== params.refundAddress ? "recipient" : null;
|
|
786
|
+
}
|
|
787
|
+
function routeOptions(body, excluded, allowed) {
|
|
645
788
|
const routes = body?.routes;
|
|
646
789
|
if (!Array.isArray(routes)) return [];
|
|
647
790
|
const out = [];
|
|
@@ -651,7 +794,7 @@ function routeOptions(body, excluded) {
|
|
|
651
794
|
const cross = Array.isArray(step.includedSteps) ? step.includedSteps.find((i) => i?.type === "cross")?.tool : void 0;
|
|
652
795
|
const tool = typeof cross === "string" ? cross : step.tool;
|
|
653
796
|
const eta = step.estimate?.executionDuration;
|
|
654
|
-
if (typeof tool !== "string" || !TOOL_KEY.test(tool) || excluded.includes(tool) || typeof eta !== "number" || !Number.isFinite(eta) || eta < 0) continue;
|
|
797
|
+
if (typeof tool !== "string" || !TOOL_KEY.test(tool) || excluded.includes(tool) || allowed && !allowed.includes(tool) || typeof eta !== "number" || !Number.isFinite(eta) || eta < 0) continue;
|
|
655
798
|
if (typeof r.toAmount !== "string" || typeof r.toAmountMin !== "string" || !/^\d{1,60}$/.test(r.toAmount) || !/^\d{1,60}$/.test(r.toAmountMin)) continue;
|
|
656
799
|
out.push({
|
|
657
800
|
tool,
|
|
@@ -716,13 +859,15 @@ function lifiProvider(config = {}) {
|
|
|
716
859
|
if (!fromToken) return null;
|
|
717
860
|
const solanaSource = isSolana(params.sourceChain);
|
|
718
861
|
const crossChain = params.sourceChain !== params.destChain;
|
|
862
|
+
const evmToSolana = !solanaSource && isSolana(params.destChain);
|
|
719
863
|
const noDestinationCall = crossChain && !solanaSource;
|
|
720
864
|
let routeScope = config.routeWideSlippage === true && !solanaSource && !isSolana(params.destChain);
|
|
721
865
|
const dry = params.dry !== false;
|
|
722
866
|
const fromAddress = params.refundAddress || (dry ? solanaSource ? SOLANA_PLACEHOLDER : EVM_PLACEHOLDER : "");
|
|
723
|
-
const toAddress = params.destAddress || (dry ? fromAddress : "");
|
|
867
|
+
const toAddress = params.destAddress || (dry ? dryRecipient(params, fromAddress) : "");
|
|
724
868
|
if (!fromAddress) return reject("a live quote needs the sender's address");
|
|
725
869
|
if (!toAddress) return reject("a live quote needs a recipient");
|
|
870
|
+
const placeholder = dry ? placeholderIn(params) : null;
|
|
726
871
|
const slippageBps = params.slippageBps ?? defaultSlippageBps;
|
|
727
872
|
let sentSlippage = slippageBps / 1e4;
|
|
728
873
|
let deadline;
|
|
@@ -743,7 +888,7 @@ function lifiProvider(config = {}) {
|
|
|
743
888
|
options: {
|
|
744
889
|
slippage: slippageBps / 1e4,
|
|
745
890
|
...routeScope ? { slippageScope: "route" } : {},
|
|
746
|
-
...solanaSource ? {} : { bridges: { deny:
|
|
891
|
+
...solanaSource ? {} : { bridges: { ...evmToSolana ? { allow: SOLANA_DESTINATION_TOOLS } : {}, deny: REFUSED_EVM_TOOLS } },
|
|
747
892
|
...noDestinationCall ? { allowDestinationCall: false } : {},
|
|
748
893
|
...integrator().trim() ? { integrator: integrator().trim() } : {}
|
|
749
894
|
}
|
|
@@ -751,7 +896,7 @@ function lifiProvider(config = {}) {
|
|
|
751
896
|
signal: signal ?? timeoutSignal(8e3)
|
|
752
897
|
});
|
|
753
898
|
if (res.ok) {
|
|
754
|
-
const options = routeOptions(await res.json(), solanaSource ? [] :
|
|
899
|
+
const options = routeOptions(await res.json(), solanaSource ? [] : REFUSED_EVM_TOOLS, evmToSolana ? SOLANA_DESTINATION_TOOLS : void 0);
|
|
755
900
|
if (options.length) choice = cleanChoice(config.chooseRoute(options, params));
|
|
756
901
|
}
|
|
757
902
|
} catch {
|
|
@@ -773,8 +918,10 @@ function lifiProvider(config = {}) {
|
|
|
773
918
|
}
|
|
774
919
|
if (routeScope) url.searchParams.set("slippageScope", "route");
|
|
775
920
|
if (noDestinationCall) url.searchParams.set("allowDestinationCall", "false");
|
|
776
|
-
const unbound = crossChain && !solanaSource ?
|
|
777
|
-
|
|
921
|
+
const unbound = crossChain && !solanaSource ? REFUSED_EVM_TOOLS : [];
|
|
922
|
+
const allow = (choice?.allowBridges ?? []).filter((b) => solanaSource || !REFUSED_EVM_TOOLS.includes(b));
|
|
923
|
+
const solanaAllow = evmToSolana ? allow.filter((b) => SOLANA_DESTINATION_TOOLS.includes(b)) : [];
|
|
924
|
+
for (const t of evmToSolana ? solanaAllow.length ? solanaAllow : SOLANA_DESTINATION_TOOLS : allow) url.searchParams.append("allowBridges", t);
|
|
778
925
|
for (const t of /* @__PURE__ */ new Set([...choice?.denyBridges ?? [], ...unbound])) url.searchParams.append("denyBridges", t);
|
|
779
926
|
if (choice?.order) url.searchParams.set("order", choice.order);
|
|
780
927
|
deadline = signal ? void 0 : timeoutSignal(12e3);
|
|
@@ -798,6 +945,13 @@ function lifiProvider(config = {}) {
|
|
|
798
945
|
}
|
|
799
946
|
const q = await res.json();
|
|
800
947
|
const receivedAt = Date.now();
|
|
948
|
+
if (evmToSolana) {
|
|
949
|
+
const steps = q.includedSteps;
|
|
950
|
+
const tools = [q.tool, ...Array.isArray(steps) ? steps.map((i) => i?.tool) : []];
|
|
951
|
+
if (tools.some((t) => typeof t === "string" && SMART_DEPOSITS_TOOL.test(t))) {
|
|
952
|
+
return reject("LI.FI Smart Deposits pay a deposit address whose recipient the transaction does not bind \u2014 refused for a Solana destination");
|
|
953
|
+
}
|
|
954
|
+
}
|
|
801
955
|
const action = q.action;
|
|
802
956
|
const est = q.estimate;
|
|
803
957
|
const tr = q.transactionRequest;
|
|
@@ -838,6 +992,7 @@ function lifiProvider(config = {}) {
|
|
|
838
992
|
if (!trustedAddress(params.sourceChain, est.approvalAddress, trustedApprovalAddresses)) return reject("the approval target is not a trusted LI.FI contract");
|
|
839
993
|
}
|
|
840
994
|
let txRequest;
|
|
995
|
+
let nativeFee = 0n;
|
|
841
996
|
if (solanaSource) {
|
|
842
997
|
txRequest = { serializedTx: tr.data, value: sourceIsToken ? "0" : params.amountIn };
|
|
843
998
|
} else {
|
|
@@ -847,7 +1002,7 @@ function lifiProvider(config = {}) {
|
|
|
847
1002
|
return reject("the transaction value or gas limit is not a number");
|
|
848
1003
|
}
|
|
849
1004
|
const value = BigInt(tr.value);
|
|
850
|
-
|
|
1005
|
+
nativeFee = sourceIsToken ? value : value > BigInt(params.amountIn) ? value - BigInt(params.amountIn) : 0n;
|
|
851
1006
|
if (!sourceIsToken && value < BigInt(params.amountIn)) return reject("the transaction sends less native coin than the input");
|
|
852
1007
|
txRequest = {
|
|
853
1008
|
to: tr.to,
|
|
@@ -908,7 +1063,10 @@ function lifiProvider(config = {}) {
|
|
|
908
1063
|
intentId: `lifi:${fromChain}:${toChain}`,
|
|
909
1064
|
depositAddress: "",
|
|
910
1065
|
depositMemo: null,
|
|
911
|
-
|
|
1066
|
+
// Every check above ran on LI.FI's transaction; one built for a placeholder is not handed out (placeholderIn).
|
|
1067
|
+
// Its bridge fee on top of the input still is, as `providerData.nativeFee`, so the router's value nets it out
|
|
1068
|
+
// (usdValue reads it when there is no txRequest) instead of ranking the route on its gross output.
|
|
1069
|
+
...placeholder ? { providerData: { placeholder, ...nativeFee > 0n ? { nativeFee: nativeFee.toString() } : {} } } : { txRequest },
|
|
912
1070
|
// What the wallet spends (action.fromAmount, = the input); an input-side bridge fee is inside destAmount.
|
|
913
1071
|
sourceAmount: params.amountIn,
|
|
914
1072
|
destAmount: est.toAmount,
|
|
@@ -928,6 +1086,10 @@ function lifiProvider(config = {}) {
|
|
|
928
1086
|
return null;
|
|
929
1087
|
},
|
|
930
1088
|
async buildDeposit(quote) {
|
|
1089
|
+
const placeholder = quote.providerData?.placeholder;
|
|
1090
|
+
if (placeholder !== void 0) {
|
|
1091
|
+
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`);
|
|
1092
|
+
}
|
|
931
1093
|
const tx = quote.txRequest;
|
|
932
1094
|
if (!tx) throw new Error("swap2: LI.FI quote has no transaction request");
|
|
933
1095
|
const [, fromChain] = quote.intentId.split(":");
|
|
@@ -979,6 +1141,11 @@ function lifiVerifyQuote(params, quote) {
|
|
|
979
1141
|
const fromChain = LIFI_CHAIN[params.sourceChain];
|
|
980
1142
|
const toChain = LIFI_CHAIN[params.destChain];
|
|
981
1143
|
if (!fromChain || !toChain) return "not a LI.FI chain pair";
|
|
1144
|
+
const placeholder = params.dry !== false ? placeholderIn(params) : null;
|
|
1145
|
+
if (placeholder) {
|
|
1146
|
+
if (quote.txRequest !== void 0) return `a dry quote priced for a placeholder ${placeholder} carries a transaction \u2014 it would pay or refund that placeholder, not the wallet that signs it`;
|
|
1147
|
+
return priceOnlyMismatch(params, quote, `lifi:${fromChain}:${toChain}`);
|
|
1148
|
+
}
|
|
982
1149
|
const tx = quote.txRequest;
|
|
983
1150
|
if (!tx) return "the quote carries no transaction";
|
|
984
1151
|
if (isSolana(params.sourceChain)) {
|
|
@@ -993,7 +1160,7 @@ function lifiVerifyQuote(params, quote) {
|
|
|
993
1160
|
const sourceIsToken = fromToken !== EVM_ZERO;
|
|
994
1161
|
const dry = params.dry !== false;
|
|
995
1162
|
const fromAddress = params.refundAddress || (dry ? EVM_PLACEHOLDER : "");
|
|
996
|
-
const toAddress = params.destAddress || (dry ? fromAddress : "");
|
|
1163
|
+
const toAddress = params.destAddress || (dry ? dryRecipient(params, fromAddress) : "");
|
|
997
1164
|
if (!fromAddress || !toAddress) return "a live quote needs the sender and the recipient";
|
|
998
1165
|
let amountIn, minOut, value, nativeFee;
|
|
999
1166
|
try {
|
|
@@ -1027,6 +1194,19 @@ function lifiVerifyQuote(params, quote) {
|
|
|
1027
1194
|
});
|
|
1028
1195
|
return why ? `the transaction does not match the request: ${why}` : null;
|
|
1029
1196
|
}
|
|
1197
|
+
function priceOnlyMismatch(params, quote, route) {
|
|
1198
|
+
if (quote.depositAddress !== "") return `a LI.FI quote pays no deposit address, but this one names ${String(quote.depositAddress).slice(0, 64)}`;
|
|
1199
|
+
const fee = quote.providerData?.nativeFee;
|
|
1200
|
+
if (fee !== void 0 && !/^\d{1,80}$/.test(String(fee))) return `the price-only quote declares a native fee of ${String(fee).slice(0, 40)}, not a base-unit integer`;
|
|
1201
|
+
try {
|
|
1202
|
+
if (BigInt(quote.sourceAmount) !== BigInt(params.amountIn)) return `the quote spends ${quote.sourceAmount}, not the ${params.amountIn} input`;
|
|
1203
|
+
const minOut = BigInt(quote.minDestAmount);
|
|
1204
|
+
if (params.minAmountOut !== void 0 && minOut < BigInt(params.minAmountOut)) return `the quoted minimum ${minOut} is below the caller's floor ${params.minAmountOut}`;
|
|
1205
|
+
} catch {
|
|
1206
|
+
return "the quote's amounts are not integers";
|
|
1207
|
+
}
|
|
1208
|
+
return quote.intentId.replace(/~[0-9a-f]{32}$/, "") === route ? null : "the intent id does not name this route";
|
|
1209
|
+
}
|
|
1030
1210
|
var lifiPins = Object.freeze({
|
|
1031
1211
|
callTargets: (chain) => /^eip155:[0-9]+$/.test(chain) ? [diamondFor(chain)] : [],
|
|
1032
1212
|
spenders: (chain) => /^eip155:[0-9]+$/.test(chain) ? [diamondFor(chain)] : [],
|
|
@@ -1038,4 +1218,4 @@ export {
|
|
|
1038
1218
|
lifiVerifyQuote,
|
|
1039
1219
|
lifiPins
|
|
1040
1220
|
};
|
|
1041
|
-
//# sourceMappingURL=chunk-
|
|
1221
|
+
//# sourceMappingURL=chunk-GWUGK4SG.js.map
|