@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.
- package/dist/cjs/xcs/autochoice.js +96 -237
- package/dist/cjs/xcs/bebop-agg.js +34 -5
- package/dist/cjs/xcs/index.js +0 -1
- package/dist/cjs/xcs/lifi-agg.js +34 -5
- package/dist/esm/xcs/autochoice.js +98 -238
- package/dist/esm/xcs/bebop-agg.js +34 -5
- package/dist/esm/xcs/index.js +0 -1
- package/dist/esm/xcs/lifi-agg.js +34 -5
- package/dist/types/xcs/autochoice.d.ts +6 -52
- package/dist/types/xcs/bebop-agg.d.ts +2 -2
- package/dist/types/xcs/iface.d.ts +37 -5
- package/dist/types/xcs/index.d.ts +0 -1
- package/dist/types/xcs/lifi-agg.d.ts +18 -14
- package/package.json +1 -1
- package/dist/cjs/xcs/yieldyak-agg.js +0 -113
- package/dist/esm/xcs/yieldyak-agg.js +0 -109
- package/dist/types/xcs/yieldyak-agg.d.ts +0 -21
package/dist/cjs/xcs/lifi-agg.js
CHANGED
|
@@ -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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
|
2
|
-
import {
|
|
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?.
|
|
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 })),
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
177
|
+
console.debug("XCS | SS | Early return with continuous COTs:", {
|
|
176
178
|
cots: usedCOTs,
|
|
177
179
|
});
|
|
178
|
-
return {
|
|
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.
|
|
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.
|
|
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
|
|
233
|
+
const { quote: resp, aggregator } = responses[responseIdx];
|
|
232
234
|
if (resp == null) {
|
|
233
235
|
continue;
|
|
234
236
|
}
|
|
235
|
-
console.
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
|
243
|
+
const oamD = new Decimal(resp.output.amount);
|
|
245
244
|
if (oamD.gt(remainder)) {
|
|
246
|
-
const indicativePrice = Decimal.div(resp.
|
|
247
|
-
const userBal = new Decimal(quoteData.originalHolding.
|
|
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
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
-
|
|
271
|
-
|
|
274
|
+
const quote = adequateQuote.quote;
|
|
275
|
+
console.log("partial_quote", {
|
|
276
|
+
quote,
|
|
272
277
|
});
|
|
273
|
-
const oam2D = Decimal
|
|
278
|
+
const oam2D = new Decimal(adequateQuote.quote.output.amount);
|
|
274
279
|
if (oam2D.gte(remainder)) {
|
|
275
280
|
final.push({
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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.
|
|
298
|
+
console.debug("full_quote", resp);
|
|
293
299
|
final.push({
|
|
294
|
-
...quoteData,
|
|
295
300
|
quote: resp,
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
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.
|
|
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("
|
|
314
|
+
throw new AutoSelectionError("NOT_ENOUGH_SWAP_FOR_REQUIREMENT");
|
|
448
315
|
}
|
|
449
|
-
console.log("
|
|
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,
|
|
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.
|
|
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.
|
|
481
|
-
|
|
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.
|
|
371
|
+
console.debug("XCS | DDS | 2⒜ iteration", {
|
|
506
372
|
buyQuote,
|
|
507
373
|
curAmount,
|
|
508
374
|
});
|
|
509
|
-
if (buyQuote.quote.
|
|
375
|
+
if (buyQuote.quote.output.amountRaw >= requirement.amountRaw) {
|
|
510
376
|
return {
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
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,
|
|
522
|
-
console.
|
|
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.
|
|
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.
|
|
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.
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
.
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
}
|
|
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,
|
|
587
|
-
const chaindata = ChaindataMap.get(
|
|
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 ===
|
|
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:
|
|
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
|
-
|
|
615
|
-
|
|
616
|
-
|
|
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
|
}
|