@alcorexchange/alcor-swap-sdk 1.0.32 → 1.0.34

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.
@@ -7,6 +7,7 @@ import { Tick, TickConstructorArgs } from "./tick";
7
7
  import { TickDataProvider } from "./tickDataProvider";
8
8
  export interface PoolConstructorArgs {
9
9
  id: number;
10
+ active: boolean;
10
11
  tokenA: Token;
11
12
  tokenB: Token;
12
13
  fee: FeeAmount;
@@ -22,6 +23,7 @@ export interface PoolConstructorArgs {
22
23
  */
23
24
  export declare class Pool {
24
25
  readonly id: number;
26
+ readonly active: boolean;
25
27
  readonly tokenA: Token;
26
28
  readonly tokenB: Token;
27
29
  readonly fee: FeeAmount;
@@ -48,7 +50,7 @@ export declare class Pool {
48
50
  * @param tickCurrent The current tick of the pool
49
51
  * @param ticks The current state of the pool ticks or a data provider that can return tick data
50
52
  */
51
- constructor({ id, tokenA, tokenB, fee, sqrtPriceX64, liquidity, tickCurrent, ticks, feeGrowthGlobalAX64, feeGrowthGlobalBX64, }: PoolConstructorArgs);
53
+ constructor({ id, active, tokenA, tokenB, fee, sqrtPriceX64, liquidity, tickCurrent, ticks, feeGrowthGlobalAX64, feeGrowthGlobalBX64, }: PoolConstructorArgs);
52
54
  /**
53
55
  * Returns true if the token is either tokenA or tokenB
54
56
  * @param token The token to check
@@ -35,7 +35,7 @@ class Pool {
35
35
  * @param tickCurrent The current tick of the pool
36
36
  * @param ticks The current state of the pool ticks or a data provider that can return tick data
37
37
  */
38
- constructor({ id, tokenA, tokenB, fee, sqrtPriceX64, liquidity, tickCurrent, ticks = NO_TICK_DATA_PROVIDER_DEFAULT, feeGrowthGlobalAX64 = 0, feeGrowthGlobalBX64 = 0, }) {
38
+ constructor({ id, active, tokenA, tokenB, fee, sqrtPriceX64, liquidity, tickCurrent, ticks = NO_TICK_DATA_PROVIDER_DEFAULT, feeGrowthGlobalAX64 = 0, feeGrowthGlobalBX64 = 0, }) {
39
39
  (0, tiny_invariant_1.default)(Number.isInteger(fee) && fee < 1000000, "FEE");
40
40
  const tickCurrentSqrtRatioX64 = tickMath_1.TickMath.getSqrtRatioAtTick(tickCurrent);
41
41
  const nextTickSqrtRatioX64 = tickMath_1.TickMath.getSqrtRatioAtTick(tickCurrent + 1);
@@ -44,6 +44,7 @@ class Pool {
44
44
  // always create a copy of the list since we want the pool's tick list to be immutable
45
45
  this.id = id;
46
46
  this.fee = fee;
47
+ this.active = active;
47
48
  this.sqrtPriceX64 = jsbi_1.default.BigInt(sqrtPriceX64);
48
49
  this.liquidity = jsbi_1.default.BigInt(liquidity);
49
50
  this.tickCurrent = tickCurrent;
@@ -211,6 +212,7 @@ class Pool {
211
212
  id: pool.id,
212
213
  tokenA: token_1.Token.toJSON(pool.tokenA),
213
214
  tokenB: token_1.Token.toJSON(pool.tokenB),
215
+ active: pool.active,
214
216
  fee: pool.fee,
215
217
  sqrtPriceX64: pool.sqrtPriceX64.toString(),
216
218
  liquidity: pool.liquidity.toString(),
@@ -224,6 +226,7 @@ class Pool {
224
226
  static fromJSON(json) {
225
227
  return new Pool({
226
228
  id: json.id,
229
+ active: json.active,
227
230
  tokenA: token_1.Token.fromJSON(json.tokenA),
228
231
  tokenB: token_1.Token.fromJSON(json.tokenB),
229
232
  fee: json.fee,
@@ -126,6 +126,7 @@ class Position {
126
126
  // construct counterfactual pools
127
127
  const poolLower = new pool_1.Pool({
128
128
  id: this.pool.id,
129
+ active: this.pool.active,
129
130
  tokenA: this.pool.tokenA,
130
131
  tokenB: this.pool.tokenB,
131
132
  fee: this.pool.fee,
@@ -138,6 +139,7 @@ class Position {
138
139
  });
139
140
  const poolUpper = new pool_1.Pool({
140
141
  id: this.pool.id,
142
+ active: this.pool.active,
141
143
  tokenA: this.pool.tokenA,
142
144
  tokenB: this.pool.tokenB,
143
145
  fee: this.pool.fee,
@@ -203,6 +205,7 @@ class Position {
203
205
  // construct counterfactual pools
204
206
  const poolLower = new pool_1.Pool({
205
207
  id: this.pool.id,
208
+ active: this.pool.active,
206
209
  tokenA: this.pool.tokenA,
207
210
  tokenB: this.pool.tokenB,
208
211
  fee: this.pool.fee,
@@ -215,6 +218,7 @@ class Position {
215
218
  });
216
219
  const poolUpper = new pool_1.Pool({
217
220
  id: this.pool.id,
221
+ active: this.pool.active,
218
222
  tokenA: this.pool.tokenA,
219
223
  tokenB: this.pool.tokenB,
220
224
  fee: this.pool.fee,
@@ -42,6 +42,7 @@ export declare class Trade<TInput extends Currency, TOutput extends Currency, TT
42
42
  * make up the trade.
43
43
  */
44
44
  readonly swaps: {
45
+ percent: number;
45
46
  route: Route<TInput, TOutput>;
46
47
  inputAmount: CurrencyAmount<TInput>;
47
48
  outputAmount: CurrencyAmount<TOutput>;
@@ -114,7 +115,7 @@ export declare class Trade<TInput extends Currency, TOutput extends Currency, TT
114
115
  * @param tradeType whether the trade is an exact input or exact output swap
115
116
  * @returns The route
116
117
  */
117
- static fromRoute<TInput extends Currency, TOutput extends Currency, TTradeType extends TradeType>(route: Route<TInput, TOutput>, amount: TTradeType extends TradeType.EXACT_INPUT ? CurrencyAmount<TInput> : CurrencyAmount<TOutput>, tradeType: TTradeType): Trade<TInput, TOutput, TTradeType>;
118
+ static fromRoute<TInput extends Currency, TOutput extends Currency, TTradeType extends TradeType>(route: Route<TInput, TOutput>, amount: TTradeType extends TradeType.EXACT_INPUT ? CurrencyAmount<TInput> : CurrencyAmount<TOutput>, tradeType: TTradeType, percent?: number): Trade<TInput, TOutput, TTradeType>;
118
119
  /**
119
120
  * Constructs a trade from routes by simulating swaps
120
121
  *
@@ -128,6 +129,7 @@ export declare class Trade<TInput extends Currency, TOutput extends Currency, TT
128
129
  static fromRoutes<TInput extends Currency, TOutput extends Currency, TTradeType extends TradeType>(routes: {
129
130
  amount: TTradeType extends TradeType.EXACT_INPUT ? CurrencyAmount<TInput> : CurrencyAmount<TOutput>;
130
131
  route: Route<TInput, TOutput>;
132
+ percent: number;
131
133
  }[], tradeType: TTradeType): Trade<TInput, TOutput, TTradeType>;
132
134
  /**
133
135
  * Creates a trade without computing the result of swapping through the route. Useful when you have simulated the trade
@@ -139,6 +141,7 @@ export declare class Trade<TInput extends Currency, TOutput extends Currency, TT
139
141
  * @returns The unchecked trade
140
142
  */
141
143
  static createUncheckedTrade<TInput extends Currency, TOutput extends Currency, TTradeType extends TradeType>(constructorArguments: {
144
+ percent: number;
142
145
  route: Route<TInput, TOutput>;
143
146
  inputAmount: CurrencyAmount<TInput>;
144
147
  outputAmount: CurrencyAmount<TOutput>;
@@ -155,6 +158,7 @@ export declare class Trade<TInput extends Currency, TOutput extends Currency, TT
155
158
  */
156
159
  static createUncheckedTradeWithMultipleRoutes<TInput extends Currency, TOutput extends Currency, TTradeType extends TradeType>(constructorArguments: {
157
160
  routes: {
161
+ percent: number;
158
162
  route: Route<TInput, TOutput>;
159
163
  inputAmount: CurrencyAmount<TInput>;
160
164
  outputAmount: CurrencyAmount<TOutput>;
@@ -187,4 +191,8 @@ export declare class Trade<TInput extends Currency, TOutput extends Currency, TT
187
191
  worstExecutionPrice(slippageTolerance: Percent): Price<TInput, TOutput>;
188
192
  static bestTradeExactIn<TInput extends Currency, TOutput extends Currency>(routes: Route<TInput, TOutput>[], currencyAmountIn: CurrencyAmount<TInput>, maxNumResults?: number): Trade<TInput, TOutput, TradeType.EXACT_INPUT>[];
189
193
  static bestTradeExactOut<TInput extends Currency, TOutput extends Currency>(routes: Route<TInput, TOutput>[], currencyAmountOut: CurrencyAmount<TOutput>, maxNumResults?: number): Trade<TInput, TOutput, TradeType.EXACT_OUTPUT>[];
194
+ static bestTradeWithSplit<TInput extends Currency, TOutput extends Currency>(routes: Route<TInput, TOutput>[], amount: CurrencyAmount<Currency>, percents: number[], tradeType: TradeType, swapConfig?: {
195
+ minSplits: number;
196
+ maxSplits: number;
197
+ }): Trade<Currency, Currency, TradeType.EXACT_INPUT> | null;
190
198
  }
@@ -8,6 +8,7 @@ const tiny_invariant_1 = __importDefault(require("tiny-invariant"));
8
8
  const fractions_1 = require("./fractions");
9
9
  const utils_1 = require("../utils");
10
10
  const internalConstants_1 = require("../internalConstants");
11
+ const getBestSwapRoute_1 = require("../utils/getBestSwapRoute");
11
12
  /**
12
13
  * Trades comparator, an extension of the input output comparator that also considers other dimensions of the trade in ranking them
13
14
  * @template TInput The input token, either Ether or an ERC-20
@@ -154,7 +155,7 @@ class Trade {
154
155
  * @param tradeType whether the trade is an exact input or exact output swap
155
156
  * @returns The route
156
157
  */
157
- static fromRoute(route, amount, tradeType) {
158
+ static fromRoute(route, amount, tradeType, percent = 100) {
158
159
  const amounts = new Array(route.tokenPath.length);
159
160
  let inputAmount;
160
161
  let outputAmount;
@@ -181,7 +182,7 @@ class Trade {
181
182
  outputAmount = fractions_1.CurrencyAmount.fromFractionalAmount(route.output, amount.numerator, amount.denominator);
182
183
  }
183
184
  return new Trade({
184
- routes: [{ inputAmount, outputAmount, route }],
185
+ routes: [{ inputAmount, outputAmount, route, percent }],
185
186
  tradeType
186
187
  });
187
188
  }
@@ -197,7 +198,7 @@ class Trade {
197
198
  */
198
199
  static fromRoutes(routes, tradeType) {
199
200
  const populatedRoutes = [];
200
- for (const { route, amount } of routes) {
201
+ for (const { route, amount, percent } of routes) {
201
202
  const amounts = new Array(route.tokenPath.length);
202
203
  let inputAmount;
203
204
  let outputAmount;
@@ -223,7 +224,7 @@ class Trade {
223
224
  }
224
225
  inputAmount = fractions_1.CurrencyAmount.fromFractionalAmount(route.input, amounts[0].numerator, amounts[0].denominator);
225
226
  }
226
- populatedRoutes.push({ route, inputAmount, outputAmount });
227
+ populatedRoutes.push({ route, inputAmount, outputAmount, percent });
227
228
  }
228
229
  return new Trade({
229
230
  routes: populatedRoutes,
@@ -244,6 +245,7 @@ class Trade {
244
245
  ...constructorArguments,
245
246
  routes: [
246
247
  {
248
+ percent: constructorArguments.percent,
247
249
  inputAmount: constructorArguments.inputAmount,
248
250
  outputAmount: constructorArguments.outputAmount,
249
251
  route: constructorArguments.route
@@ -331,9 +333,8 @@ class Trade {
331
333
  for (const route of routes) {
332
334
  const trade = Trade.fromRoute(route, currencyAmountIn, internalConstants_1.TradeType.EXACT_INPUT);
333
335
  // FIXME! Sorting bug multiple pools
334
- if (!trade.inputAmount.greaterThan(0) || !trade.priceImpact.greaterThan(0)) {
336
+ if (!trade.inputAmount.greaterThan(0) || !trade.priceImpact.greaterThan(0))
335
337
  continue;
336
- }
337
338
  (0, utils_1.sortedInsert)(bestTrades, trade, maxNumResults, tradeComparator);
338
339
  }
339
340
  return bestTrades;
@@ -343,12 +344,36 @@ class Trade {
343
344
  const bestTrades = [];
344
345
  for (const route of routes) {
345
346
  const trade = Trade.fromRoute(route, currencyAmountOut, internalConstants_1.TradeType.EXACT_OUTPUT);
346
- if (!trade.inputAmount.greaterThan(0) || !trade.priceImpact.greaterThan(0)) {
347
+ if (!trade.inputAmount.greaterThan(0) || !trade.priceImpact.greaterThan(0))
347
348
  continue;
348
- }
349
349
  (0, utils_1.sortedInsert)(bestTrades, trade, maxNumResults, tradeComparator);
350
350
  }
351
351
  return bestTrades;
352
352
  }
353
+ static bestTradeWithSplit(routes, amount, percents, tradeType, swapConfig = { minSplits: 1, maxSplits: 10 }) {
354
+ (0, tiny_invariant_1.default)(routes.length > 0, 'ROUTES');
355
+ (0, tiny_invariant_1.default)(percents.length > 0, 'PERCENTS');
356
+ // Compute routes for all percents for all routes
357
+ const percentToTrades = {};
358
+ for (const percent of percents) {
359
+ const splitAmount = amount.multiply(percent).divide(100);
360
+ for (const route of routes) {
361
+ const trade = Trade.fromRoute(route, splitAmount, tradeType, percent);
362
+ if (!trade.inputAmount.greaterThan(0) || !trade.priceImpact.greaterThan(0))
363
+ continue;
364
+ if (!percentToTrades[percent]) {
365
+ percentToTrades[percent] = [];
366
+ }
367
+ percentToTrades[percent].push(trade);
368
+ }
369
+ }
370
+ const bestTrades = (0, getBestSwapRoute_1.getBestSwapRoute)(tradeType, percentToTrades, percents, swapConfig);
371
+ if (!bestTrades)
372
+ return null;
373
+ return new Trade({
374
+ routes: bestTrades.map(({ inputAmount, outputAmount, route, swaps }) => ({ inputAmount, outputAmount, route, percent: swaps[0].percent })),
375
+ tradeType: internalConstants_1.TradeType.EXACT_INPUT
376
+ });
377
+ }
353
378
  }
354
379
  exports.Trade = Trade;
@@ -1,11 +1,11 @@
1
1
  export declare function parseTrade(trade: any): {
2
+ swaps: any;
2
3
  input: any;
3
4
  output: any;
4
5
  minReceived: any;
5
6
  maxSent: any;
6
7
  priceImpact: any;
7
- memo: string;
8
- route: any;
8
+ executionPriceStr: any;
9
9
  executionPrice: {
10
10
  numerator: any;
11
11
  denominator: any;
@@ -7,19 +7,28 @@ function parseTrade(trade) {
7
7
  // Parse Trade into api format object
8
8
  const slippage = new entities_1.Percent(3, 100); // 0.3%
9
9
  const receiver = '<receiver>';
10
- const tradeType = trade.tradeType == internalConstants_1.TradeType.EXACT_INPUT ? 'swapexactin' : 'swapexactout';
11
- const route = trade.route.pools.map(p => p.id);
12
10
  const maxSent = trade.inputAmount;
13
11
  const minReceived = trade.minimumAmountOut(slippage);
14
- const memo = `${tradeType}#${route.join(',')}#${receiver}#${minReceived.toExtendedAsset()}#0`;
12
+ const tradeType = trade.tradeType == internalConstants_1.TradeType.EXACT_INPUT ? 'swapexactin' : 'swapexactout';
13
+ const swaps = trade.swaps.map(({ route, percent, inputAmount, outputAmount }) => {
14
+ route = route.pools.map(p => p.id);
15
+ let minReceived = outputAmount;
16
+ if (trade.tradeType === internalConstants_1.TradeType.EXACT_INPUT) {
17
+ minReceived = outputAmount.multiply(new entities_1.Percent(1).subtract(slippage));
18
+ }
19
+ const input = inputAmount.toSignificant();
20
+ const output = outputAmount.toSignificant();
21
+ const memo = `${tradeType}#${route.join(',')}#${receiver}#${minReceived.toExtendedAsset()}#0`;
22
+ return { input, output, percent, memo };
23
+ });
15
24
  const result = {
25
+ swaps,
16
26
  input: trade.inputAmount.toFixed(),
17
27
  output: trade.outputAmount.toFixed(),
18
28
  minReceived: minReceived.toFixed(),
19
29
  maxSent: maxSent.toFixed(),
20
30
  priceImpact: trade.priceImpact.toSignificant(2),
21
- memo,
22
- route,
31
+ executionPriceStr: trade.executionPrice.toFixed(),
23
32
  executionPrice: {
24
33
  numerator: trade.executionPrice.numerator.toString(),
25
34
  denominator: trade.executionPrice.denominator.toString()
@@ -0,0 +1,9 @@
1
+ import { Currency } from '../entities/currency';
2
+ import { Trade } from '../entities/trade';
3
+ import { TradeType } from '../internalConstants';
4
+ export declare function getBestSwapRoute(routeType: TradeType, percentToQuotes: {
5
+ [percent: number]: Trade<Currency, Currency, TradeType>[];
6
+ }, percents: number[], swapRouteConfig?: {
7
+ minSplits: number;
8
+ maxSplits: number;
9
+ }): Trade<Currency, Currency, TradeType>[] | null;
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getBestSwapRoute = void 0;
7
+ const lodash_1 = __importDefault(require("lodash"));
8
+ const queue_1 = __importDefault(require("mnemonist/queue"));
9
+ const fixed_reverse_heap_1 = __importDefault(require("mnemonist/fixed-reverse-heap"));
10
+ const internalConstants_1 = require("../internalConstants");
11
+ function getBestSwapRoute(routeType, percentToQuotes, percents, swapRouteConfig = { minSplits: 1, maxSplits: 8 }) {
12
+ const percentToSortedQuotes = lodash_1.default.mapValues(percentToQuotes, (routeQuotes) => {
13
+ return routeQuotes.sort((routeQuoteA, routeQuoteB) => {
14
+ if (routeType == internalConstants_1.TradeType.EXACT_INPUT) {
15
+ return routeQuoteA.outputAmount.greaterThan(routeQuoteB.outputAmount) ? -1 : 1;
16
+ }
17
+ else {
18
+ return routeQuoteA.inputAmount.lessThan(routeQuoteB.inputAmount) ? -1 : 1;
19
+ }
20
+ });
21
+ });
22
+ const quoteCompFn = routeType == internalConstants_1.TradeType.EXACT_INPUT
23
+ ? (a, b) => a.greaterThan(b)
24
+ : (a, b) => a.lessThan(b);
25
+ const sumFn = (currencyAmounts) => {
26
+ let sum = currencyAmounts[0];
27
+ for (let i = 1; i < currencyAmounts.length; i++) {
28
+ sum = sum.add(currencyAmounts[i]);
29
+ }
30
+ return sum;
31
+ };
32
+ let bestQuote;
33
+ let bestSwap;
34
+ const bestSwapsPerSplit = new fixed_reverse_heap_1.default(Array, (a, b) => {
35
+ return quoteCompFn(a.quote, b.quote) ? -1 : 1;
36
+ }, 3);
37
+ const { minSplits, maxSplits } = swapRouteConfig;
38
+ if (!percentToSortedQuotes[100] || minSplits > 1) {
39
+ console.log({
40
+ percentToSortedQuotes: lodash_1.default.mapValues(percentToSortedQuotes, (p) => p.length),
41
+ }, 'Did not find a valid route without any splits. Continuing search anyway.');
42
+ }
43
+ else {
44
+ bestQuote = percentToSortedQuotes[100][0].outputAmount;
45
+ bestSwap = [percentToSortedQuotes[100][0]];
46
+ for (const routeWithQuote of percentToSortedQuotes[100].slice(0, 5)) {
47
+ bestSwapsPerSplit.push({
48
+ quote: routeWithQuote.outputAmount,
49
+ routes: [routeWithQuote],
50
+ });
51
+ }
52
+ }
53
+ const queue = new queue_1.default();
54
+ if (percents.length === 0)
55
+ return null; // Handle empty percents array
56
+ for (let i = percents.length - 1; i >= 0; i--) {
57
+ const percent = percents[i];
58
+ if (!percentToSortedQuotes[percent]) {
59
+ console.log('continue', { percent });
60
+ continue;
61
+ }
62
+ queue.enqueue({
63
+ curRoutes: [percentToSortedQuotes[percent][0]],
64
+ percentIndex: i,
65
+ remainingPercent: 100 - percent,
66
+ special: false,
67
+ });
68
+ if (!percentToSortedQuotes[percent] ||
69
+ !percentToSortedQuotes[percent][1]) {
70
+ console.log('continue2', { percent });
71
+ continue;
72
+ }
73
+ queue.enqueue({
74
+ curRoutes: [percentToSortedQuotes[percent][1]],
75
+ percentIndex: i,
76
+ remainingPercent: 100 - percent,
77
+ special: true,
78
+ });
79
+ }
80
+ let splits = 1;
81
+ while (queue.size > 0) {
82
+ bestSwapsPerSplit.clear();
83
+ let layer = queue.size;
84
+ splits++;
85
+ if (splits >= 3 && bestSwap && bestSwap.length < splits - 1) {
86
+ break;
87
+ }
88
+ if (splits > maxSplits) {
89
+ break;
90
+ }
91
+ while (layer > 0) {
92
+ layer--;
93
+ const { remainingPercent, curRoutes, percentIndex, special } = queue.dequeue();
94
+ for (let i = percentIndex; i >= 0; i--) {
95
+ const percentA = percents[i];
96
+ if (percentA > remainingPercent) {
97
+ continue;
98
+ }
99
+ if (!percentToSortedQuotes[percentA]) {
100
+ continue;
101
+ }
102
+ const candidateRoutesA = percentToSortedQuotes[percentA];
103
+ const routeWithQuoteA = findFirstRouteNotUsingUsedPools(curRoutes, candidateRoutesA);
104
+ if (!routeWithQuoteA) {
105
+ continue;
106
+ }
107
+ const remainingPercentNew = remainingPercent - percentA;
108
+ const curRoutesNew = [...curRoutes, routeWithQuoteA];
109
+ if (remainingPercentNew == 0 && splits >= minSplits) {
110
+ const quotesNew = lodash_1.default.map(curRoutesNew, (r) => r.outputAmount);
111
+ const quoteNew = sumFn(quotesNew);
112
+ bestSwapsPerSplit.push({
113
+ quote: quoteNew,
114
+ routes: curRoutesNew,
115
+ });
116
+ if (!bestQuote || quoteCompFn(quoteNew, bestQuote)) {
117
+ bestQuote = quoteNew;
118
+ bestSwap = curRoutesNew;
119
+ }
120
+ }
121
+ else {
122
+ queue.enqueue({
123
+ curRoutes: curRoutesNew,
124
+ remainingPercent: remainingPercentNew,
125
+ percentIndex: i,
126
+ special,
127
+ });
128
+ }
129
+ }
130
+ }
131
+ }
132
+ if (!bestSwap) {
133
+ console.log(`Could not find a valid swap`);
134
+ return null;
135
+ }
136
+ const quote = sumFn(lodash_1.default.map(bestSwap, (routeWithValidQuote) => routeWithValidQuote.outputAmount));
137
+ const routeWithQuotes = bestSwap.sort((routeAmountA, routeAmountB) => routeAmountB.outputAmount.greaterThan(routeAmountA.outputAmount) ? 1 : -1);
138
+ return routeWithQuotes;
139
+ }
140
+ exports.getBestSwapRoute = getBestSwapRoute;
141
+ const findFirstRouteNotUsingUsedPools = (usedRoutes, candidateRoutes) => {
142
+ const usedPools = new Set();
143
+ usedRoutes.forEach(r => r.route.pools.forEach(pool => usedPools.add(pool.id)));
144
+ for (const candidate of candidateRoutes) {
145
+ if (candidate.route.pools.every(pool => !usedPools.has(pool.id))) {
146
+ return candidate;
147
+ }
148
+ }
149
+ return null;
150
+ };
@@ -54,12 +54,12 @@ async function main() {
54
54
  })
55
55
 
56
56
  const pool = new Pool({
57
- ...poolRow,
57
+ ...poolRow as any,
58
58
  tokenA: parseToken(tokenA),
59
59
  tokenB: parseToken(tokenB),
60
60
  sqrtPriceX64,
61
61
  tickCurrent: tick,
62
- ticks: ticks.sort((a, b) => a.id - b.id)
62
+ ticks: ticks.sort((a: any, b: any) => a.id - b.id)
63
63
  })
64
64
 
65
65
  const { rows: positions } = await rpc.get_table_rows({
@@ -28,6 +28,7 @@ async function main() {
28
28
  const pools: Pool[] = []
29
29
 
30
30
  // We have to get all pools with fetched ticks for them
31
+ let i = 0
31
32
  for (const p of rows) {
32
33
  const { id, tokenA, tokenB, currSlot: { sqrtPriceX64, tick } } = p
33
34
 
@@ -40,13 +41,15 @@ async function main() {
40
41
  if (ticks.length == 0) continue
41
42
 
42
43
  pools.push(new Pool({
43
- ...p,
44
+ ...p as any,
44
45
  tokenA: parseToken(tokenA),
45
46
  tokenB: parseToken(tokenB),
46
47
  sqrtPriceX64,
47
48
  tickCurrent: tick,
48
- ticks: ticks.sort((a, b) => a.id - b.id)
49
+ ticks: ticks.sort((a: any, b: any) => a.id - b.id)
49
50
  }))
51
+ i += 1
52
+ process.stdout.write(`fetching ticks: ${i}/${rows.length} \r`)
50
53
  }
51
54
 
52
55
  // 1.0000 EOS
@@ -56,9 +59,8 @@ async function main() {
56
59
  const receiver = 'myaccount'
57
60
 
58
61
  // First trade sorted by biggest output
59
- //const [trade] = Trade.bestTradeExactIn(pools, amountIn, tokenOut, { maxHops: 3 })
60
- const routes = computeAllRoutes(amountIn.currency, tokenOut, pools, 3)
61
- const [trade] = Trade.bestTradeExactIn(routes, pools, amountIn, 3)
62
+ const routes = computeAllRoutes(amountIn.currency, tokenOut, pools, 2)
63
+ const [trade] = Trade.bestTradeExactIn(routes, amountIn, 1)
62
64
 
63
65
  const route = trade.route.pools.map(p => p.id)
64
66
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alcorexchange/alcor-swap-sdk",
3
- "version": "1.0.32",
3
+ "version": "1.0.34",
4
4
  "description": "",
5
5
  "main": "build/index.js",
6
6
  "scripts": {
@@ -45,7 +45,7 @@
45
45
  "redis": "^4.6.10",
46
46
  "rimraf": "^4.1.1",
47
47
  "ts-jest": "^28.0.4",
48
- "ts-node": "^10.9.1",
48
+ "ts-node": "^10.9.2",
49
49
  "typescript": "^4.9.4"
50
50
  },
51
51
  "dependencies": {
@@ -55,6 +55,8 @@
55
55
  "eosjs": "^22.1.0",
56
56
  "eosjs-account-name": "2.3.0",
57
57
  "jsbi": "^4.3.0",
58
+ "lodash": "^4.17.21",
59
+ "mnemonist": "^0.39.8",
58
60
  "msgpack-lite": "^0.1.26",
59
61
  "node-fetch": "2",
60
62
  "tiny-invariant": "^1.1.0",
@@ -16,6 +16,7 @@ import { TickListDataProvider } from "./tickListDataProvider";
16
16
 
17
17
  export interface PoolConstructorArgs {
18
18
  id: number,
19
+ active: boolean,
19
20
  tokenA: Token,
20
21
  tokenB: Token,
21
22
  fee: FeeAmount,
@@ -50,6 +51,7 @@ const NO_TICK_DATA_PROVIDER_DEFAULT = new NoTickDataProvider();
50
51
  export class Pool {
51
52
  // public readonly id: number;
52
53
  public readonly id: number;
54
+ public readonly active: boolean;
53
55
  public readonly tokenA: Token;
54
56
  public readonly tokenB: Token;
55
57
  public readonly fee: FeeAmount;
@@ -83,6 +85,7 @@ export class Pool {
83
85
  */
84
86
  public constructor({
85
87
  id,
88
+ active,
86
89
  tokenA,
87
90
  tokenB,
88
91
  fee,
@@ -108,6 +111,7 @@ export class Pool {
108
111
  // always create a copy of the list since we want the pool's tick list to be immutable
109
112
  this.id = id;
110
113
  this.fee = fee;
114
+ this.active = active;
111
115
  this.sqrtPriceX64 = JSBI.BigInt(sqrtPriceX64);
112
116
  this.liquidity = JSBI.BigInt(liquidity);
113
117
  this.tickCurrent = tickCurrent;
@@ -380,6 +384,7 @@ export class Pool {
380
384
  id: pool.id,
381
385
  tokenA: Token.toJSON(pool.tokenA),
382
386
  tokenB: Token.toJSON(pool.tokenB),
387
+ active: pool.active,
383
388
  fee: pool.fee,
384
389
  sqrtPriceX64: pool.sqrtPriceX64.toString(),
385
390
  liquidity: pool.liquidity.toString(),
@@ -395,6 +400,7 @@ export class Pool {
395
400
  static fromJSON(json: any): Pool {
396
401
  return new Pool({
397
402
  id: json.id,
403
+ active: json.active,
398
404
  tokenA: Token.fromJSON(json.tokenA),
399
405
  tokenB: Token.fromJSON(json.tokenB),
400
406
  fee: json.fee,
@@ -234,6 +234,7 @@ export class Position {
234
234
  // construct counterfactual pools
235
235
  const poolLower = new Pool({
236
236
  id: this.pool.id,
237
+ active: this.pool.active,
237
238
  tokenA: this.pool.tokenA,
238
239
  tokenB: this.pool.tokenB,
239
240
  fee: this.pool.fee,
@@ -246,6 +247,7 @@ export class Position {
246
247
  });
247
248
  const poolUpper = new Pool({
248
249
  id: this.pool.id,
250
+ active: this.pool.active,
249
251
  tokenA: this.pool.tokenA,
250
252
  tokenB: this.pool.tokenB,
251
253
  fee: this.pool.fee,
@@ -319,6 +321,7 @@ export class Position {
319
321
  // construct counterfactual pools
320
322
  const poolLower = new Pool({
321
323
  id: this.pool.id,
324
+ active: this.pool.active,
322
325
  tokenA: this.pool.tokenA,
323
326
  tokenB: this.pool.tokenB,
324
327
  fee: this.pool.fee,
@@ -331,6 +334,7 @@ export class Position {
331
334
  });
332
335
  const poolUpper = new Pool({
333
336
  id: this.pool.id,
337
+ active: this.pool.active,
334
338
  tokenA: this.pool.tokenA,
335
339
  tokenB: this.pool.tokenB,
336
340
  fee: this.pool.fee,
@@ -1,12 +1,13 @@
1
1
  import invariant from 'tiny-invariant'
2
2
 
3
+
3
4
  import { Currency } from './currency'
4
5
  import { Fraction, Percent, Price, CurrencyAmount } from './fractions'
5
- import { sortedInsert, parseTrade } from '../utils'
6
+ import { sortedInsert } from '../utils'
6
7
  import { Token } from './token'
7
8
  import { ONE, ZERO, TradeType } from '../internalConstants'
8
- import { Pool } from './pool'
9
9
  import { Route } from './route'
10
+ import { getBestSwapRoute } from '../utils/getBestSwapRoute'
10
11
 
11
12
  /**
12
13
  * Trades comparator, an extension of the input output comparator that also considers other dimensions of the trade in ranking them
@@ -84,6 +85,7 @@ export class Trade<TInput extends Currency, TOutput extends Currency, TTradeType
84
85
  * make up the trade.
85
86
  */
86
87
  public readonly swaps: {
88
+ percent: number,
87
89
  route: Route<TInput, TOutput>
88
90
  inputAmount: CurrencyAmount<TInput>
89
91
  outputAmount: CurrencyAmount<TOutput>
@@ -230,7 +232,8 @@ export class Trade<TInput extends Currency, TOutput extends Currency, TTradeType
230
232
  public static fromRoute<TInput extends Currency, TOutput extends Currency, TTradeType extends TradeType>(
231
233
  route: Route<TInput, TOutput>,
232
234
  amount: TTradeType extends TradeType.EXACT_INPUT ? CurrencyAmount<TInput> : CurrencyAmount<TOutput>,
233
- tradeType: TTradeType
235
+ tradeType: TTradeType,
236
+ percent = 100
234
237
  ): Trade<TInput, TOutput, TTradeType> {
235
238
  const amounts: CurrencyAmount<Token>[] = new Array(route.tokenPath.length)
236
239
  let inputAmount: CurrencyAmount<TInput>
@@ -262,7 +265,7 @@ export class Trade<TInput extends Currency, TOutput extends Currency, TTradeType
262
265
  }
263
266
 
264
267
  return new Trade({
265
- routes: [{ inputAmount, outputAmount, route }],
268
+ routes: [{ inputAmount, outputAmount, route, percent }],
266
269
  tradeType
267
270
  })
268
271
  }
@@ -280,17 +283,19 @@ export class Trade<TInput extends Currency, TOutput extends Currency, TTradeType
280
283
  public static fromRoutes<TInput extends Currency, TOutput extends Currency, TTradeType extends TradeType>(
281
284
  routes: {
282
285
  amount: TTradeType extends TradeType.EXACT_INPUT ? CurrencyAmount<TInput> : CurrencyAmount<TOutput>
283
- route: Route<TInput, TOutput>
286
+ route: Route<TInput, TOutput>,
287
+ percent: number
284
288
  }[],
285
289
  tradeType: TTradeType
286
290
  ): Trade<TInput, TOutput, TTradeType> {
287
291
  const populatedRoutes: {
292
+ percent: number,
288
293
  route: Route<TInput, TOutput>
289
294
  inputAmount: CurrencyAmount<TInput>
290
295
  outputAmount: CurrencyAmount<TOutput>
291
296
  }[] = []
292
297
 
293
- for (const { route, amount } of routes) {
298
+ for (const { route, amount, percent } of routes) {
294
299
  const amounts: CurrencyAmount<Token>[] = new Array(route.tokenPath.length)
295
300
  let inputAmount: CurrencyAmount<TInput>
296
301
  let outputAmount: CurrencyAmount<TOutput>
@@ -329,7 +334,7 @@ export class Trade<TInput extends Currency, TOutput extends Currency, TTradeType
329
334
  inputAmount = CurrencyAmount.fromFractionalAmount(route.input, amounts[0].numerator, amounts[0].denominator)
330
335
  }
331
336
 
332
- populatedRoutes.push({ route, inputAmount, outputAmount })
337
+ populatedRoutes.push({ route, inputAmount, outputAmount, percent })
333
338
  }
334
339
 
335
340
  return new Trade({
@@ -352,6 +357,7 @@ export class Trade<TInput extends Currency, TOutput extends Currency, TTradeType
352
357
  TOutput extends Currency,
353
358
  TTradeType extends TradeType
354
359
  >(constructorArguments: {
360
+ percent: number,
355
361
  route: Route<TInput, TOutput>
356
362
  inputAmount: CurrencyAmount<TInput>
357
363
  outputAmount: CurrencyAmount<TOutput>
@@ -361,6 +367,7 @@ export class Trade<TInput extends Currency, TOutput extends Currency, TTradeType
361
367
  ...constructorArguments,
362
368
  routes: [
363
369
  {
370
+ percent: constructorArguments.percent,
364
371
  inputAmount: constructorArguments.inputAmount,
365
372
  outputAmount: constructorArguments.outputAmount,
366
373
  route: constructorArguments.route
@@ -384,6 +391,7 @@ export class Trade<TInput extends Currency, TOutput extends Currency, TTradeType
384
391
  TTradeType extends TradeType
385
392
  >(constructorArguments: {
386
393
  routes: {
394
+ percent: number
387
395
  route: Route<TInput, TOutput>
388
396
  inputAmount: CurrencyAmount<TInput>
389
397
  outputAmount: CurrencyAmount<TOutput>
@@ -403,6 +411,7 @@ export class Trade<TInput extends Currency, TOutput extends Currency, TTradeType
403
411
  tradeType
404
412
  }: {
405
413
  routes: {
414
+ percent: number,
406
415
  route: Route<TInput, TOutput>
407
416
  inputAmount: CurrencyAmount<TInput>
408
417
  outputAmount: CurrencyAmount<TOutput>
@@ -493,9 +502,7 @@ export class Trade<TInput extends Currency, TOutput extends Currency, TTradeType
493
502
  const trade = Trade.fromRoute(route, currencyAmountIn, TradeType.EXACT_INPUT)
494
503
 
495
504
  // FIXME! Sorting bug multiple pools
496
- if (!trade.inputAmount.greaterThan(0) || !trade.priceImpact.greaterThan(0)) {
497
- continue
498
- }
505
+ if (!trade.inputAmount.greaterThan(0) || !trade.priceImpact.greaterThan(0)) continue
499
506
 
500
507
  sortedInsert(
501
508
  bestTrades,
@@ -519,9 +526,7 @@ export class Trade<TInput extends Currency, TOutput extends Currency, TTradeType
519
526
  for (const route of routes) {
520
527
  const trade = Trade.fromRoute(route, currencyAmountOut, TradeType.EXACT_OUTPUT)
521
528
 
522
- if (!trade.inputAmount.greaterThan(0) || !trade.priceImpact.greaterThan(0)) {
523
- continue
524
- }
529
+ if (!trade.inputAmount.greaterThan(0) || !trade.priceImpact.greaterThan(0)) continue
525
530
 
526
531
  sortedInsert(
527
532
  bestTrades,
@@ -533,4 +538,41 @@ export class Trade<TInput extends Currency, TOutput extends Currency, TTradeType
533
538
 
534
539
  return bestTrades
535
540
  }
541
+
542
+ public static bestTradeWithSplit<TInput extends Currency, TOutput extends Currency>(
543
+ routes: Route<TInput, TOutput>[],
544
+ amount: CurrencyAmount<Currency>,
545
+ percents: number[],
546
+ tradeType: TradeType,
547
+ swapConfig = { minSplits: 1, maxSplits: 10 }
548
+ ): Trade<Currency, Currency, TradeType.EXACT_INPUT> | null {
549
+ invariant(routes.length > 0, 'ROUTES')
550
+ invariant(percents.length > 0, 'PERCENTS')
551
+
552
+ // Compute routes for all percents for all routes
553
+ const percentToTrades: { [percent: number]: Trade<Currency, Currency, TradeType>[] } = {};
554
+ for (const percent of percents) {
555
+ const splitAmount = amount.multiply(percent).divide(100)
556
+
557
+ for (const route of routes) {
558
+ const trade = Trade.fromRoute(route, splitAmount, tradeType, percent)
559
+ if (!trade.inputAmount.greaterThan(0) || !trade.priceImpact.greaterThan(0)) continue
560
+
561
+ if (!percentToTrades[percent]) {
562
+ percentToTrades[percent] = []
563
+ }
564
+
565
+ percentToTrades[percent].push(trade)
566
+ }
567
+ }
568
+
569
+ const bestTrades = getBestSwapRoute(tradeType, percentToTrades, percents, swapConfig)
570
+ if (!bestTrades) return null
571
+
572
+ return new Trade({
573
+ routes: bestTrades.map(({ inputAmount, outputAmount, route, swaps }) =>
574
+ ({ inputAmount, outputAmount, route, percent: swaps[0].percent })),
575
+ tradeType: TradeType.EXACT_INPUT }
576
+ )
577
+ }
536
578
  }
@@ -1,4 +1,4 @@
1
- import { Percent } from "../entities"
1
+ import { Percent, Trade } from "../entities"
2
2
  import { TradeType } from "../internalConstants"
3
3
 
4
4
  export function parseTrade(trade) {
@@ -6,21 +6,35 @@ export function parseTrade(trade) {
6
6
  const slippage = new Percent(3, 100) // 0.3%
7
7
  const receiver = '<receiver>'
8
8
 
9
- const tradeType = trade.tradeType == TradeType.EXACT_INPUT ? 'swapexactin' : 'swapexactout'
10
-
11
- const route = trade.route.pools.map(p => p.id)
12
9
  const maxSent = trade.inputAmount
13
10
  const minReceived = trade.minimumAmountOut(slippage)
14
- const memo = `${tradeType}#${route.join(',')}#${receiver}#${minReceived.toExtendedAsset()}#0`
11
+ const tradeType = trade.tradeType == TradeType.EXACT_INPUT ? 'swapexactin' : 'swapexactout'
12
+
13
+ const swaps = trade.swaps.map(({ route, percent, inputAmount, outputAmount }) => {
14
+ route = route.pools.map(p => p.id)
15
+
16
+ let minReceived = outputAmount
17
+
18
+ if (trade.tradeType === TradeType.EXACT_INPUT) {
19
+ minReceived = outputAmount.multiply(new Percent(1).subtract(slippage))
20
+ }
21
+
22
+ const input = inputAmount.toSignificant()
23
+ const output = outputAmount.toSignificant()
24
+
25
+ const memo = `${tradeType}#${route.join(',')}#${receiver}#${minReceived.toExtendedAsset()}#0`
26
+ return { input, output, percent, memo }
27
+ })
15
28
 
16
29
  const result = {
30
+ swaps,
17
31
  input: trade.inputAmount.toFixed(),
18
32
  output: trade.outputAmount.toFixed(),
19
33
  minReceived: minReceived.toFixed(),
20
34
  maxSent: maxSent.toFixed(),
21
35
  priceImpact: trade.priceImpact.toSignificant(2),
22
- memo,
23
- route,
36
+
37
+ executionPriceStr: trade.executionPrice.toFixed(),
24
38
  executionPrice: {
25
39
  numerator: trade.executionPrice.numerator.toString(),
26
40
  denominator: trade.executionPrice.denominator.toString()
@@ -0,0 +1,223 @@
1
+ import _ from 'lodash'
2
+ import Queue from 'mnemonist/queue'
3
+ import FixedReverseHeap from 'mnemonist/fixed-reverse-heap'
4
+
5
+ import { Currency } from '../entities/currency'
6
+ import { Trade } from '../entities/trade'
7
+ import { CurrencyAmount } from '../entities/fractions'
8
+ import { TradeType } from '../internalConstants'
9
+
10
+ export function getBestSwapRoute(
11
+ routeType: TradeType,
12
+ percentToQuotes: { [percent: number]: Trade<Currency, Currency, TradeType>[] },
13
+ percents: number[],
14
+ swapRouteConfig = { minSplits: 1, maxSplits: 8 }
15
+ ): Trade<Currency, Currency, TradeType>[] | null {
16
+ const percentToSortedQuotes = _.mapValues(
17
+ percentToQuotes,
18
+ (routeQuotes: Trade<Currency, Currency, TradeType>[]) => {
19
+ return routeQuotes.sort((routeQuoteA, routeQuoteB) => {
20
+ if (routeType == TradeType.EXACT_INPUT) {
21
+ return routeQuoteA.outputAmount.greaterThan(routeQuoteB.outputAmount) ? -1 : 1;
22
+ } else {
23
+ return routeQuoteA.inputAmount.lessThan(routeQuoteB.inputAmount) ? -1 : 1;
24
+ }
25
+ });
26
+ }
27
+ );
28
+
29
+ const quoteCompFn =
30
+ routeType == TradeType.EXACT_INPUT
31
+ ? (a: CurrencyAmount<Currency>, b: CurrencyAmount<Currency>) => a.greaterThan(b)
32
+ : (a: CurrencyAmount<Currency>, b: CurrencyAmount<Currency>) => a.lessThan(b);
33
+
34
+ const sumFn = (currencyAmounts: CurrencyAmount<Currency>[]): CurrencyAmount<Currency> => {
35
+ let sum = currencyAmounts[0]!;
36
+ for (let i = 1; i < currencyAmounts.length; i++) {
37
+ sum = sum.add(currencyAmounts[i]!);
38
+ }
39
+ return sum;
40
+ };
41
+
42
+ let bestQuote: CurrencyAmount<Currency> | undefined;
43
+ let bestSwap: Trade<Currency, Currency, TradeType>[] | undefined;
44
+
45
+ const bestSwapsPerSplit = new FixedReverseHeap<{
46
+ quote: CurrencyAmount<Currency>;
47
+ routes: Trade<Currency, Currency, TradeType>[];
48
+ }>(
49
+ Array,
50
+ (a, b) => {
51
+ return quoteCompFn(a.quote, b.quote) ? -1 : 1;
52
+ },
53
+ 3
54
+ );
55
+
56
+ const { minSplits, maxSplits } = swapRouteConfig;
57
+
58
+ if (!percentToSortedQuotes[100] || minSplits > 1) {
59
+ console.log(
60
+ {
61
+ percentToSortedQuotes: _.mapValues(
62
+ percentToSortedQuotes,
63
+ (p) => p.length
64
+ ),
65
+ },
66
+ 'Did not find a valid route without any splits. Continuing search anyway.'
67
+ );
68
+ } else {
69
+ bestQuote = percentToSortedQuotes[100][0]!.outputAmount;
70
+ bestSwap = [percentToSortedQuotes[100][0]!];
71
+
72
+ for (const routeWithQuote of percentToSortedQuotes[100].slice(0, 5)) {
73
+ bestSwapsPerSplit.push({
74
+ quote: routeWithQuote.outputAmount,
75
+ routes: [routeWithQuote],
76
+ });
77
+ }
78
+ }
79
+
80
+ const queue = new Queue<{
81
+ percentIndex: number;
82
+ curRoutes: Trade<Currency, Currency, TradeType>[];
83
+ remainingPercent: number;
84
+ special: boolean;
85
+ }>();
86
+
87
+ if (percents.length === 0) return null; // Handle empty percents array
88
+
89
+ for (let i = percents.length - 1; i >= 0; i--) {
90
+ const percent = percents[i];
91
+
92
+ if (!percentToSortedQuotes[percent]) {
93
+ console.log('continue', { percent });
94
+ continue;
95
+ }
96
+
97
+ queue.enqueue({
98
+ curRoutes: [percentToSortedQuotes[percent]![0]!],
99
+ percentIndex: i,
100
+ remainingPercent: 100 - percent,
101
+ special: false,
102
+ });
103
+
104
+ if (
105
+ !percentToSortedQuotes[percent] ||
106
+ !percentToSortedQuotes[percent]![1]
107
+ ) {
108
+ console.log('continue2', { percent });
109
+ continue;
110
+ }
111
+
112
+ queue.enqueue({
113
+ curRoutes: [percentToSortedQuotes[percent]![1]!],
114
+ percentIndex: i,
115
+ remainingPercent: 100 - percent,
116
+ special: true,
117
+ });
118
+ }
119
+
120
+ let splits = 1;
121
+
122
+ while (queue.size > 0) {
123
+ bestSwapsPerSplit.clear();
124
+
125
+ let layer = queue.size;
126
+ splits++;
127
+
128
+ if (splits >= 3 && bestSwap && bestSwap.length < splits - 1) {
129
+ break;
130
+ }
131
+
132
+ if (splits > maxSplits) {
133
+ break;
134
+ }
135
+
136
+ while (layer > 0) {
137
+ layer--;
138
+
139
+ const { remainingPercent, curRoutes, percentIndex, special } =
140
+ queue.dequeue()!;
141
+
142
+ for (let i = percentIndex; i >= 0; i--) {
143
+ const percentA = percents[i]!;
144
+
145
+ if (percentA > remainingPercent) {
146
+ continue;
147
+ }
148
+
149
+ if (!percentToSortedQuotes[percentA]) {
150
+ continue;
151
+ }
152
+
153
+ const candidateRoutesA = percentToSortedQuotes[percentA]!;
154
+
155
+ const routeWithQuoteA = findFirstRouteNotUsingUsedPools(
156
+ curRoutes,
157
+ candidateRoutesA,
158
+ );
159
+
160
+ if (!routeWithQuoteA) {
161
+ continue;
162
+ }
163
+
164
+ const remainingPercentNew = remainingPercent - percentA;
165
+ const curRoutesNew = [...curRoutes, routeWithQuoteA];
166
+
167
+ if (remainingPercentNew == 0 && splits >= minSplits) {
168
+ const quotesNew = _.map(curRoutesNew, (r) => r.outputAmount);
169
+ const quoteNew = sumFn(quotesNew);
170
+
171
+ bestSwapsPerSplit.push({
172
+ quote: quoteNew,
173
+ routes: curRoutesNew,
174
+ });
175
+
176
+ if (!bestQuote || quoteCompFn(quoteNew, bestQuote)) {
177
+ bestQuote = quoteNew;
178
+ bestSwap = curRoutesNew;
179
+ }
180
+ } else {
181
+ queue.enqueue({
182
+ curRoutes: curRoutesNew,
183
+ remainingPercent: remainingPercentNew,
184
+ percentIndex: i,
185
+ special,
186
+ });
187
+ }
188
+ }
189
+ }
190
+ }
191
+
192
+ if (!bestSwap) {
193
+ console.log(`Could not find a valid swap`);
194
+ return null;
195
+ }
196
+
197
+ const quote = sumFn(
198
+ _.map(bestSwap, (routeWithValidQuote) => routeWithValidQuote.outputAmount)
199
+ );
200
+
201
+ const routeWithQuotes = bestSwap.sort((routeAmountA, routeAmountB) =>
202
+ routeAmountB.outputAmount.greaterThan(routeAmountA.outputAmount) ? 1 : -1
203
+ );
204
+
205
+ return routeWithQuotes;
206
+ }
207
+
208
+
209
+ const findFirstRouteNotUsingUsedPools = (
210
+ usedRoutes: Trade<Currency, Currency, TradeType>[],
211
+ candidateRoutes: Trade<Currency, Currency, TradeType>[],
212
+ ): Trade<Currency, Currency, TradeType> | null => {
213
+ const usedPools = new Set<number>();
214
+ usedRoutes.forEach(r => r.route.pools.forEach(pool => usedPools.add(pool.id)));
215
+
216
+ for (const candidate of candidateRoutes) {
217
+ if (candidate.route.pools.every(pool => !usedPools.has(pool.id))) {
218
+ return candidate;
219
+ }
220
+ }
221
+
222
+ return null;
223
+ }