@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 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, EVM-source routes to a non-EVM chain, and the
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. Unknown calldata is refused.
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-6N4HZ5XX.js.map
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-6N4HZ5XX.js";
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) return "the recipient on a non-EVM destination sits in bridge-specific data and cannot be checked here";
390
- if (!same(receiver, e.receiver)) return `pays ${receiver}, not ${e.receiver}`;
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 BASE58 = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
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 && BASE58.test(actual) && actual === expected;
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
- function routeOptions(body, excluded) {
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: UNBOUND_TOOLS } },
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 ? [] : UNBOUND_TOOLS);
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 ? UNBOUND_TOOLS : [];
770
- for (const t of (choice?.allowBridges ?? []).filter((b) => solanaSource || !UNBOUND_TOOLS.includes(b))) url.searchParams.append("allowBridges", t);
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
- const nativeFee = sourceIsToken ? value : value > BigInt(params.amountIn) ? value - BigInt(params.amountIn) : 0n;
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
- txRequest,
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-IZ7TP4KT.js.map
1221
+ //# sourceMappingURL=chunk-GWUGK4SG.js.map