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