@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/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
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/config.ts
|
|
2
2
|
import { evmRef, isEvm } from "@anywalletkit/protocol-swap2-core/util";
|
|
3
3
|
var LIFI_SOLANA_CAIP2 = "solana:mainnet";
|
|
4
|
-
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]);
|
|
4
|
+
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]);
|
|
5
5
|
var lifi = {
|
|
6
6
|
enabled: () => true,
|
|
7
7
|
apiKey: () => void 0,
|
|
@@ -19,4 +19,4 @@ export {
|
|
|
19
19
|
LIFI_SOLANA_CAIP2,
|
|
20
20
|
lifi
|
|
21
21
|
};
|
|
22
|
-
//# sourceMappingURL=chunk-
|
|
22
|
+
//# sourceMappingURL=chunk-AARB4MBP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/config.ts"],"sourcesContent":["import {evmRef, isEvm} from \"@anywalletkit/protocol-swap2-core/util\";\n\n// ── LI.FI — EVM calldata routes implemented by this contribution adapter ─────\n// 2026-08 sweep (li.quest/v1/chains): + Avalanche 43114, Scroll 534352,\n// Berachain 80094 (HyperEVM 999 / Robinhood 4663 were already routed by the\n// provider's own chain map). 2026-11-10: + Elysium 99901 (li.quest/v1/chains key \"ely\").\n/** The catalog's Solana id; LI.FI numbers it 1151111081099710. */\nexport const LIFI_SOLANA_CAIP2 = \"solana:mainnet\";\nconst LIFI_EVM = 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, 5000, 747474, 2818, 14, 99901]);\nexport const lifi = {\n enabled: () => true,\n apiKey: () => undefined,\n integrator: () => \"\",\n supportsChain(chain: string): boolean {\n if (isEvm(chain)) return LIFI_EVM.has(evmRef(chain));\n // Solana legs come back as a serialized transaction the account executes\n // through its `sendSerializedTransaction` guard. Tron is still excluded.\n return chain === LIFI_SOLANA_CAIP2;\n },\n supports(from: string, to: string): boolean {\n return this.supportsChain(from) && this.supportsChain(to);\n },\n};\n"],"mappings":";AAAA,SAAQ,QAAQ,aAAY;AAOrB,IAAM,oBAAoB;AACjC,IAAM,WAAW,oBAAI,IAAI,CAAC,GAAG,MAAM,IAAI,KAAK,OAAO,IAAI,KAAK,KAAK,KAAK,KAAK,MAAM,OAAO,QAAQ,OAAO,KAAK,OAAO,MAAM,MAAM,OAAO,KAAK,KAAK,MAAM,OAAO,OAAO,KAAK,MAAM,KAAM,QAAQ,MAAM,IAAI,KAAK,CAAC;AACtM,IAAM,OAAO;AAAA,EAClB,SAAS,MAAM;AAAA,EACf,QAAQ,MAAM;AAAA,EACd,YAAY,MAAM;AAAA,EAClB,cAAc,OAAwB;AACpC,QAAI,MAAM,KAAK,EAAG,QAAO,SAAS,IAAI,OAAO,KAAK,CAAC;AAGnD,WAAO,UAAU;AAAA,EACnB;AAAA,EACA,SAAS,MAAc,IAAqB;AAC1C,WAAO,KAAK,cAAc,IAAI,KAAK,KAAK,cAAc,EAAE;AAAA,EAC1D;AACF;","names":[]}
|
|
@@ -1,11 +1,75 @@
|
|
|
1
1
|
import {
|
|
2
2
|
LIFI_SOLANA_CAIP2,
|
|
3
3
|
lifi
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-AARB4MBP.js";
|
|
5
5
|
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;
|
|
@@ -536,6 +669,10 @@ var LIFI_CHAIN = {
|
|
|
536
669
|
"eip155:747474": "747474",
|
|
537
670
|
"eip155:2818": "2818",
|
|
538
671
|
"eip155:14": "14",
|
|
672
|
+
// 2026-11-10: Elysium (Kinetiq; li.quest/v1/chains id 99901, key "ely"). LI.FI's bridges there are Relay (refused
|
|
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).
|
|
675
|
+
"eip155:99901": "99901",
|
|
539
676
|
[LIFI_SOLANA_CAIP2]: "1151111081099710"
|
|
540
677
|
};
|
|
541
678
|
var CAIP2_BY_LIFI_CHAIN = Object.fromEntries(Object.entries(LIFI_CHAIN).map(([caip2, id]) => [id, caip2]));
|
|
@@ -544,12 +681,12 @@ var SOLANA_NATIVE = "11111111111111111111111111111111";
|
|
|
544
681
|
var EVM_PLACEHOLDER = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045";
|
|
545
682
|
var SOLANA_PLACEHOLDER = "HAgk14JpMQLgt6rVgv7cBQFJWFto5Dqxi472uT3DKpqk";
|
|
546
683
|
var isSolana = (chain) => chain === LIFI_SOLANA_CAIP2;
|
|
547
|
-
var
|
|
684
|
+
var BASE582 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
548
685
|
var BASE64 = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
|
|
549
686
|
var SAME_CHAIN_MAX_SECONDS = 90;
|
|
550
687
|
var MAX_INPUT_FEE_BPS = 300;
|
|
551
688
|
function sameChainAddress(chain, actual, expected) {
|
|
552
|
-
if (isSolana(chain)) return !!actual &&
|
|
689
|
+
if (isSolana(chain)) return !!actual && BASE582.test(actual) && actual === expected;
|
|
553
690
|
return sameAddress(actual, expected);
|
|
554
691
|
}
|
|
555
692
|
function nativeFor(chain, token) {
|
|
@@ -588,6 +725,10 @@ var LIFI_DIAMONDS = {
|
|
|
588
725
|
// Morph
|
|
589
726
|
"eip155:14": "0x198FC70Dfe05E755C81e54bd67Bff3F729344B9b",
|
|
590
727
|
// Flare (li.quest diamondAddress 2026-10-14; the canonical 0x1231… has no code there)
|
|
728
|
+
// Elysium: li.quest diamondAddress = deployments/elysium.json LiFiDiamond; code on 99901, DiamondLoupe facets() serves
|
|
729
|
+
// GenericSwapFacetV3 + LiFiIntentEscrowFacetV2; the canonical 0x1231… has no code there (all read 2026-11-10).
|
|
730
|
+
"eip155:99901": "0x24E6dA91AA4A8FEe2447F542c92E006243bd6Dc5",
|
|
731
|
+
// Elysium
|
|
591
732
|
"eip155:4326": "0x026F252016A7C47CDEf1F05a3Fc9E20C92a49C37"
|
|
592
733
|
// MegaETH (deployments/megaeth.json, matches a live quote 2026-10-12)
|
|
593
734
|
};
|
|
@@ -634,7 +775,16 @@ function headers(key) {
|
|
|
634
775
|
}
|
|
635
776
|
var TOOL_KEY = /^[A-Za-z0-9_-]{1,64}$/;
|
|
636
777
|
var UNBOUND_TOOLS = Object.freeze(["relaydepository", "garden", "squid", "symbiosis"]);
|
|
637
|
-
|
|
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) {
|
|
638
788
|
const routes = body?.routes;
|
|
639
789
|
if (!Array.isArray(routes)) return [];
|
|
640
790
|
const out = [];
|
|
@@ -644,7 +794,7 @@ function routeOptions(body, excluded) {
|
|
|
644
794
|
const cross = Array.isArray(step.includedSteps) ? step.includedSteps.find((i) => i?.type === "cross")?.tool : void 0;
|
|
645
795
|
const tool = typeof cross === "string" ? cross : step.tool;
|
|
646
796
|
const eta = step.estimate?.executionDuration;
|
|
647
|
-
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;
|
|
648
798
|
if (typeof r.toAmount !== "string" || typeof r.toAmountMin !== "string" || !/^\d{1,60}$/.test(r.toAmount) || !/^\d{1,60}$/.test(r.toAmountMin)) continue;
|
|
649
799
|
out.push({
|
|
650
800
|
tool,
|
|
@@ -709,13 +859,15 @@ function lifiProvider(config = {}) {
|
|
|
709
859
|
if (!fromToken) return null;
|
|
710
860
|
const solanaSource = isSolana(params.sourceChain);
|
|
711
861
|
const crossChain = params.sourceChain !== params.destChain;
|
|
862
|
+
const evmToSolana = !solanaSource && isSolana(params.destChain);
|
|
712
863
|
const noDestinationCall = crossChain && !solanaSource;
|
|
713
864
|
let routeScope = config.routeWideSlippage === true && !solanaSource && !isSolana(params.destChain);
|
|
714
865
|
const dry = params.dry !== false;
|
|
715
866
|
const fromAddress = params.refundAddress || (dry ? solanaSource ? SOLANA_PLACEHOLDER : EVM_PLACEHOLDER : "");
|
|
716
|
-
const toAddress = params.destAddress || (dry ? fromAddress : "");
|
|
867
|
+
const toAddress = params.destAddress || (dry ? dryRecipient(params, fromAddress) : "");
|
|
717
868
|
if (!fromAddress) return reject("a live quote needs the sender's address");
|
|
718
869
|
if (!toAddress) return reject("a live quote needs a recipient");
|
|
870
|
+
const placeholder = dry ? placeholderIn(params) : null;
|
|
719
871
|
const slippageBps = params.slippageBps ?? defaultSlippageBps;
|
|
720
872
|
let sentSlippage = slippageBps / 1e4;
|
|
721
873
|
let deadline;
|
|
@@ -736,7 +888,7 @@ function lifiProvider(config = {}) {
|
|
|
736
888
|
options: {
|
|
737
889
|
slippage: slippageBps / 1e4,
|
|
738
890
|
...routeScope ? { slippageScope: "route" } : {},
|
|
739
|
-
...solanaSource ? {} : { bridges: { deny:
|
|
891
|
+
...solanaSource ? {} : { bridges: { ...evmToSolana ? { allow: SOLANA_DESTINATION_TOOLS } : {}, deny: REFUSED_EVM_TOOLS } },
|
|
740
892
|
...noDestinationCall ? { allowDestinationCall: false } : {},
|
|
741
893
|
...integrator().trim() ? { integrator: integrator().trim() } : {}
|
|
742
894
|
}
|
|
@@ -744,7 +896,7 @@ function lifiProvider(config = {}) {
|
|
|
744
896
|
signal: signal ?? timeoutSignal(8e3)
|
|
745
897
|
});
|
|
746
898
|
if (res.ok) {
|
|
747
|
-
const options = routeOptions(await res.json(), solanaSource ? [] :
|
|
899
|
+
const options = routeOptions(await res.json(), solanaSource ? [] : REFUSED_EVM_TOOLS, evmToSolana ? SOLANA_DESTINATION_TOOLS : void 0);
|
|
748
900
|
if (options.length) choice = cleanChoice(config.chooseRoute(options, params));
|
|
749
901
|
}
|
|
750
902
|
} catch {
|
|
@@ -766,8 +918,10 @@ function lifiProvider(config = {}) {
|
|
|
766
918
|
}
|
|
767
919
|
if (routeScope) url.searchParams.set("slippageScope", "route");
|
|
768
920
|
if (noDestinationCall) url.searchParams.set("allowDestinationCall", "false");
|
|
769
|
-
const unbound = crossChain && !solanaSource ?
|
|
770
|
-
|
|
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);
|
|
771
925
|
for (const t of /* @__PURE__ */ new Set([...choice?.denyBridges ?? [], ...unbound])) url.searchParams.append("denyBridges", t);
|
|
772
926
|
if (choice?.order) url.searchParams.set("order", choice.order);
|
|
773
927
|
deadline = signal ? void 0 : timeoutSignal(12e3);
|
|
@@ -791,6 +945,13 @@ function lifiProvider(config = {}) {
|
|
|
791
945
|
}
|
|
792
946
|
const q = await res.json();
|
|
793
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
|
+
}
|
|
794
955
|
const action = q.action;
|
|
795
956
|
const est = q.estimate;
|
|
796
957
|
const tr = q.transactionRequest;
|
|
@@ -831,6 +992,7 @@ function lifiProvider(config = {}) {
|
|
|
831
992
|
if (!trustedAddress(params.sourceChain, est.approvalAddress, trustedApprovalAddresses)) return reject("the approval target is not a trusted LI.FI contract");
|
|
832
993
|
}
|
|
833
994
|
let txRequest;
|
|
995
|
+
let nativeFee = 0n;
|
|
834
996
|
if (solanaSource) {
|
|
835
997
|
txRequest = { serializedTx: tr.data, value: sourceIsToken ? "0" : params.amountIn };
|
|
836
998
|
} else {
|
|
@@ -840,7 +1002,7 @@ function lifiProvider(config = {}) {
|
|
|
840
1002
|
return reject("the transaction value or gas limit is not a number");
|
|
841
1003
|
}
|
|
842
1004
|
const value = BigInt(tr.value);
|
|
843
|
-
|
|
1005
|
+
nativeFee = sourceIsToken ? value : value > BigInt(params.amountIn) ? value - BigInt(params.amountIn) : 0n;
|
|
844
1006
|
if (!sourceIsToken && value < BigInt(params.amountIn)) return reject("the transaction sends less native coin than the input");
|
|
845
1007
|
txRequest = {
|
|
846
1008
|
to: tr.to,
|
|
@@ -901,7 +1063,10 @@ function lifiProvider(config = {}) {
|
|
|
901
1063
|
intentId: `lifi:${fromChain}:${toChain}`,
|
|
902
1064
|
depositAddress: "",
|
|
903
1065
|
depositMemo: null,
|
|
904
|
-
|
|
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 },
|
|
905
1070
|
// What the wallet spends (action.fromAmount, = the input); an input-side bridge fee is inside destAmount.
|
|
906
1071
|
sourceAmount: params.amountIn,
|
|
907
1072
|
destAmount: est.toAmount,
|
|
@@ -921,6 +1086,10 @@ function lifiProvider(config = {}) {
|
|
|
921
1086
|
return null;
|
|
922
1087
|
},
|
|
923
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
|
+
}
|
|
924
1093
|
const tx = quote.txRequest;
|
|
925
1094
|
if (!tx) throw new Error("swap2: LI.FI quote has no transaction request");
|
|
926
1095
|
const [, fromChain] = quote.intentId.split(":");
|
|
@@ -972,6 +1141,11 @@ function lifiVerifyQuote(params, quote) {
|
|
|
972
1141
|
const fromChain = LIFI_CHAIN[params.sourceChain];
|
|
973
1142
|
const toChain = LIFI_CHAIN[params.destChain];
|
|
974
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
|
+
}
|
|
975
1149
|
const tx = quote.txRequest;
|
|
976
1150
|
if (!tx) return "the quote carries no transaction";
|
|
977
1151
|
if (isSolana(params.sourceChain)) {
|
|
@@ -986,7 +1160,7 @@ function lifiVerifyQuote(params, quote) {
|
|
|
986
1160
|
const sourceIsToken = fromToken !== EVM_ZERO;
|
|
987
1161
|
const dry = params.dry !== false;
|
|
988
1162
|
const fromAddress = params.refundAddress || (dry ? EVM_PLACEHOLDER : "");
|
|
989
|
-
const toAddress = params.destAddress || (dry ? fromAddress : "");
|
|
1163
|
+
const toAddress = params.destAddress || (dry ? dryRecipient(params, fromAddress) : "");
|
|
990
1164
|
if (!fromAddress || !toAddress) return "a live quote needs the sender and the recipient";
|
|
991
1165
|
let amountIn, minOut, value, nativeFee;
|
|
992
1166
|
try {
|
|
@@ -1020,6 +1194,19 @@ function lifiVerifyQuote(params, quote) {
|
|
|
1020
1194
|
});
|
|
1021
1195
|
return why ? `the transaction does not match the request: ${why}` : null;
|
|
1022
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
|
+
}
|
|
1023
1210
|
var lifiPins = Object.freeze({
|
|
1024
1211
|
callTargets: (chain) => /^eip155:[0-9]+$/.test(chain) ? [diamondFor(chain)] : [],
|
|
1025
1212
|
spenders: (chain) => /^eip155:[0-9]+$/.test(chain) ? [diamondFor(chain)] : [],
|
|
@@ -1031,4 +1218,4 @@ export {
|
|
|
1031
1218
|
lifiVerifyQuote,
|
|
1032
1219
|
lifiPins
|
|
1033
1220
|
};
|
|
1034
|
-
//# sourceMappingURL=chunk-
|
|
1221
|
+
//# sourceMappingURL=chunk-GWUGK4SG.js.map
|