@alcorexchange/alcor-swap-sdk 1.1.2 → 1.1.4

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.
@@ -170,10 +170,7 @@ let Pool = exports.Pool = /*#__PURE__*/function () {
170
170
  const outputToken = zeroForOne ? this.tokenB : this.tokenA;
171
171
  const outputAmount = zeroForOne ? amountB : amountA;
172
172
  const amountIn = zeroForOne ? amountA : amountB;
173
-
174
- //console.log((amountIn === inputAmount.quotient))
175
- if (!(inputAmount.quotient >= amountIn)) {
176
- //if (!(amountIn === inputAmount.quotient)) {
173
+ if (!(amountIn === inputAmount.quotient)) {
177
174
  throw new _errors.InsufficientInputAmountError();
178
175
  }
179
176
  return _fractions.CurrencyAmount.fromRawAmount(outputToken, outputAmount * _internalConstants.NEGATIVE_ONE);
@@ -217,5 +217,7 @@ export declare class Trade<TInput extends Currency, TOutput extends Currency, TT
217
217
  static bestTradeWithSplit<TInput extends Currency, TOutput extends Currency>(_routes: Route<TInput, TOutput>[], amount: CurrencyAmount<Currency>, percents: number[], tradeType: TradeType, swapConfig?: {
218
218
  minSplits: number;
219
219
  maxSplits: number;
220
+ branchFactor?: number;
221
+ candidateLimit?: number;
220
222
  }): Trade<Currency, Currency, TradeType> | null;
221
223
  }
@@ -604,23 +604,46 @@ let Trade = exports.Trade = /*#__PURE__*/function () {
604
604
  for (const percent of percents) {
605
605
  percentToAmount.set(percent, amount.multiply(percent).divide(100));
606
606
  }
607
-
608
- // Используем Map вместо объекта для лучшей производительности
609
- const percentToTrades = new Map();
607
+ const quoteRoute = (route, splitAmount, percent) => {
608
+ const amounts = new Array(route.tokenPath.length);
609
+ let inputAmount;
610
+ let outputAmount;
611
+ if (tradeType === _internalConstants.TradeType.EXACT_INPUT) {
612
+ amounts[0] = splitAmount;
613
+ for (let i = 0; i < route.tokenPath.length - 1; i++) {
614
+ amounts[i + 1] = route.pools[i].getOutputAmount(amounts[i]);
615
+ }
616
+ inputAmount = splitAmount;
617
+ outputAmount = amounts[amounts.length - 1];
618
+ } else {
619
+ amounts[amounts.length - 1] = splitAmount;
620
+ for (let i = route.tokenPath.length - 1; i > 0; i--) {
621
+ amounts[i - 1] = route.pools[i - 1].getInputAmount(amounts[i]);
622
+ }
623
+ inputAmount = amounts[0];
624
+ outputAmount = splitAmount;
625
+ }
626
+ if (!outputAmount.greaterThan(0)) {
627
+ return null;
628
+ }
629
+ return {
630
+ percent,
631
+ route: route,
632
+ inputAmount,
633
+ outputAmount
634
+ };
635
+ };
636
+ const percentToQuotes = {};
610
637
  for (const percent of percents) {
611
- percentToTrades.set(percent, []);
638
+ percentToQuotes[percent] = [];
612
639
  }
613
-
614
- // Оптимизируем внутренний цикл - группируем вычисления по маршрутам
615
640
  for (const route of validRoutes) {
616
641
  for (const percent of percents) {
617
642
  const splitAmount = percentToAmount.get(percent);
618
643
  try {
619
- const trade = Trade.fromRoute(route, splitAmount, tradeType, percent);
620
-
621
- // Only check outputAmount > 0, skip expensive priceImpact calculation
622
- if (trade.outputAmount.greaterThan(0)) {
623
- percentToTrades.get(percent).push(trade);
644
+ const quote = quoteRoute(route, splitAmount, percent);
645
+ if (quote) {
646
+ percentToQuotes[percent].push(quote);
624
647
  }
625
648
  } catch (error) {
626
649
  if (error.isInsufficientReservesError || error.isInsufficientInputAmountError) {
@@ -630,25 +653,19 @@ let Trade = exports.Trade = /*#__PURE__*/function () {
630
653
  }
631
654
  }
632
655
  }
633
-
634
- // Преобразуем Map обратно в объект для совместимости с getBestSwapRoute
635
- const percentToTradesObj = {};
636
- percentToTrades.forEach((trades, percent) => {
637
- percentToTradesObj[percent] = trades;
638
- });
639
- const bestTrades = (0, _getBestSwapRoute.getBestSwapRoute)(tradeType, percentToTradesObj, percents, swapConfig);
640
- if (!bestTrades) return null;
641
- const routes = bestTrades.map(({
656
+ const bestQuotes = (0, _getBestSwapRoute.getBestSwapRoute)(tradeType, percentToQuotes, percents, swapConfig);
657
+ if (!bestQuotes) return null;
658
+ const routes = bestQuotes.map(({
642
659
  inputAmount,
643
660
  outputAmount,
644
661
  route,
645
- swaps
662
+ percent
646
663
  }) => {
647
664
  return {
648
665
  inputAmount,
649
666
  outputAmount,
650
667
  route,
651
- percent: swaps[0].percent
668
+ percent
652
669
  };
653
670
  });
654
671
 
@@ -164,10 +164,7 @@ export let Pool = /*#__PURE__*/function () {
164
164
  const outputToken = zeroForOne ? this.tokenB : this.tokenA;
165
165
  const outputAmount = zeroForOne ? amountB : amountA;
166
166
  const amountIn = zeroForOne ? amountA : amountB;
167
-
168
- //console.log((amountIn === inputAmount.quotient))
169
- if (!(inputAmount.quotient >= amountIn)) {
170
- //if (!(amountIn === inputAmount.quotient)) {
167
+ if (!(amountIn === inputAmount.quotient)) {
171
168
  throw new InsufficientInputAmountError();
172
169
  }
173
170
  return CurrencyAmount.fromRawAmount(outputToken, outputAmount * NEGATIVE_ONE);
@@ -595,23 +595,46 @@ export let Trade = /*#__PURE__*/function () {
595
595
  for (const percent of percents) {
596
596
  percentToAmount.set(percent, amount.multiply(percent).divide(100));
597
597
  }
598
-
599
- // Используем Map вместо объекта для лучшей производительности
600
- const percentToTrades = new Map();
598
+ const quoteRoute = (route, splitAmount, percent) => {
599
+ const amounts = new Array(route.tokenPath.length);
600
+ let inputAmount;
601
+ let outputAmount;
602
+ if (tradeType === TradeType.EXACT_INPUT) {
603
+ amounts[0] = splitAmount;
604
+ for (let i = 0; i < route.tokenPath.length - 1; i++) {
605
+ amounts[i + 1] = route.pools[i].getOutputAmount(amounts[i]);
606
+ }
607
+ inputAmount = splitAmount;
608
+ outputAmount = amounts[amounts.length - 1];
609
+ } else {
610
+ amounts[amounts.length - 1] = splitAmount;
611
+ for (let i = route.tokenPath.length - 1; i > 0; i--) {
612
+ amounts[i - 1] = route.pools[i - 1].getInputAmount(amounts[i]);
613
+ }
614
+ inputAmount = amounts[0];
615
+ outputAmount = splitAmount;
616
+ }
617
+ if (!outputAmount.greaterThan(0)) {
618
+ return null;
619
+ }
620
+ return {
621
+ percent,
622
+ route: route,
623
+ inputAmount,
624
+ outputAmount
625
+ };
626
+ };
627
+ const percentToQuotes = {};
601
628
  for (const percent of percents) {
602
- percentToTrades.set(percent, []);
629
+ percentToQuotes[percent] = [];
603
630
  }
604
-
605
- // Оптимизируем внутренний цикл - группируем вычисления по маршрутам
606
631
  for (const route of validRoutes) {
607
632
  for (const percent of percents) {
608
633
  const splitAmount = percentToAmount.get(percent);
609
634
  try {
610
- const trade = Trade.fromRoute(route, splitAmount, tradeType, percent);
611
-
612
- // Only check outputAmount > 0, skip expensive priceImpact calculation
613
- if (trade.outputAmount.greaterThan(0)) {
614
- percentToTrades.get(percent).push(trade);
635
+ const quote = quoteRoute(route, splitAmount, percent);
636
+ if (quote) {
637
+ percentToQuotes[percent].push(quote);
615
638
  }
616
639
  } catch (error) {
617
640
  if (error.isInsufficientReservesError || error.isInsufficientInputAmountError) {
@@ -621,25 +644,19 @@ export let Trade = /*#__PURE__*/function () {
621
644
  }
622
645
  }
623
646
  }
624
-
625
- // Преобразуем Map обратно в объект для совместимости с getBestSwapRoute
626
- const percentToTradesObj = {};
627
- percentToTrades.forEach((trades, percent) => {
628
- percentToTradesObj[percent] = trades;
629
- });
630
- const bestTrades = getBestSwapRoute(tradeType, percentToTradesObj, percents, swapConfig);
631
- if (!bestTrades) return null;
632
- const routes = bestTrades.map(({
647
+ const bestQuotes = getBestSwapRoute(tradeType, percentToQuotes, percents, swapConfig);
648
+ if (!bestQuotes) return null;
649
+ const routes = bestQuotes.map(({
633
650
  inputAmount,
634
651
  outputAmount,
635
652
  route,
636
- swaps
653
+ percent
637
654
  }) => {
638
655
  return {
639
656
  inputAmount,
640
657
  outputAmount,
641
658
  route,
642
- percent: swaps[0].percent
659
+ percent
643
660
  };
644
661
  });
645
662
 
@@ -1,106 +1,78 @@
1
1
  import Queue from 'mnemonist/queue';
2
- import FixedReverseHeap from 'mnemonist/fixed-reverse-heap';
3
2
  import { TradeType } from '../internalConstants';
4
3
  export function getBestSwapRoute(routeType, percentToQuotes, percents, swapRouteConfig = {
5
4
  minSplits: 1,
6
5
  maxSplits: 8
7
6
  }) {
8
- // Извлекаем уникальные пулы из всех маршрутов
9
- const allPools = [...new Set(Object.values(percentToQuotes).flatMap(routes => routes.flatMap(r => r.route.pools)))];
7
+ const {
8
+ minSplits,
9
+ maxSplits,
10
+ branchFactor = 1,
11
+ candidateLimit = 0
12
+ } = swapRouteConfig;
13
+ const branchWidth = Math.max(1, branchFactor);
14
+ const quoteCompFn = routeType === TradeType.EXACT_INPUT ? (a, b) => a.greaterThan(b) : (a, b) => a.lessThan(b);
15
+ const quoteOf = routeType === TradeType.EXACT_INPUT ? q => q.outputAmount : q => q.inputAmount;
10
16
 
11
- // Создаем битовую карту для пулов (bigint для поддержки >31 пулов)
17
+ // Build pool bit ids without intermediate arrays.
12
18
  const poolToBit = new Map();
13
19
  let bitCounter = BigInt(0);
14
- for (const pool of allPools) {
15
- poolToBit.set(pool.id, BigInt(1) << bitCounter++);
20
+ for (const quotes of Object.values(percentToQuotes)) {
21
+ for (const quote of quotes) {
22
+ for (const pool of quote.route.pools) {
23
+ if (!poolToBit.has(pool.id)) {
24
+ poolToBit.set(pool.id, BigInt(1) << bitCounter);
25
+ bitCounter += BigInt(1);
26
+ }
27
+ }
28
+ }
16
29
  }
17
-
18
- // Предвычисляем маски для всех маршрутов
19
30
  const routeToMask = new Map();
20
- for (const routes of Object.values(percentToQuotes)) {
21
- for (const route of routes) {
31
+ for (const quotes of Object.values(percentToQuotes)) {
32
+ for (const quote of quotes) {
22
33
  let mask = BigInt(0);
23
- for (const pool of route.route.pools) {
34
+ for (const pool of quote.route.pools) {
24
35
  mask |= poolToBit.get(pool.id);
25
36
  }
26
- routeToMask.set(route, mask);
37
+ routeToMask.set(quote, mask);
27
38
  }
28
39
  }
29
-
30
- // Сортируем маршруты для каждого процента
31
40
  const percentToSortedQuotes = {};
32
41
  for (const percent in percentToQuotes) {
33
- percentToSortedQuotes[percent] = percentToQuotes[percent].sort((a, b) => routeType === TradeType.EXACT_INPUT ? a.outputAmount.greaterThan(b.outputAmount) ? -1 : 1 : a.inputAmount.lessThan(b.inputAmount) ? -1 : 1);
42
+ const sorted = percentToQuotes[percent].sort((a, b) => {
43
+ const qa = quoteOf(a);
44
+ const qb = quoteOf(b);
45
+ return quoteCompFn(qa, qb) ? -1 : 1;
46
+ });
47
+ percentToSortedQuotes[percent] = candidateLimit > 0 && sorted.length > candidateLimit ? sorted.slice(0, candidateLimit) : sorted;
34
48
  }
35
-
36
- // Функция сравнения для типа торговли
37
- const quoteCompFn = routeType === TradeType.EXACT_INPUT ? (a, b) => a.greaterThan(b) : (a, b) => a.lessThan(b);
38
-
39
- // Функция суммирования CurrencyAmount
40
- const sumFn = currencyAmounts => {
41
- let sum = currencyAmounts[0];
42
- for (let i = 1; i < currencyAmounts.length; i++) {
43
- sum = sum.add(currencyAmounts[i]);
44
- }
45
- return sum;
46
- };
47
49
  let bestQuote;
48
50
  let bestSwap;
49
-
50
- // Храним лучшие маршруты для каждого уровня разбиения (максимум 3)
51
- const bestSwapsPerSplit = new FixedReverseHeap(Array, (a, b) => quoteCompFn(a.quote, b.quote) ? -1 : 1, 3);
52
- const {
53
- minSplits,
54
- maxSplits
55
- } = swapRouteConfig;
56
-
57
- // Проверяем наличие маршрута для 100% и инициализируем начальные данные
58
- if (!percentToSortedQuotes[100] || percentToSortedQuotes[100].length === 0 || minSplits > 1) {
51
+ if ((!percentToSortedQuotes[100] || percentToSortedQuotes[100].length === 0) && minSplits <= 1) {
59
52
  console.log('Did not find a valid route without any splits. Continuing search anyway.');
60
- } else {
61
- bestQuote = percentToSortedQuotes[100][0].outputAmount;
53
+ } else if (minSplits <= 1 && percentToSortedQuotes[100] && percentToSortedQuotes[100][0]) {
62
54
  bestSwap = [percentToSortedQuotes[100][0]];
63
- for (const routeWithQuote of percentToSortedQuotes[100].slice(0, 5)) {
64
- bestSwapsPerSplit.push({
65
- quote: routeWithQuote.outputAmount,
66
- routes: [routeWithQuote]
67
- });
68
- }
55
+ bestQuote = quoteOf(percentToSortedQuotes[100][0]);
69
56
  }
70
-
71
- // Очередь для обработки комбинаций маршрутов (с usedMask для быстрой проверки overlap)
72
57
  const queue = new Queue();
73
58
  if (percents.length === 0) return null;
74
-
75
- // Инициализируем очередь с топ-2 маршрутами для каждого процента
76
59
  for (let i = percents.length - 1; i >= 0; i--) {
77
60
  const percent = percents[i];
78
- if (!percentToSortedQuotes[percent] || percentToSortedQuotes[percent].length === 0) continue;
79
- const topRoutes = percentToSortedQuotes[percent].slice(0, 2);
80
- if (topRoutes[0]) {
61
+ const candidates = percentToSortedQuotes[percent];
62
+ if (!candidates || candidates.length === 0) continue;
63
+ const seeds = candidates.slice(0, branchWidth);
64
+ for (const seed of seeds) {
81
65
  queue.enqueue({
82
- curRoutes: [topRoutes[0]],
66
+ curRoutes: [seed],
83
67
  percentIndex: i,
84
68
  remainingPercent: 100 - percent,
85
- usedMask: routeToMask.get(topRoutes[0]),
86
- special: false
87
- });
88
- }
89
- if (topRoutes[1]) {
90
- queue.enqueue({
91
- curRoutes: [topRoutes[1]],
92
- percentIndex: i,
93
- remainingPercent: 100 - percent,
94
- usedMask: routeToMask.get(topRoutes[1]),
95
- special: true
69
+ usedMask: routeToMask.get(seed),
70
+ quoteSoFar: quoteOf(seed)
96
71
  });
97
72
  }
98
73
  }
99
74
  let splits = 1;
100
-
101
- // Основной цикл поиска лучших маршрутов
102
75
  while (queue.size > 0) {
103
- bestSwapsPerSplit.clear();
104
76
  let layer = queue.size;
105
77
  splits++;
106
78
  if (splits >= 3 && bestSwap && bestSwap.length < splits - 1) {
@@ -116,38 +88,37 @@ export function getBestSwapRoute(routeType, percentToQuotes, percents, swapRoute
116
88
  curRoutes,
117
89
  percentIndex,
118
90
  usedMask,
119
- special
91
+ quoteSoFar
120
92
  } = queue.dequeue();
121
93
  for (let i = percentIndex; i >= 0; i--) {
122
- const percentA = percents[i];
123
- if (percentA > remainingPercent) continue;
124
- if (!percentToSortedQuotes[percentA] || percentToSortedQuotes[percentA].length === 0) continue;
125
- const candidateRoutesA = percentToSortedQuotes[percentA];
126
- const routeWithQuoteA = findFirstRouteNotUsingUsedPools(usedMask, candidateRoutesA, routeToMask);
127
- if (!routeWithQuoteA) continue;
128
- const remainingPercentNew = remainingPercent - percentA;
129
- const curRoutesNew = curRoutes.slice();
130
- curRoutesNew.push(routeWithQuoteA);
131
- const usedMaskNew = usedMask | routeToMask.get(routeWithQuoteA);
132
- if (remainingPercentNew === 0 && splits >= minSplits) {
133
- const quotesNew = curRoutesNew.map(r => r.outputAmount);
134
- const quoteNew = sumFn(quotesNew);
135
- bestSwapsPerSplit.push({
136
- quote: quoteNew,
137
- routes: curRoutesNew
138
- });
139
- if (!bestQuote || quoteCompFn(quoteNew, bestQuote)) {
140
- bestQuote = quoteNew;
141
- bestSwap = curRoutesNew;
94
+ const percent = percents[i];
95
+ if (percent > remainingPercent) continue;
96
+ const candidates = percentToSortedQuotes[percent];
97
+ if (!candidates || candidates.length === 0) continue;
98
+ const routeCandidates = findRoutesNotUsingUsedPools(usedMask, candidates, routeToMask, branchWidth);
99
+ if (routeCandidates.length === 0) continue;
100
+ for (const candidate of routeCandidates) {
101
+ const remainingPercentNew = remainingPercent - percent;
102
+ const usedMaskNew = usedMask | routeToMask.get(candidate);
103
+ const quoteNew = quoteSoFar.add(quoteOf(candidate));
104
+ if (remainingPercentNew === 0 && splits >= minSplits) {
105
+ const curRoutesNew = curRoutes.slice();
106
+ curRoutesNew.push(candidate);
107
+ if (!bestQuote || quoteCompFn(quoteNew, bestQuote)) {
108
+ bestQuote = quoteNew;
109
+ bestSwap = curRoutesNew;
110
+ }
111
+ } else {
112
+ const curRoutesNew = curRoutes.slice();
113
+ curRoutesNew.push(candidate);
114
+ queue.enqueue({
115
+ curRoutes: curRoutesNew,
116
+ remainingPercent: remainingPercentNew,
117
+ percentIndex: i,
118
+ usedMask: usedMaskNew,
119
+ quoteSoFar: quoteNew
120
+ });
142
121
  }
143
- } else {
144
- queue.enqueue({
145
- curRoutes: curRoutesNew,
146
- remainingPercent: remainingPercentNew,
147
- percentIndex: i,
148
- usedMask: usedMaskNew,
149
- special
150
- });
151
122
  }
152
123
  }
153
124
  }
@@ -156,18 +127,16 @@ export function getBestSwapRoute(routeType, percentToQuotes, percents, swapRoute
156
127
  console.log('Could not find a valid swap');
157
128
  return null;
158
129
  }
159
- const quote = sumFn(bestSwap.map(routeWithValidQuote => routeWithValidQuote.outputAmount));
160
- const routeWithQuotes = bestSwap.sort((routeAmountA, routeAmountB) => routeAmountB.outputAmount.greaterThan(routeAmountA.outputAmount) ? 1 : -1);
161
- return routeWithQuotes;
130
+ return bestSwap;
162
131
  }
163
-
164
- // Вспомогательная функция для поиска маршрута без пересекающихся пулов
165
- const findFirstRouteNotUsingUsedPools = (usedMask, candidateRoutes, routeToMask) => {
132
+ const findRoutesNotUsingUsedPools = (usedMask, candidateRoutes, routeToMask, limit) => {
133
+ const result = [];
166
134
  for (const candidate of candidateRoutes) {
167
135
  const candidateMask = routeToMask.get(candidate);
168
136
  if ((candidateMask & usedMask) === BigInt(0)) {
169
- return candidate;
137
+ result.push(candidate);
138
+ if (result.length >= limit) return result;
170
139
  }
171
140
  }
172
- return null;
141
+ return result;
173
142
  };
@@ -7,6 +7,7 @@ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e =
7
7
  import { Trade } from '../entities';
8
8
  import { CurrencyAmount } from '../entities/fractions';
9
9
  import { TradeType } from '../internalConstants';
10
+ import { getBestSwapRoute } from './getBestSwapRoute';
10
11
 
11
12
  // WASM module - lazy loaded
12
13
  let wasmModule = null;
@@ -146,6 +147,8 @@ export let WASMTradeCalculator = /*#__PURE__*/function () {
146
147
  amountOut: CurrencyAmount.fromRawAmount(route.output, BigInt(result.amountOut)),
147
148
  priceImpact: result.priceImpact
148
149
  });
150
+ } else {
151
+ trades.push(null);
149
152
  }
150
153
  }
151
154
  }
@@ -212,37 +215,34 @@ export async function bestTradeWithSplitWASM(routes, amount, percents, tradeType
212
215
  const allTrades = calculator.calculateTradesBatch(routes, splitAmounts);
213
216
 
214
217
  // Group trades by percent
215
- const tradesByPercent = {};
216
- let tradeIdx = 0;
218
+ const quotesByPercent = {};
217
219
  for (let i = 0; i < percents.length; i++) {
218
220
  const percent = percents[i];
219
- tradesByPercent[percent] = [];
220
- for (const route of routes) {
221
- const tradeDat = allTrades[tradeIdx++];
221
+ quotesByPercent[percent] = [];
222
+ }
223
+ const amountsPerRoute = splitAmounts.length;
224
+ for (let routeIndex = 0; routeIndex < routes.length; routeIndex++) {
225
+ for (let percentIndex = 0; percentIndex < percents.length; percentIndex++) {
226
+ const flatIndex = routeIndex * amountsPerRoute + percentIndex;
227
+ const tradeDat = allTrades[flatIndex];
222
228
  if (tradeDat) {
223
- const trade = Trade.createUncheckedTrade({
229
+ const percent = percents[percentIndex];
230
+ quotesByPercent[percent].push({
224
231
  route: tradeDat.route,
225
232
  inputAmount: tradeDat.amountIn,
226
233
  outputAmount: tradeDat.amountOut,
227
- tradeType,
228
234
  percent
229
235
  });
230
- tradesByPercent[percent].push(trade);
231
236
  }
232
237
  }
233
238
  }
234
-
235
- // Use existing getBestSwapRoute logic
236
- const {
237
- getBestSwapRoute
238
- } = require('./getBestSwapRoute');
239
- const bestTrades = getBestSwapRoute(tradeType, tradesByPercent, percents, swapConfig);
239
+ const bestTrades = getBestSwapRoute(tradeType, quotesByPercent, percents, swapConfig);
240
240
  if (!bestTrades) return null;
241
241
  const routeData = bestTrades.map(trade => ({
242
242
  inputAmount: trade.inputAmount,
243
243
  outputAmount: trade.outputAmount,
244
244
  route: trade.route,
245
- percent: trade.swaps[0].percent
245
+ percent: trade.percent
246
246
  }));
247
247
  return Trade.createUncheckedTradeWithMultipleRoutes({
248
248
  routes: routeData,
@@ -1,9 +1,20 @@
1
1
  import { Currency } from '../entities/currency';
2
- import { Trade } from '../entities/trade';
2
+ import { Route } from '../entities/route';
3
+ import { CurrencyAmount } from '../entities/fractions';
3
4
  import { TradeType } from '../internalConstants';
4
- export declare function getBestSwapRoute(routeType: TradeType, percentToQuotes: {
5
- [percent: number]: Trade<Currency, Currency, TradeType>[];
6
- }, percents: number[], swapRouteConfig?: {
5
+ export interface SplitRouteQuote {
6
+ percent: number;
7
+ route: Route<Currency, Currency>;
8
+ inputAmount: CurrencyAmount<Currency>;
9
+ outputAmount: CurrencyAmount<Currency>;
10
+ }
11
+ interface SwapRouteConfig {
7
12
  minSplits: number;
8
13
  maxSplits: number;
9
- }): Trade<Currency, Currency, TradeType>[] | null;
14
+ branchFactor?: number;
15
+ candidateLimit?: number;
16
+ }
17
+ export declare function getBestSwapRoute(routeType: TradeType, percentToQuotes: {
18
+ [percent: number]: SplitRouteQuote[];
19
+ }, percents: number[], swapRouteConfig?: SwapRouteConfig): SplitRouteQuote[] | null;
20
+ export {};
@@ -5,109 +5,81 @@ Object.defineProperty(exports, "__esModule", {
5
5
  });
6
6
  exports.getBestSwapRoute = getBestSwapRoute;
7
7
  var _queue = _interopRequireDefault(require("mnemonist/queue"));
8
- var _fixedReverseHeap = _interopRequireDefault(require("mnemonist/fixed-reverse-heap"));
9
8
  var _internalConstants = require("../internalConstants");
10
9
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
11
10
  function getBestSwapRoute(routeType, percentToQuotes, percents, swapRouteConfig = {
12
11
  minSplits: 1,
13
12
  maxSplits: 8
14
13
  }) {
15
- // Извлекаем уникальные пулы из всех маршрутов
16
- const allPools = [...new Set(Object.values(percentToQuotes).flatMap(routes => routes.flatMap(r => r.route.pools)))];
14
+ const {
15
+ minSplits,
16
+ maxSplits,
17
+ branchFactor = 1,
18
+ candidateLimit = 0
19
+ } = swapRouteConfig;
20
+ const branchWidth = Math.max(1, branchFactor);
21
+ const quoteCompFn = routeType === _internalConstants.TradeType.EXACT_INPUT ? (a, b) => a.greaterThan(b) : (a, b) => a.lessThan(b);
22
+ const quoteOf = routeType === _internalConstants.TradeType.EXACT_INPUT ? q => q.outputAmount : q => q.inputAmount;
17
23
 
18
- // Создаем битовую карту для пулов (bigint для поддержки >31 пулов)
24
+ // Build pool bit ids without intermediate arrays.
19
25
  const poolToBit = new Map();
20
26
  let bitCounter = BigInt(0);
21
- for (const pool of allPools) {
22
- poolToBit.set(pool.id, BigInt(1) << bitCounter++);
27
+ for (const quotes of Object.values(percentToQuotes)) {
28
+ for (const quote of quotes) {
29
+ for (const pool of quote.route.pools) {
30
+ if (!poolToBit.has(pool.id)) {
31
+ poolToBit.set(pool.id, BigInt(1) << bitCounter);
32
+ bitCounter += BigInt(1);
33
+ }
34
+ }
35
+ }
23
36
  }
24
-
25
- // Предвычисляем маски для всех маршрутов
26
37
  const routeToMask = new Map();
27
- for (const routes of Object.values(percentToQuotes)) {
28
- for (const route of routes) {
38
+ for (const quotes of Object.values(percentToQuotes)) {
39
+ for (const quote of quotes) {
29
40
  let mask = BigInt(0);
30
- for (const pool of route.route.pools) {
41
+ for (const pool of quote.route.pools) {
31
42
  mask |= poolToBit.get(pool.id);
32
43
  }
33
- routeToMask.set(route, mask);
44
+ routeToMask.set(quote, mask);
34
45
  }
35
46
  }
36
-
37
- // Сортируем маршруты для каждого процента
38
47
  const percentToSortedQuotes = {};
39
48
  for (const percent in percentToQuotes) {
40
- percentToSortedQuotes[percent] = percentToQuotes[percent].sort((a, b) => routeType === _internalConstants.TradeType.EXACT_INPUT ? a.outputAmount.greaterThan(b.outputAmount) ? -1 : 1 : a.inputAmount.lessThan(b.inputAmount) ? -1 : 1);
49
+ const sorted = percentToQuotes[percent].sort((a, b) => {
50
+ const qa = quoteOf(a);
51
+ const qb = quoteOf(b);
52
+ return quoteCompFn(qa, qb) ? -1 : 1;
53
+ });
54
+ percentToSortedQuotes[percent] = candidateLimit > 0 && sorted.length > candidateLimit ? sorted.slice(0, candidateLimit) : sorted;
41
55
  }
42
-
43
- // Функция сравнения для типа торговли
44
- const quoteCompFn = routeType === _internalConstants.TradeType.EXACT_INPUT ? (a, b) => a.greaterThan(b) : (a, b) => a.lessThan(b);
45
-
46
- // Функция суммирования CurrencyAmount
47
- const sumFn = currencyAmounts => {
48
- let sum = currencyAmounts[0];
49
- for (let i = 1; i < currencyAmounts.length; i++) {
50
- sum = sum.add(currencyAmounts[i]);
51
- }
52
- return sum;
53
- };
54
56
  let bestQuote;
55
57
  let bestSwap;
56
-
57
- // Храним лучшие маршруты для каждого уровня разбиения (максимум 3)
58
- const bestSwapsPerSplit = new _fixedReverseHeap.default(Array, (a, b) => quoteCompFn(a.quote, b.quote) ? -1 : 1, 3);
59
- const {
60
- minSplits,
61
- maxSplits
62
- } = swapRouteConfig;
63
-
64
- // Проверяем наличие маршрута для 100% и инициализируем начальные данные
65
- if (!percentToSortedQuotes[100] || percentToSortedQuotes[100].length === 0 || minSplits > 1) {
58
+ if ((!percentToSortedQuotes[100] || percentToSortedQuotes[100].length === 0) && minSplits <= 1) {
66
59
  console.log('Did not find a valid route without any splits. Continuing search anyway.');
67
- } else {
68
- bestQuote = percentToSortedQuotes[100][0].outputAmount;
60
+ } else if (minSplits <= 1 && percentToSortedQuotes[100] && percentToSortedQuotes[100][0]) {
69
61
  bestSwap = [percentToSortedQuotes[100][0]];
70
- for (const routeWithQuote of percentToSortedQuotes[100].slice(0, 5)) {
71
- bestSwapsPerSplit.push({
72
- quote: routeWithQuote.outputAmount,
73
- routes: [routeWithQuote]
74
- });
75
- }
62
+ bestQuote = quoteOf(percentToSortedQuotes[100][0]);
76
63
  }
77
-
78
- // Очередь для обработки комбинаций маршрутов (с usedMask для быстрой проверки overlap)
79
64
  const queue = new _queue.default();
80
65
  if (percents.length === 0) return null;
81
-
82
- // Инициализируем очередь с топ-2 маршрутами для каждого процента
83
66
  for (let i = percents.length - 1; i >= 0; i--) {
84
67
  const percent = percents[i];
85
- if (!percentToSortedQuotes[percent] || percentToSortedQuotes[percent].length === 0) continue;
86
- const topRoutes = percentToSortedQuotes[percent].slice(0, 2);
87
- if (topRoutes[0]) {
68
+ const candidates = percentToSortedQuotes[percent];
69
+ if (!candidates || candidates.length === 0) continue;
70
+ const seeds = candidates.slice(0, branchWidth);
71
+ for (const seed of seeds) {
88
72
  queue.enqueue({
89
- curRoutes: [topRoutes[0]],
73
+ curRoutes: [seed],
90
74
  percentIndex: i,
91
75
  remainingPercent: 100 - percent,
92
- usedMask: routeToMask.get(topRoutes[0]),
93
- special: false
94
- });
95
- }
96
- if (topRoutes[1]) {
97
- queue.enqueue({
98
- curRoutes: [topRoutes[1]],
99
- percentIndex: i,
100
- remainingPercent: 100 - percent,
101
- usedMask: routeToMask.get(topRoutes[1]),
102
- special: true
76
+ usedMask: routeToMask.get(seed),
77
+ quoteSoFar: quoteOf(seed)
103
78
  });
104
79
  }
105
80
  }
106
81
  let splits = 1;
107
-
108
- // Основной цикл поиска лучших маршрутов
109
82
  while (queue.size > 0) {
110
- bestSwapsPerSplit.clear();
111
83
  let layer = queue.size;
112
84
  splits++;
113
85
  if (splits >= 3 && bestSwap && bestSwap.length < splits - 1) {
@@ -123,38 +95,37 @@ function getBestSwapRoute(routeType, percentToQuotes, percents, swapRouteConfig
123
95
  curRoutes,
124
96
  percentIndex,
125
97
  usedMask,
126
- special
98
+ quoteSoFar
127
99
  } = queue.dequeue();
128
100
  for (let i = percentIndex; i >= 0; i--) {
129
- const percentA = percents[i];
130
- if (percentA > remainingPercent) continue;
131
- if (!percentToSortedQuotes[percentA] || percentToSortedQuotes[percentA].length === 0) continue;
132
- const candidateRoutesA = percentToSortedQuotes[percentA];
133
- const routeWithQuoteA = findFirstRouteNotUsingUsedPools(usedMask, candidateRoutesA, routeToMask);
134
- if (!routeWithQuoteA) continue;
135
- const remainingPercentNew = remainingPercent - percentA;
136
- const curRoutesNew = curRoutes.slice();
137
- curRoutesNew.push(routeWithQuoteA);
138
- const usedMaskNew = usedMask | routeToMask.get(routeWithQuoteA);
139
- if (remainingPercentNew === 0 && splits >= minSplits) {
140
- const quotesNew = curRoutesNew.map(r => r.outputAmount);
141
- const quoteNew = sumFn(quotesNew);
142
- bestSwapsPerSplit.push({
143
- quote: quoteNew,
144
- routes: curRoutesNew
145
- });
146
- if (!bestQuote || quoteCompFn(quoteNew, bestQuote)) {
147
- bestQuote = quoteNew;
148
- bestSwap = curRoutesNew;
101
+ const percent = percents[i];
102
+ if (percent > remainingPercent) continue;
103
+ const candidates = percentToSortedQuotes[percent];
104
+ if (!candidates || candidates.length === 0) continue;
105
+ const routeCandidates = findRoutesNotUsingUsedPools(usedMask, candidates, routeToMask, branchWidth);
106
+ if (routeCandidates.length === 0) continue;
107
+ for (const candidate of routeCandidates) {
108
+ const remainingPercentNew = remainingPercent - percent;
109
+ const usedMaskNew = usedMask | routeToMask.get(candidate);
110
+ const quoteNew = quoteSoFar.add(quoteOf(candidate));
111
+ if (remainingPercentNew === 0 && splits >= minSplits) {
112
+ const curRoutesNew = curRoutes.slice();
113
+ curRoutesNew.push(candidate);
114
+ if (!bestQuote || quoteCompFn(quoteNew, bestQuote)) {
115
+ bestQuote = quoteNew;
116
+ bestSwap = curRoutesNew;
117
+ }
118
+ } else {
119
+ const curRoutesNew = curRoutes.slice();
120
+ curRoutesNew.push(candidate);
121
+ queue.enqueue({
122
+ curRoutes: curRoutesNew,
123
+ remainingPercent: remainingPercentNew,
124
+ percentIndex: i,
125
+ usedMask: usedMaskNew,
126
+ quoteSoFar: quoteNew
127
+ });
149
128
  }
150
- } else {
151
- queue.enqueue({
152
- curRoutes: curRoutesNew,
153
- remainingPercent: remainingPercentNew,
154
- percentIndex: i,
155
- usedMask: usedMaskNew,
156
- special
157
- });
158
129
  }
159
130
  }
160
131
  }
@@ -163,18 +134,16 @@ function getBestSwapRoute(routeType, percentToQuotes, percents, swapRouteConfig
163
134
  console.log('Could not find a valid swap');
164
135
  return null;
165
136
  }
166
- const quote = sumFn(bestSwap.map(routeWithValidQuote => routeWithValidQuote.outputAmount));
167
- const routeWithQuotes = bestSwap.sort((routeAmountA, routeAmountB) => routeAmountB.outputAmount.greaterThan(routeAmountA.outputAmount) ? 1 : -1);
168
- return routeWithQuotes;
137
+ return bestSwap;
169
138
  }
170
-
171
- // Вспомогательная функция для поиска маршрута без пересекающихся пулов
172
- const findFirstRouteNotUsingUsedPools = (usedMask, candidateRoutes, routeToMask) => {
139
+ const findRoutesNotUsingUsedPools = (usedMask, candidateRoutes, routeToMask, limit) => {
140
+ const result = [];
173
141
  for (const candidate of candidateRoutes) {
174
142
  const candidateMask = routeToMask.get(candidate);
175
143
  if ((candidateMask & usedMask) === BigInt(0)) {
176
- return candidate;
144
+ result.push(candidate);
145
+ if (result.length >= limit) return result;
177
146
  }
178
147
  }
179
- return null;
148
+ return result;
180
149
  };
@@ -26,7 +26,7 @@ export declare class WASMTradeCalculator {
26
26
  amountIn: CurrencyAmount<Token>;
27
27
  amountOut: CurrencyAmount<Token>;
28
28
  priceImpact: number;
29
- }>;
29
+ } | null>;
30
30
  /**
31
31
  * Clear all pools from memory
32
32
  */
@@ -9,6 +9,7 @@ exports.createTradeFromRouteWASM = createTradeFromRouteWASM;
9
9
  var _entities = require("../entities");
10
10
  var _fractions = require("../entities/fractions");
11
11
  var _internalConstants = require("../internalConstants");
12
+ var _getBestSwapRoute = require("./getBestSwapRoute");
12
13
  function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
13
14
  function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
14
15
  function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; }
@@ -153,6 +154,8 @@ let WASMTradeCalculator = exports.WASMTradeCalculator = /*#__PURE__*/function ()
153
154
  amountOut: _fractions.CurrencyAmount.fromRawAmount(route.output, BigInt(result.amountOut)),
154
155
  priceImpact: result.priceImpact
155
156
  });
157
+ } else {
158
+ trades.push(null);
156
159
  }
157
160
  }
158
161
  }
@@ -218,37 +221,34 @@ async function bestTradeWithSplitWASM(routes, amount, percents, tradeType, pools
218
221
  const allTrades = calculator.calculateTradesBatch(routes, splitAmounts);
219
222
 
220
223
  // Group trades by percent
221
- const tradesByPercent = {};
222
- let tradeIdx = 0;
224
+ const quotesByPercent = {};
223
225
  for (let i = 0; i < percents.length; i++) {
224
226
  const percent = percents[i];
225
- tradesByPercent[percent] = [];
226
- for (const route of routes) {
227
- const tradeDat = allTrades[tradeIdx++];
227
+ quotesByPercent[percent] = [];
228
+ }
229
+ const amountsPerRoute = splitAmounts.length;
230
+ for (let routeIndex = 0; routeIndex < routes.length; routeIndex++) {
231
+ for (let percentIndex = 0; percentIndex < percents.length; percentIndex++) {
232
+ const flatIndex = routeIndex * amountsPerRoute + percentIndex;
233
+ const tradeDat = allTrades[flatIndex];
228
234
  if (tradeDat) {
229
- const trade = _entities.Trade.createUncheckedTrade({
235
+ const percent = percents[percentIndex];
236
+ quotesByPercent[percent].push({
230
237
  route: tradeDat.route,
231
238
  inputAmount: tradeDat.amountIn,
232
239
  outputAmount: tradeDat.amountOut,
233
- tradeType,
234
240
  percent
235
241
  });
236
- tradesByPercent[percent].push(trade);
237
242
  }
238
243
  }
239
244
  }
240
-
241
- // Use existing getBestSwapRoute logic
242
- const {
243
- getBestSwapRoute
244
- } = require('./getBestSwapRoute');
245
- const bestTrades = getBestSwapRoute(tradeType, tradesByPercent, percents, swapConfig);
245
+ const bestTrades = (0, _getBestSwapRoute.getBestSwapRoute)(tradeType, quotesByPercent, percents, swapConfig);
246
246
  if (!bestTrades) return null;
247
247
  const routeData = bestTrades.map(trade => ({
248
248
  inputAmount: trade.inputAmount,
249
249
  outputAmount: trade.outputAmount,
250
250
  route: trade.route,
251
- percent: trade.swaps[0].percent
251
+ percent: trade.percent
252
252
  }));
253
253
  return _entities.Trade.createUncheckedTradeWithMultipleRoutes({
254
254
  routes: routeData,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alcorexchange/alcor-swap-sdk",
3
- "version": "1.1.2",
3
+ "version": "1.1.4",
4
4
  "description": "",
5
5
  "main": "build/index.js",
6
6
  "module": "build/esm/index.js",