@anywalletkit/protocol-swap2-lifi 0.2.4 → 0.2.6

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.cjs CHANGED
@@ -79,8 +79,257 @@ function describeLifiActivity(tx, ctx, isDiamond) {
79
79
  });
80
80
  }
81
81
 
82
+ // src/calldata.ts
83
+ var GENERIC_SWAPS = {
84
+ "0x4630a0d8": { single: false },
85
+ // swapTokensGeneric
86
+ "0x4666fc80": { single: true },
87
+ // swapTokensSingleV3ERC20ToERC20
88
+ "0x733214a3": { single: true },
89
+ // swapTokensSingleV3ERC20ToNative
90
+ "0xaf7060fd": { single: true },
91
+ // swapTokensSingleV3NativeToERC20
92
+ "0x5fd9ae2e": { single: false },
93
+ // swapTokensMultipleV3ERC20ToERC20
94
+ "0x2c57e884": { single: false },
95
+ // swapTokensMultipleV3ERC20ToNative
96
+ "0x736eac0b": { single: false }
97
+ // swapTokensMultipleV3NativeToERC20
98
+ };
99
+ var BRIDGE_CALLS = {
100
+ "0x1fd8010c": { sourceSwaps: false },
101
+ // startBridgeTokensViaAcross (AcrossFacet)
102
+ "0x3a3f7332": { sourceSwaps: true },
103
+ // swapAndStartBridgeTokensViaAcross (AcrossFacet)
104
+ "0xa1f1ce43": { sourceSwaps: false },
105
+ // startBridgeTokensViaAcrossV4 (AcrossFacetV4)
106
+ "0x1794958f": { sourceSwaps: true },
107
+ // swapAndStartBridgeTokensViaAcrossV4 (AcrossFacetV4)
108
+ "0x6a90d66e": { sourceSwaps: false },
109
+ // startBridgeTokensViaAcrossV4Swap (AcrossV4SwapFacet)
110
+ "0x9b054bc4": { sourceSwaps: true },
111
+ // swapAndStartBridgeTokensViaAcrossV4Swap (AcrossV4SwapFacet)
112
+ "0x6a51e9a9": { sourceSwaps: false },
113
+ // startBridgeTokensViaAllBridge (AllBridgeFacet)
114
+ "0x63267469": { sourceSwaps: true },
115
+ // swapAndStartBridgeTokensViaAllBridge (AllBridgeFacet)
116
+ "0xc9851d0b": { sourceSwaps: false },
117
+ // startBridgeTokensViaArbitrumBridge (ArbitrumBridgeFacet)
118
+ "0x3cc9517b": { sourceSwaps: true },
119
+ // swapAndStartBridgeTokensViaArbitrumBridge (ArbitrumBridgeFacet)
120
+ "0xe0a4201c": { sourceSwaps: false },
121
+ // startBridgeTokensViaCelerCircleBridge (CelerCircleBridgeFacet)
122
+ "0x4f3b0759": { sourceSwaps: true },
123
+ // swapAndStartBridgeTokensViaCelerCircleBridge (CelerCircleBridgeFacet)
124
+ "0x0315138f": { sourceSwaps: false },
125
+ // startBridgeTokensViaCentrifuge (CentrifugeFacet)
126
+ "0x8fd4d4dd": { sourceSwaps: true },
127
+ // swapAndStartBridgeTokensViaCentrifuge (CentrifugeFacet)
128
+ "0x0ad553b3": { sourceSwaps: false },
129
+ // startBridgeTokensViaChainflip (ChainflipFacet)
130
+ "0xee3314a1": { sourceSwaps: true },
131
+ // swapAndStartBridgeTokensViaChainflip (ChainflipFacet)
132
+ "0x4004633e": { sourceSwaps: false },
133
+ // startBridgeTokensViaDeBridgeDln (DeBridgeDlnFacet)
134
+ "0x2c7d2db0": { sourceSwaps: true },
135
+ // swapAndStartBridgeTokensViaDeBridgeDln (DeBridgeDlnFacet)
136
+ "0xbff90b61": { sourceSwaps: false },
137
+ // startBridgeTokensViaEco (EcoFacet)
138
+ "0x762aea18": { sourceSwaps: true },
139
+ // swapAndStartBridgeTokensViaEco (EcoFacet)
140
+ "0x678786ea": { sourceSwaps: false },
141
+ // startBridgeTokensViaFrax (FraxFacet)
142
+ "0x07b8e4c7": { sourceSwaps: true },
143
+ // swapAndStartBridgeTokensViaFrax (FraxFacet)
144
+ "0x5f9af35d": { sourceSwaps: false },
145
+ // startBridgeTokensViaGarden (GardenFacet)
146
+ "0x76ad76fe": { sourceSwaps: true },
147
+ // swapAndStartBridgeTokensViaGarden (GardenFacet)
148
+ "0xfc5f1003": { sourceSwaps: false },
149
+ // startBridgeTokensViaGasZip (GasZipFacet)
150
+ "0x606326ff": { sourceSwaps: true },
151
+ // swapAndStartBridgeTokensViaGasZip (GasZipFacet)
152
+ "0x6f9206ba": { sourceSwaps: false },
153
+ // startBridgeTokensViaGlacis (GlacisFacet)
154
+ "0x9c4b6dd9": { sourceSwaps: true },
155
+ // swapAndStartBridgeTokensViaGlacis (GlacisFacet)
156
+ "0xf66fe519": { sourceSwaps: false },
157
+ // startBridgeTokensViaGnosisBridge (GnosisBridgeFacet)
158
+ "0x7bf96e0a": { sourceSwaps: true },
159
+ // swapAndStartBridgeTokensViaGnosisBridge (GnosisBridgeFacet)
160
+ "0xee9e98e0": { sourceSwaps: false },
161
+ // startBridgeTokensViaLayerSwap (LayerSwapFacet)
162
+ "0x4c279d6b": { sourceSwaps: true },
163
+ // swapAndStartBridgeTokensViaLayerSwap (LayerSwapFacet)
164
+ "0x7dbcf1d9": { sourceSwaps: false },
165
+ // startBridgeTokensViaLiFiIntentEscrowV2 (LiFiIntentEscrowFacetV2)
166
+ "0x6d21c5df": { sourceSwaps: true },
167
+ // swapAndStartBridgeTokensViaLiFiIntentEscrowV2 (LiFiIntentEscrowFacetV2)
168
+ "0x65782c4b": { sourceSwaps: false },
169
+ // startBridgeTokensViaM0 (M0Facet)
170
+ "0xb9eaf86d": { sourceSwaps: true },
171
+ // swapAndStartBridgeTokensViaM0 (M0Facet)
172
+ "0xb4875292": { sourceSwaps: false },
173
+ // startBridgeTokensViaMayan (MayanFacet)
174
+ "0x80c65808": { sourceSwaps: true },
175
+ // swapAndStartBridgeTokensViaMayan (MayanFacet)
176
+ "0x4213dfff": { sourceSwaps: false },
177
+ // startBridgeTokensViaMegaETHBridge (MegaETHBridgeFacet)
178
+ "0x22256e89": { sourceSwaps: true },
179
+ // swapAndStartBridgeTokensViaMegaETHBridge (MegaETHBridgeFacet)
180
+ "0x4698f032": { sourceSwaps: false },
181
+ // startBridgeTokensViaNEARIntents (NEARIntentsFacet)
182
+ "0x02631b09": { sourceSwaps: true },
183
+ // swapAndStartBridgeTokensViaNEARIntents (NEARIntentsFacet)
184
+ "0x782621d8": { sourceSwaps: false },
185
+ // startBridgeTokensViaOmniBridge (OmniBridgeFacet)
186
+ "0x95726782": { sourceSwaps: true },
187
+ // swapAndStartBridgeTokensViaOmniBridge (OmniBridgeFacet)
188
+ "0xce8a97a5": { sourceSwaps: false },
189
+ // startBridgeTokensViaOptimismBridge (OptimismBridgeFacet)
190
+ "0x5bb5d448": { sourceSwaps: true },
191
+ // swapAndStartBridgeTokensViaOptimismBridge (OptimismBridgeFacet)
192
+ "0xaf62c7d6": { sourceSwaps: false },
193
+ // startBridgeTokensViaPolygonBridge (PolygonBridgeFacet)
194
+ "0xb4f37581": { sourceSwaps: true },
195
+ // swapAndStartBridgeTokensViaPolygonBridge (PolygonBridgeFacet)
196
+ "0xf434d6ca": { sourceSwaps: false },
197
+ // startBridgeTokensViaPolymerCCTP (PolymerCCTPFacet)
198
+ "0x17917a4e": { sourceSwaps: true },
199
+ // swapAndStartBridgeTokensViaPolymerCCTP (PolymerCCTPFacet)
200
+ "0x092e8fa4": { sourceSwaps: false },
201
+ // startBridgeTokensViaRelayDepository (RelayDepositoryFacet)
202
+ "0xa3443faa": { sourceSwaps: true },
203
+ // swapAndStartBridgeTokensViaRelayDepository (RelayDepositoryFacet)
204
+ "0x3f313808": { sourceSwaps: false },
205
+ // startBridgeTokensViaSquid (SquidFacet)
206
+ "0xa8f66666": { sourceSwaps: true },
207
+ // swapAndStartBridgeTokensViaSquid (SquidFacet)
208
+ "0x14d53077": { sourceSwaps: false },
209
+ // startBridgeTokensViaStargate (StargateFacetV2)
210
+ "0xa6010a66": { sourceSwaps: true },
211
+ // swapAndStartBridgeTokensViaStargate (StargateFacetV2)
212
+ "0x0691ff78": { sourceSwaps: false },
213
+ // startBridgeTokensViaSuperset (SupersetFacet)
214
+ "0xf26e657f": { sourceSwaps: true },
215
+ // swapAndStartBridgeTokensViaSuperset (SupersetFacet)
216
+ "0xe23b7a08": { sourceSwaps: false },
217
+ // startBridgeTokensViaSymbiosis (SymbiosisFacet)
218
+ "0xc46059b2": { sourceSwaps: true },
219
+ // swapAndStartBridgeTokensViaSymbiosis (SymbiosisFacet)
220
+ "0x2541ec57": { sourceSwaps: false },
221
+ // startBridgeTokensViaThorSwap (ThorSwapFacet)
222
+ "0xad673d88": { sourceSwaps: true },
223
+ // swapAndStartBridgeTokensViaThorSwap (ThorSwapFacet)
224
+ "0x64261d58": { sourceSwaps: false },
225
+ // startBridgeTokensViaUnit (UnitFacet)
226
+ "0x21a3af52": { sourceSwaps: true }
227
+ // swapAndStartBridgeTokensViaUnit (UnitFacet)
228
+ };
229
+ var Reader = class {
230
+ b;
231
+ constructor(hex) {
232
+ if (!/^0x([0-9a-fA-F]{2})*$/.test(hex)) throw new Error("not hex");
233
+ this.b = Uint8Array.from(hex.slice(2).match(/../g) ?? [], (h) => Number.parseInt(h, 16));
234
+ }
235
+ get length() {
236
+ return this.b.length;
237
+ }
238
+ word(at) {
239
+ if (at < 0 || at + 32 > this.b.length) throw new Error("out of bounds");
240
+ let v = 0n;
241
+ for (let i = at; i < at + 32; i++) v = v << 8n | BigInt(this.b[i]);
242
+ return v;
243
+ }
244
+ /** A word that must fit a JS offset / length (bounded by the calldata). */
245
+ small(at) {
246
+ const v = this.word(at);
247
+ if (v > BigInt(this.b.length)) throw new Error("offset out of bounds");
248
+ return Number(v.toString());
249
+ }
250
+ address(at) {
251
+ const v = this.word(at);
252
+ if (v >> 160n !== 0n) throw new Error("not an address");
253
+ return `0x${v.toString(16).padStart(40, "0")}`;
254
+ }
255
+ bool(at) {
256
+ const v = this.word(at);
257
+ if (v > 1n) throw new Error("not a bool");
258
+ return v === 1n;
259
+ }
260
+ };
261
+ var same = (a, b) => a.toLowerCase() === b.toLowerCase();
262
+ function swapAt(r, at) {
263
+ r.address(at);
264
+ r.address(at + 32);
265
+ const step = { sendingAssetId: r.address(at + 64), receivingAssetId: r.address(at + 96), fromAmount: r.word(at + 128) };
266
+ const bytesAt = at + r.small(at + 160);
267
+ r.small(bytesAt);
268
+ r.bool(at + 192);
269
+ return step;
270
+ }
271
+ function swapsAt(r, base, head) {
272
+ const array = base + r.small(head);
273
+ const n = r.small(array);
274
+ if (n === 0 || n > 16) throw new Error("bad swap count");
275
+ const items = array + 32;
276
+ return Array.from({ length: n }, (_, i) => swapAt(r, items + r.small(items + 32 * i)));
277
+ }
278
+ function checkSteps(steps, e, finalAsset) {
279
+ const first = steps[0];
280
+ const last = steps[steps.length - 1];
281
+ if (!same(first.sendingAssetId, e.fromToken)) return `spends ${first.sendingAssetId}, not ${e.fromToken}`;
282
+ if (first.fromAmount === 0n || first.fromAmount > e.amountIn) return `swaps ${first.fromAmount}, not the ${e.amountIn} spent`;
283
+ if (!same(last.receivingAssetId, finalAsset)) return `delivers ${last.receivingAssetId}, not ${finalAsset}`;
284
+ return null;
285
+ }
286
+ function diamondCallMismatch(data, e) {
287
+ let r;
288
+ try {
289
+ r = new Reader(data);
290
+ } catch {
291
+ return "the calldata is not hex";
292
+ }
293
+ if (r.length < 4 + 32 * 2) return "the calldata is too short";
294
+ const selector = data.slice(0, 10).toLowerCase();
295
+ const generic = GENERIC_SWAPS[selector];
296
+ try {
297
+ if (generic) {
298
+ if (!e.sameChain) return "a same-chain swap call on a cross-chain route";
299
+ const receiver2 = r.address(4 + 96);
300
+ const minAmount2 = r.word(4 + 128);
301
+ const steps = generic.single ? [swapAt(r, 4 + r.small(4 + 160))] : swapsAt(r, 4, 4 + 160);
302
+ if (!e.evmDestination || !same(receiver2, e.receiver)) return `pays ${receiver2}, not ${e.receiver}`;
303
+ if (minAmount2 < e.minOut) return `accepts ${minAmount2}, below the ${e.minOut} shown`;
304
+ return checkSteps(steps, e, e.toToken);
305
+ }
306
+ const bridge = BRIDGE_CALLS[selector];
307
+ if (!bridge) return `unknown Diamond call ${selector}`;
308
+ const head = r.small(4);
309
+ if (head < (bridge.sourceSwaps ? 96 : 64) || head % 32 !== 0 || head > 32 * 32) return `unknown Diamond call ${selector}`;
310
+ const at = 4 + head;
311
+ if (r.small(at + 32) !== 320) return `unknown Diamond call ${selector}`;
312
+ const sendingAssetId = r.address(at + 128);
313
+ const receiver = r.address(at + 160);
314
+ const minAmount = r.word(at + 192);
315
+ const destinationChainId = r.word(at + 224);
316
+ const hasSourceSwaps = r.bool(at + 256);
317
+ const hasDestinationCall = r.bool(at + 288);
318
+ if (e.sameChain) return "a bridge call on a same-chain route";
319
+ if (destinationChainId !== e.destChainId) return `bridges to chain ${destinationChainId}, not ${e.destChainId}`;
320
+ if (hasDestinationCall) return "the route runs a call on the destination chain, whose recipient cannot be checked here";
321
+ if (!e.evmDestination) return "the recipient on a non-EVM destination sits in bridge-specific data and cannot be checked here";
322
+ if (!same(receiver, e.receiver)) return `pays ${receiver}, not ${e.receiver}`;
323
+ if (hasSourceSwaps !== bridge.sourceSwaps) return `unknown Diamond call ${selector}`;
324
+ if (hasSourceSwaps) return checkSteps(swapsAt(r, 4, 4 + 32), e, sendingAssetId);
325
+ if (minAmount === 0n || minAmount > e.amountIn) return `bridges ${minAmount}, not the ${e.amountIn} spent`;
326
+ return same(sendingAssetId, e.fromToken) ? null : `bridges ${sendingAssetId}, not ${e.fromToken}`;
327
+ } catch (err) {
328
+ return `the Diamond calldata does not decode (${err instanceof Error ? err.message : String(err)})`;
329
+ }
330
+ }
331
+
82
332
  // src/provider.ts
