@avail-project/ca-common 1.0.1 → 2.0.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.
@@ -6,11 +6,13 @@ const axios_1 = tslib_1.__importStar(require("axios"));
6
6
  const viem_1 = require("viem");
7
7
  const iface_1 = require("./iface");
8
8
  const definition_1 = require("../proto/definition");
9
+ const decimal_js_1 = tslib_1.__importDefault(require("decimal.js"));
9
10
  class LiFiAggregator {
10
11
  static BASE_URL_V1 = "https://li.quest/v1";
11
12
  static COMMON_OPTIONS = {
12
13
  denyExchanges: "openocean",
13
14
  slippage: "0.01",
15
+ skipSimulation: true,
14
16
  };
15
17
  axios;
16
18
  constructor(apiKey) {
@@ -19,6 +21,7 @@ class LiFiAggregator {
19
21
  headers: {
20
22
  "x-lifi-api-key": apiKey,
21
23
  },
24
+ timeout: 10_000,
22
25
  });
23
26
  }
24
27
  async getQuotes(requests) {
@@ -80,12 +83,38 @@ class LiFiAggregator {
80
83
  }
81
84
  throw e;
82
85
  }
86
+ const { estimate, transactionRequest: { to, value, data }, action: { fromToken, toToken }, } = resp.data;
87
+ const inputAmountInDecimal = new decimal_js_1.default(estimate.fromAmount)
88
+ .div(decimal_js_1.default.pow(10, fromToken.decimals))
89
+ .toFixed(fromToken.decimals);
90
+ const outputAmountInDecimal = new decimal_js_1.default(estimate.toAmountMin)
91
+ .div(decimal_js_1.default.pow(10, toToken.decimals))
92
+ .toFixed(toToken.decimals);
83
93
  return {
84
- type: r.type,
85
- inputAmount: BigInt(resp.data.estimate.fromAmount),
86
- outputAmountMinimum: BigInt(resp.data.estimate.toAmountMin),
87
- outputAmountLikely: BigInt(resp.data.estimate.toAmount),
88
- originalResponse: resp.data,
94
+ input: {
95
+ amount: inputAmountInDecimal,
96
+ amountRaw: BigInt(estimate.fromAmount),
97
+ contractAddress: inputTokenAddr,
98
+ decimals: fromToken.decimals,
99
+ value: decimal_js_1.default.mul(inputAmountInDecimal, fromToken.priceUSD).toNumber(),
100
+ symbol: fromToken.symbol,
101
+ },
102
+ output: {
103
+ amount: outputAmountInDecimal,
104
+ amountRaw: BigInt(estimate.toAmountMin),
105
+ contractAddress: outputTokenAddr,
106
+ decimals: toToken.decimals,
107
+ value: decimal_js_1.default.mul(outputAmountInDecimal, toToken.priceUSD).toNumber(),
108
+ symbol: toToken.symbol,
109
+ },
110
+ txData: {
111
+ approvalAddress: estimate.approvalAddress,
112
+ tx: {
113
+ to,
114
+ value,
115
+ data,
116
+ },
117
+ },
89
118
  };
90
119
  }));
