@anywalletkit/protocol-swap2-lifi 0.7.0 → 0.8.0

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