83
- var import_swap2_assets = require("@anywalletkit/swap2-assets");
84
333
  var import_util2 = require("@anywalletkit/protocol-swap2-core/util");
85
334
  var import_core = require("@anywalletkit/core");
86
335
  var DEFAULT_LIFI_BASE = "https://li.quest/v1";
@@ -205,8 +454,9 @@ function trustMap(input, label) {
205
454
  }
206
455
  return out;
207
456
  }
208
- function lifiToken(sourceChain, symbol) {
209
- const asset = (0, import_swap2_assets.ccAsset)(sourceChain, symbol);
457
+ function lifiToken(params) {
458
+ const sourceChain = params.sourceChain;
459
+ const asset = (0, import_util2.sourceAssetOf)(params);
210
460
  if (!asset) return null;
211
461
  if (asset.contract) return asset.contract;
212
462
  return isSolana(sourceChain) ? SOLANA_NATIVE : EVM_ZERO;
@@ -288,10 +538,14 @@ function lifiProvider(config = {}) {
288
538
  const fromChain = LIFI_CHAIN[params.sourceChain];
289
539
  const toChain = LIFI_CHAIN[params.destChain];
290
540
  if (!fromChain || !toChain) return null;
291
- const fromToken = lifiToken(params.sourceChain, params.sourceToken);
541
+ const fromToken = lifiToken(params);
292
542
  if (!fromToken) return null;
293
543
  const solanaSource = isSolana(params.sourceChain);
294
- const fromAddress = params.refundAddress ?? (solanaSource ? SOLANA_PLACEHOLDER : EVM_PLACEHOLDER);
544
+ const dry = params.dry !== false;
545
+ const fromAddress = params.refundAddress || (dry ? solanaSource ? SOLANA_PLACEHOLDER : EVM_PLACEHOLDER : "");
546
+ const toAddress = params.destAddress || (dry ? fromAddress : "");
547
+ if (!fromAddress) return reject("a live quote needs the sender's address");
548
+ if (!toAddress) return reject("a live quote needs a recipient");
295
549
  const slippageBps = params.slippageBps ?? defaultSlippageBps;
296
550
  let sentSlippage = slippageBps / 1e4;
297
551
  let deadline;
@@ -308,7 +562,7 @@ function lifiProvider(config = {}) {
308
562
  toTokenAddress: nativeFor(params.destChain, params.destToken),
309
563
  fromAmount: params.amountIn,
310
564
  fromAddress,
311
- toAddress: params.destAddress || fromAddress,
565
+ toAddress,
312
566
  options: { slippage: slippageBps / 1e4, ...integrator().trim() ? { integrator: integrator().trim() } : {} }
313
567
  }),
314
568
  signal: signal ?? (0, import_util2.timeoutSignal)(8e3)
@@ -328,7 +582,6 @@ function lifiProvider(config = {}) {
328
582
  url.searchParams.set("toToken", nativeFor(params.destChain, params.destToken));
329
583
  url.searchParams.set("fromAmount", params.amountIn);
330
584
  url.searchParams.set("fromAddress", fromAddress);
331
- const toAddress = params.destAddress || fromAddress;
332
585
  url.searchParams.set("toAddress", toAddress);
333
586
  const integratorId = integrator().trim();
334
587
  if (integratorId) url.searchParams.set("integrator", integratorId);
@@ -440,6 +693,19 @@ function lifiProvider(config = {}) {
440
693
  params.onRefusal?.(`lifi: same-chain route settles asynchronously (${duration} s via ${q.tool ?? "unknown tool"}) \u2014 a same-chain swap must deliver in the same transaction`);
441
694
  return null;
442
695
  }
696
+ if (!solanaSource) {
697
+ const why = diamondCallMismatch(tr.data, {
698
+ fromToken,
699
+ toToken: expectedToToken,
700
+ amountIn: BigInt(params.amountIn),
701
+ receiver: toAddress,
702
+ minOut: BigInt(minDestAmount),
703
+ destChainId: BigInt(toChain),
704
+ sameChain: fromChain === toChain,
705
+ evmDestination: !isSolana(params.destChain)
706
+ });
707
+ if (why) return reject(`the transaction does not match the quote: ${why}`);
708
+ }
443
709
  return {
444
710
  provider: "lifi",
445
711
  // Status is tracked by SOURCE TX HASH + the chain pair.