91
120
  return list.map((item) => {
@@ -1,5 +1,5 @@
1
- import { groupBy, orderBy } from "es-toolkit";
2
- import { bytesToBigInt, bytesToHex } from "viem";
1
+ import { groupBy } from "es-toolkit";
2
+ import { bytesToHex } from "viem";
3
3
  import Decimal from "decimal.js";
4
4
  import { QuoteSeriousness, QuoteType, } from "./iface";
5
5
  import { ChaindataMap, convertBigIntToDecimal, convertDecimalToBigInt, CurrencyID, maxByBigInt, minByBigInt, } from "../data";
@@ -25,7 +25,7 @@ export async function aggregateAggregators(requests, aggregators, mode) {
25
25
  switch (mode) {
26
26
  case 0 /* AggregateAggregatorsMode.MaximizeOutput */: {
27
27
  for (let i = 0; i < requests.length; i++) {
28
- const best = maxByBigInt(responses.map((ra) => ({ quote: ra.quotes[i], aggregator: ra.agg })), (r) => r.quote?.outputAmountMinimum ?? 0n);
28
+ const best = maxByBigInt(responses.map((ra) => ({ quote: ra.quotes[i], aggregator: ra.agg })), (r) => r.quote?.output.amountRaw ?? 0n);
29
29
  if (best != null) {
30
30
  final[i] = best;
31
31
  }
@@ -40,7 +40,9 @@ export async function aggregateAggregators(requests, aggregators, mode) {
40
40
  }
41
41
  case 1 /* AggregateAggregatorsMode.MinimizeInput */: {
42
42
  for (let i = 0; i < requests.length; i++) {
43
- const best = minByBigInt(responses.map((ra) => ({ quote: ra.quotes[i], aggregator: ra.agg })), (r) => r.quote?.inputAmount ?? 0n);
43
+ const best = minByBigInt(responses.map((ra) => ({ quote: ra.quotes[i], aggregator: ra.agg })),
44
+ // Default null quotes to MAX so they never win as the minimum
45
+ (r) => r.quote?.input.amountRaw ?? BigInt(Number.MAX_SAFE_INTEGER));
44
46
  if (best != null) {
45
47
  final[i] = best;
46
48
  }
@@ -98,7 +100,7 @@ cots = [(1 COT, 1), (1 COT, 4)]
98
100
  */
99
101
  export async function autoSelectSourcesV2(userAddress, holdings, outputRequired, aggregators, commonCurrencyID = CurrencyID.USDC) {
100
102
  // Assumption: Holding is already sorted in usage priority
101
- console.log("XCS | SSV2:", {
103
+ console.debug("XCS | SSV2:", {
102
104
  holdings,
103
105
  outputRequired: outputRequired.toFixed(),
104
106
  });
@@ -111,14 +113,14 @@ export async function autoSelectSourcesV2(userAddress, holdings, outputRequired,
111
113
  }
112
114
  const correspondingCurrency = chain.Currencies.find((cur) => cur.currencyID === commonCurrencyID);
113
115
  if (correspondingCurrency == null) {
114
- console.log("XCS | SS | Skipping because correspondingCurrency is null", {
116
+ console.debug("XCS | SS | Skipping because correspondingCurrency is null", {
115
117
  chain,
116
118
  correspondingCurrency,
117
119
  });
118
120
  continue;
119
121
  }
120
122
  if (Buffer.compare(holding.tokenAddress, correspondingCurrency.tokenAddress) === 0) {
121
- const normalizedAmount = new Decimal(holding.amount).div(Decimal.pow(10, correspondingCurrency.decimals));
123
+ const normalizedAmount = new Decimal(holding.amountRaw).div(Decimal.pow(10, correspondingCurrency.decimals));
122
124
  cotList.push({
123
125
  amount: normalizedAmount,
124
126
  idx,
@@ -134,7 +136,7 @@ export async function autoSelectSourcesV2(userAddress, holdings, outputRequired,
134
136
  type: QuoteType.EXACT_IN,
135
137
  chain: chain.ChainID,
136
138
  inputToken: holding.tokenAddress,
137
- inputAmount: holding.amount,
139
+ inputAmount: holding.amountRaw,
138
140
  outputToken: correspondingCurrency.tokenAddress,
139
141
  seriousness: QuoteSeriousness.PRICE_SURVEY,
140
142
  },
@@ -172,10 +174,10 @@ export async function autoSelectSourcesV2(userAddress, holdings, outputRequired,
172
174
  if (remainder.lte(0))
173
175
  break;
174
176
  }
175
- console.log("XCS | SS | Early return with continuous COTs:", {
177
+ console.debug("XCS | SS | Early return with continuous COTs:", {
176
178
  cots: usedCOTs,
177
179
  });
178
- return { quotes: [], usedCOTs };
180
+ return { quoteResponses: [], usedCOTs };
179
181
  }
180
182
  }
181
183
  }
@@ -200,7 +202,7 @@ export async function autoSelectSourcesV2(userAddress, holdings, outputRequired,
200
202
  // Sort by original index to maintain priority
201
203
  processingQueue.sort((a, b) => a.idx - b.idx);
202
204
  const responses = await aggregateAggregators(fullLiquidationQuotes.map((fq) => fq.req), aggregators, 0 /* AggregateAggregatorsMode.MaximizeOutput */);
203
- console.log("XCS | SS | Responses:", responses);
205
+ console.debug("AutoSelectSources:Quotes", responses);
204
206
  const final = [];
205
207
  const usedCOTs = [];
206
208
  let remainder = outputRequired;
@@ -219,7 +221,7 @@ export async function autoSelectSourcesV2(userAddress, holdings, outputRequired,
219
221
  cur: cotData.currency,
220
222
  });
221
223
  remainder = remainder.minus(amountToUse);
222
- console.log("XCS | SS | Used COT", {
224
+ console.debug("selection:cot", {
223
225
  idx: cotData.idx,
224
226
  amountToUse: amountToUse.toFixed(),
225
227
  remainder: remainder.toFixed(),
@@ -228,30 +230,32 @@ export async function autoSelectSourcesV2(userAddress, holdings, outputRequired,
228
230
  else {
229
231
  // Process non-COT holding - use existing quote logic
230
232
  const { quoteData, responseIdx } = item;
231
- const { quote: resp, aggregator: agg } = responses[responseIdx];
233
+ const { quote: resp, aggregator } = responses[responseIdx];
232
234
  if (resp == null) {
233
235
  continue;
234
236
  }
235
- console.log("XCS | SS | 1", {
236
- i: responseIdx,
237
- idx: quoteData.idx,
238
- remainder,
239
- q: quoteData,
240
- resp,
241
- agg,
237
+ console.debug("selection:quote", {
238
+ remainder: remainder.toFixed(),
239
+ input: resp.input,
240
+ output: resp.output,
242
241
  });
243
242
  const divisor = Decimal.pow(10, quoteData.cur.decimals);
244
- const oamD = Decimal.div(resp.outputAmountMinimum, divisor);
243
+ const oamD = new Decimal(resp.output.amount);
245
244
  if (oamD.gt(remainder)) {
246
- const indicativePrice = Decimal.div(resp.inputAmount, resp.outputAmountMinimum);
247
- const userBal = new Decimal(quoteData.originalHolding.amount);
245
+ const indicativePrice = Decimal.div(resp.input.amountRaw, resp.output.amountRaw);
246
+ const userBal = new Decimal(quoteData.originalHolding.amountRaw);
248
247
  // remainder is the output we want, so the input amount is remainder × indicativePrice
249
248
  let expectedInput = Decimal.min(remainder.mul(divisor).mul(indicativePrice).mul(safetyMultiplier), userBal);
249
+ let attempts = 0;
250
250
  while (true) {
251
- console.log("XCS | SS | 2⒜", {
252
- indicativePrice,
253
- expectedInput,
254
- userBal,
251
+ if (++attempts > 10) {
252
+ throw new AutoSelectionError("Partial quote did not converge");
253
+ }
254
+ console.debug("partial_quote_loop", {
255
+ indicativePrice: indicativePrice.toFixed(),
256
+ expectedInput: expectedInput.toFixed(),
257
+ userBal: userBal.toFixed(),
258
+ remainder: remainder.toFixed(),
255
259
  });
256
260
  const adequateQuoteResult = await aggregateAggregators([
257
261
  {
@@ -261,21 +265,23 @@ export async function autoSelectSourcesV2(userAddress, holdings, outputRequired,
261
265
  },
262
266
  ], aggregators, 0 /* AggregateAggregatorsMode.MaximizeOutput */);
263
267
  if (adequateQuoteResult.length !== 1) {
264
- throw new AutoSelectionError("???");
268
+ throw new AutoSelectionError("Unexpected response length from aggregateAggregators");
265
269
  }
266
270
  const adequateQuote = adequateQuoteResult[0];
267
271
  if (adequateQuote.quote == null) {
268
272
  throw new AutoSelectionError("Couldn't get buy quote");
269
273
  }
270
- console.log("XCS | SS | 2⒜⑴", {
271
- adequateQuote,
274
+ const quote = adequateQuote.quote;
275
+ console.log("partial_quote", {
276
+ quote,
272
277
  });
273
- const oam2D = Decimal.div(adequateQuote.quote.outputAmountMinimum, divisor);
278
+ const oam2D = new Decimal(adequateQuote.quote.output.amount);
274
279
  if (oam2D.gte(remainder)) {
275
280
  final.push({
276
- ...quoteData,
277
- quote: adequateQuote.quote,
278
- agg: adequateQuote.aggregator,
281
+ quote,
282
+ aggregator: adequateQuote.aggregator,
283
+ holding: quoteData.originalHolding,
284
+ chainID: Number(quoteData.req.chain.chainID),
279
285
  });
280
286
  remainder = remainder.minus(oam2D);
281
287
  break;
@@ -289,168 +295,29 @@ export async function autoSelectSourcesV2(userAddress, holdings, outputRequired,
289
295
  }
290
296
  }
291
297
  else {
292
- console.log("XCS | SS | 2⒝", resp);
298
+ console.debug("full_quote", resp);
293
299
  final.push({
294
- ...quoteData,
295
300
  quote: resp,
296
- agg,
297
- });
298
- remainder = remainder.minus(convertBigIntToDecimal(resp.outputAmountMinimum).div(divisor));
299
- }
300
- }
301
- }
302
- console.log("XCS | SS | 3⒜", {
303
- remainder,
304
- final,
305
- });
306
- if (remainder.gt(0)) {
307
- throw new AutoSelectionError("Failed to accumulate enough swaps to meet requirement");
308
- }
309
- console.log("XCS | SS | Final:", { quotes: final, cots: usedCOTs });
310
- return { quotes: final, usedCOTs };
311
- }
312
- export async function autoSelectSources(userAddress, holdings, outputRequired, aggregators, collectionFees, commonCurrencyID = CurrencyID.USDC) {
313
- console.log("XCS | SS | Holdings:", holdings);
314
- const groupedByChainID = groupBy(holdings, (h) => bytesToHex(h.chainID.toBytes()));
315
- const fullLiquidationQuotes = [];
316
- for (const holdings of Object.values(groupedByChainID)) {
317
- const chain = ChaindataMap.get(holdings[0].chainID);
318
- if (chain == null) {
319
- throw new AutoSelectionError("Chain not found");
320
- }
321
- const correspondingCurrency = chain.Currencies.find((cur) => cur.currencyID === commonCurrencyID);
322
- if (correspondingCurrency == null) {
323
- console.log("XCS | SS | Skipping because correspondingCurrency is null", {
324
- chain,
325
- correspondingCurrency,
326
- });
327
- continue;
328
- }
329
- const cfeeTuple = collectionFees.find((cf) => {
330
- return (cf.universe === chain.Universe &&
331
- Buffer.compare(cf.chainID, chain.ChainID32) === 0 &&
332
- // output token is the CA one
333
- Buffer.compare(cf.tokenAddress, correspondingCurrency.tokenAddress) ===
334
- 0);
335
- });
336
- const cfee = cfeeTuple != null ? bytesToBigInt(cfeeTuple.fee) : 0n;
337
- for (const holding of holdings) {
338
- if (Buffer.compare(holding.tokenAddress, correspondingCurrency.tokenAddress) === 0) {
339
- console.log("XCS | SS | Disqualifying", holding, "because holding.tokenAddress = CA asset");
340
- continue;
341
- }
342
- fullLiquidationQuotes.push({
343
- req: {
344
- userAddress,
345
- type: QuoteType.EXACT_IN,
346
- chain: chain.ChainID,
347
- inputToken: holding.tokenAddress,
348
- inputAmount: holding.amount,
349
- outputToken: correspondingCurrency.tokenAddress,
350
- seriousness: QuoteSeriousness.PRICE_SURVEY,
351
- },
352
- // necessary for various purposes
353
- cfee,
354
- originalHolding: holding,
355
- cur: correspondingCurrency,
356
- });
357
- }
358
- }
359
- // const groupedByChainID = groupBy(quoteOutputs, h => h.chainIDHex)
360
- const quotesByValue = orderBy(fullLiquidationQuotes, [
361
- (quoteOut) => quoteOut.cfee,
362
- (quoteOut) => quoteOut.originalHolding.value, // once optimized for collections, we select the biggest asset we hold
363
- ], ["asc", "desc"]);
364
- const responses = await aggregateAggregators(quotesByValue.map((fq) => fq.req), aggregators, 0 /* AggregateAggregatorsMode.MaximizeOutput */);
365
- console.log("XCS | SS | Responses:", responses);
366
- const final = [];
367
- let remainder = outputRequired; // assuming all that chains have the same amount of fixed point places
368
- for (let i = 0; i < quotesByValue.length; i++) {
369
- if (remainder.lte(0)) {
370
- break;
371
- }
372
- const q = quotesByValue[i];
373
- const { quote: resp, aggregator: agg } = responses[i];
374
- if (resp == null) {
375
- continue;
376
- }
377
- console.log("XCS | SS | 1", {
378
- i,
379
- remainder,
380
- q,
381
- resp,
382
- agg,
383
- });
384
- const divisor = Decimal.pow(10, q.cur.decimals);
385
- const oamD = convertBigIntToDecimal(resp.outputAmountMinimum).div(divisor);
386
- if (oamD.gt(remainder)) {
387
- const indicativePrice = convertBigIntToDecimal(resp.inputAmount).div(convertBigIntToDecimal(resp.outputAmountMinimum));
388
- const userBal = convertBigIntToDecimal(q.originalHolding.amount);
389
- // remainder is the output we want, so the input amount is remainder × indicativePrice
390
- let expectedInput = Decimal.min(remainder.mul(divisor).mul(indicativePrice).mul(safetyMultiplier), userBal);
391
- while (true) {
392
- console.log("XCS | SS | 2⒜", {
393
- indicativePrice,
394
- expectedInput,
395
- userBal,
301
+ holding: quoteData.originalHolding,
302
+ aggregator,
303
+ chainID: Number(quoteData.req.chain.chainID),
396
304
  });
397
- const adequateQuoteResult = await aggregateAggregators([
398
- {
399
- ...q.req,
400
- seriousness: QuoteSeriousness.SERIOUS,
401
- inputAmount: convertDecimalToBigInt(expectedInput),
402
- },
403
- ], aggregators, 0 /* AggregateAggregatorsMode.MaximizeOutput */);
404
- if (adequateQuoteResult.length !== 1) {
405
- throw new AutoSelectionError("???");
406
- }
407
- const adequateQuote = adequateQuoteResult[0];
408
- if (adequateQuote.quote == null) {
409
- throw new AutoSelectionError("Couldn't get buy quote");
410
- }
411
- console.log("XCS | SS | 2⒜⑴", {
412
- adequateQuote,
413
- });
414
- const oam2D = convertBigIntToDecimal(adequateQuote.quote.outputAmountMinimum).div(divisor);
415
- if (oam2D.gte(remainder)) {
416
- final.push({
417
- ...q,
418
- quote: adequateQuote.quote,
419
- agg: adequateQuote.aggregator,
420
- });
421
- remainder = remainder.minus(oam2D);
422
- break;
423
- }
424
- else if (expectedInput.eq(userBal)) {
425
- throw new AutoSelectionError("Holding was supposedly enough to meet the full requirement but ceased to be so subsequently");
426
- }
427
- else {
428
- expectedInput = Decimal.min(expectedInput.mul(safetyMultiplier), userBal); // try again with higher amount
429
- }
305
+ remainder = remainder.minus(resp.output.amount);
430
306
  }
431
307
  }
432
- else {
433
- console.log("XCS | SS | 2⒝", resp);
434
- final.push({
435
- ...q,
436
- quote: resp,
437
- agg,
438
- });
439
- remainder = remainder.minus(convertBigIntToDecimal(resp.outputAmountMinimum).div(divisor));
440
- }
441
308
  }
442
- console.log("XCS | SS | 3⒜", {
443
- remainder,
309
+ console.debug("quotes_and_remainder", {
310
+ remainder: remainder.toFixed(),
444
311
  final,
445
312
  });
446
313
  if (remainder.gt(0)) {
447
- throw new AutoSelectionError("Failed to accumulate enough swaps to meet requirement");
314
+ throw new AutoSelectionError("NOT_ENOUGH_SWAP_FOR_REQUIREMENT");
448
315
  }
449
- console.log("XCS | SS | Final:", final);
450
- return final;
316
+ console.log("final_quotes", { quotes: final, cots: usedCOTs });
317
+ return { quoteResponses: final, usedCOTs };
451
318
  }
452
- export async function determineDestinationSwaps(userAddress, chainID, requirement, aggregators, commonCurrencyID = CurrencyID.USDC) {
453
- const chaindata = ChaindataMap.get(chainID);
319
+ export async function determineDestinationSwaps(userAddress, requirement, aggregators, commonCurrencyID = CurrencyID.USDC) {
320
+ const chaindata = ChaindataMap.get(requirement.chainID);
454
321
  if (chaindata == null) {
455
322
  throw new AutoSelectionError("Chain not found");
456
323
  }
@@ -462,33 +329,32 @@ export async function determineDestinationSwaps(userAddress, chainID, requiremen
462
329
  // what happens if we happen to sell the requirement for the COT, what would the amount be?
463
330
  const fullLiquidationQR = {
464
331
  type: QuoteType.EXACT_IN,
465
- chain: chainID,
332
+ chain: requirement.chainID,
466
333
  userAddress,
467
334
  inputToken: requirement.tokenAddress,
468
335
  outputToken: COT.tokenAddress,
469
- inputAmount: requirement.amount,
336
+ inputAmount: requirement.amountRaw,
470
337
  seriousness: QuoteSeriousness.PRICE_SURVEY,
471
338
  };
472
339
  const fullLiquidationResult = await aggregateAggregators([fullLiquidationQR], aggregators, 0 /* AggregateAggregatorsMode.MaximizeOutput */);
473
340
  if (fullLiquidationResult.length !== 1) {
474
- throw new AutoSelectionError("???");
341
+ throw new AutoSelectionError("Unexpected response length from aggregateAggregators");
475
342
  }
476
343
  const fullLiquidationQuote = fullLiquidationResult[0];
477
344
  if (fullLiquidationQuote.quote == null) {
478
345
  throw new AutoSelectionError("Couldn't get full liquidation quote");
479
346
  }
480
- let curAmount = convertBigIntToDecimal(fullLiquidationQuote.quote.outputAmountLikely).mul(safetyMultiplier);
481
- console.log("XCS | DDS | 1⒜", {
482
- fullLiquidationQR,
483
- fullLiquidationResult,
484
- COT,
485
- });
347
+ let curAmount = convertBigIntToDecimal(fullLiquidationQuote.quote.output.amountRaw).mul(safetyMultiplier);
348
+ let attempts = 0;
486
349
  while (true) {
350
+ if (++attempts > 10) {
351
+ throw new AutoSelectionError("Destination swap quote did not converge");
352
+ }
487
353
  const buyQuoteResult = await aggregateAggregators([
488
354
  {
489
355
  type: QuoteType.EXACT_IN,
490
356
  userAddress,
491
- chain: chainID,
357
+ chain: requirement.chainID,
492
358
  inputToken: COT.tokenAddress,
493
359
  outputToken: requirement.tokenAddress,
494
360
  inputAmount: convertDecimalToBigInt(curAmount),
@@ -496,21 +362,22 @@ export async function determineDestinationSwaps(userAddress, chainID, requiremen
496
362
  },
497
363
  ], aggregators, 0 /* AggregateAggregatorsMode.MaximizeOutput */);
498
364
  if (buyQuoteResult.length !== 1) {
499
- throw new AutoSelectionError("???");
365
+ throw new AutoSelectionError("Unexpected response length from aggregateAggregators");
500
366
  }
501
367
  const buyQuote = buyQuoteResult[0];
502
368
  if (buyQuote.quote == null) {
503
369
  throw new AutoSelectionError("Couldn't get buy quote");
504
370
  }
505
- console.log("XCS | DDS | 2⒜ iteration", {
371
+ console.debug("XCS | DDS | 2⒜ iteration", {
506
372
  buyQuote,
507
373
  curAmount,
508
374
  });
509
- if (buyQuote.quote.outputAmountMinimum >= requirement.amount) {
375
+ if (buyQuote.quote.output.amountRaw >= requirement.amountRaw) {
510
376
  return {
511
- ...buyQuote,
512
- inputAmount: convertBigIntToDecimal(buyQuote.quote.inputAmount).div(Decimal.pow(10, COT.decimals)),
513
- outputAmount: requirement.amount,
377
+ chainID: Number(requirement.chainID.chainID),
378
+ quote: buyQuote.quote,
379
+ aggregator: buyQuote.aggregator,
380
+ holding: requirement,
514
381
  };
515
382
  }
516
383
  else {
@@ -518,8 +385,8 @@ export async function determineDestinationSwaps(userAddress, chainID, requiremen
518
385
  }
519
386
  }
520
387
  }
521
- export async function liquidateInputHoldings(userAddress, holdings, aggregators, collectionFees, commonCurrencyID = CurrencyID.USDC) {
522
- console.log("XCS | LIH | Holdings:", holdings);
388
+ export async function liquidateInputHoldings(userAddress, holdings, aggregators, commonCurrencyID = CurrencyID.USDC) {
389
+ console.debug("XCS | LIH | Holdings:", holdings);
523
390
  const groupedByChainID = groupBy(holdings, (h) => bytesToHex(h.chainID.toBytes()));
524
391
  const fullLiquidationQuotes = [];
525
392
  for (const holdings of Object.values(groupedByChainID)) {
@@ -529,20 +396,12 @@ export async function liquidateInputHoldings(userAddress, holdings, aggregators,
529
396
  }
530
397
  const correspondingCurrency = chain.Currencies.find((cur) => cur.currencyID === commonCurrencyID);
531
398
  if (correspondingCurrency == null) {
532
- console.log("XCS | LIH | Skipping because correspondingCurrency is null", {
399
+ console.debug("XCS | LIH | Skipping because correspondingCurrency is null", {
533
400
  chain,
534
401
  correspondingCurrency,
535
402
  });
536
403
  continue;
537
404
  }
538
- const cfeeTuple = collectionFees.find((cf) => {
539
- return (cf.universe === chain.Universe &&
540
- Buffer.compare(cf.chainID, chain.ChainID32) === 0 &&
541
- // output token is the CA one
542
- Buffer.compare(cf.tokenAddress, correspondingCurrency.tokenAddress) ===
543
- 0);
544
- });
545
- const cfee = cfeeTuple != null ? bytesToBigInt(cfeeTuple.fee) : 0n;
546
405
  for (const holding of holdings) {
547
406
  if (Buffer.compare(holding.tokenAddress, correspondingCurrency.tokenAddress) === 0) {
548
407
  console.log("XCS | LIH | Disqualifying", holding, "because holding.tokenAddress = CA asset");
@@ -554,48 +413,44 @@ export async function liquidateInputHoldings(userAddress, holdings, aggregators,
554
413
  type: QuoteType.EXACT_IN,
555
414
  chain: chain.ChainID,
556
415
  inputToken: holding.tokenAddress,
557
- inputAmount: holding.amount,
416
+ inputAmount: holding.amountRaw,
558
417
  outputToken: correspondingCurrency.tokenAddress,
559
418
  seriousness: QuoteSeriousness.SERIOUS,
560
419
  },
561
420
  // necessary for various purposes
562
- cfee,
563
421
  originalHolding: holding,
564
422
  cur: correspondingCurrency,
565
423
  });
566
424
  }
567
425
  }
568
426
  const responses = await aggregateAggregators(fullLiquidationQuotes.map((fq) => fq.req), aggregators, 0 /* AggregateAggregatorsMode.MaximizeOutput */);
569
- console.log("XCS | LIH | Responses:", responses);
570
- const validResponses = responses
571
- .filter((r) => r.quote !== null)
572
- .map((r, i) => ({
573
- ...r,
574
- ...fullLiquidationQuotes[i],
575
- agg: r.aggregator,
576
- quote: r.quote,
577
- }));
578
- const total = validResponses.reduce((acc, r) => {
579
- return acc.add(new Decimal(r.quote.outputAmountMinimum ?? 0n).div(Decimal.pow(10, r.cur.decimals)));
580
- }, new Decimal(0));
581
- return {
582
- quotes: validResponses,
583
- total,
584
- };
427
+ console.debug("XCS | LIH | Responses:", responses);
428
+ const quotes = [];
429
+ for (const [i, response] of responses.entries()) {
430
+ if (response.quote !== null) {
431
+ quotes.push({
432
+ quote: response.quote,
433
+ aggregator: response.aggregator,
434
+ holding: fullLiquidationQuotes[i].originalHolding,
435
+ chainID: Number(fullLiquidationQuotes[i].req.chain.chainID),
436
+ });
437
+ }
438
+ }
439
+ return quotes;
585
440
  }
586
- export async function destinationSwapWithExactIn(userAddress, chainID, inputAmount, outputToken, aggregators, commonCurrencyID = CurrencyID.USDC) {
587
- const chaindata = ChaindataMap.get(chainID);
441
+ export async function destinationSwapWithExactIn(userAddress, omniChainID, inputAmount, outputToken, aggregators, inputCurrency = CurrencyID.USDC) {
442
+ const chaindata = ChaindataMap.get(omniChainID);
588
443
  if (chaindata == null) {
589
444
  throw new AutoSelectionError("Chain not found");
590
445
  }
591
- const COT = chaindata.Currencies.find((cur) => cur.currencyID === commonCurrencyID);
446
+ const COT = chaindata.Currencies.find((cur) => cur.currencyID === inputCurrency);
592
447
  if (COT == null) {
593
448
  throw new AutoSelectionError("COT not present on the destination chain");
594
449
  }
595
450
  const fullLiquidationResult = await aggregateAggregators([
596
451
  {
597
452
  type: QuoteType.EXACT_IN,
598
- chain: chainID,
453
+ chain: omniChainID,
599
454
  userAddress,
600
455
  inputToken: COT.tokenAddress,
601
456
  outputToken: outputToken,
@@ -604,15 +459,20 @@ export async function destinationSwapWithExactIn(userAddress, chainID, inputAmou
604
459
  },
605
460
  ], aggregators, 0 /* AggregateAggregatorsMode.MaximizeOutput */);
606
461
  if (fullLiquidationResult.length !== 1) {
607
- throw new AutoSelectionError("???");
462
+ throw new AutoSelectionError("Unexpected response length from aggregateAggregators");
608
463
  }
609
464
  const fullLiquidationQuote = fullLiquidationResult[0];
610
465
  if (fullLiquidationQuote.quote == null) {
611
466
  throw new AutoSelectionError("Couldn't get full liquidation quote");
612
467
  }
613
468
  return {
614
- ...fullLiquidationQuote,
615
- inputAmount: convertBigIntToDecimal(inputAmount).div(Decimal.pow(10, COT.decimals)),
616
- outputAmount: fullLiquidationQuote.quote.outputAmountMinimum,
469
+ chainID: Number(omniChainID.chainID),
470
+ quote: fullLiquidationQuote.quote,
471
+ aggregator: fullLiquidationQuote.aggregator,
472
+ holding: {
473
+ amountRaw: inputAmount,
474
+ chainID: omniChainID,
475
+ tokenAddress: COT.tokenAddress,
476
+ },
617
477
  };
618
478
  }