@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.
- package/build/entities/pool.js +1 -4
- package/build/entities/trade.d.ts +2 -0
- package/build/entities/trade.js +39 -22
- package/build/esm/entities/pool.js +1 -4
- package/build/esm/entities/trade.js +39 -22
- package/build/esm/utils/getBestSwapRoute.js +73 -104
- package/build/esm/utils/tradeCalculatorWASM.js +15 -15
- package/build/utils/getBestSwapRoute.d.ts +16 -5
- package/build/utils/getBestSwapRoute.js +73 -104
- package/build/utils/tradeCalculatorWASM.d.ts +1 -1
- package/build/utils/tradeCalculatorWASM.js +15 -15
- package/package.json +1 -1
package/build/entities/pool.js
CHANGED
|
@@ -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
|
}
|
package/build/entities/trade.js
CHANGED
|
@@ -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
|
-
|
|
609
|
-
|
|
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
|
-
|
|
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
|
|
620
|
-
|
|
621
|
-
|
|
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
|
-
|
|
635
|
-
const
|
|
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
|
-
|
|
662
|
+
percent
|
|
646
663
|
}) => {
|
|
647
664
|
return {
|
|
648
665
|
inputAmount,
|
|
649
666
|
outputAmount,
|
|
650
667
|
route,
|
|
651
|
-
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
|
-
|
|
600
|
-
|
|
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
|
-
|
|
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
|
|
611
|
-
|
|
612
|
-
|
|
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
|
-
|
|
626
|
-
const
|
|
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
|
-
|
|
653
|
+
percent
|
|
637
654
|
}) => {
|
|
638
655
|
return {
|
|
639
656
|
inputAmount,
|
|
640
657
|
outputAmount,
|
|
641
658
|
route,
|
|
642
|
-
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
|
-
|
|
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
|
-
//
|
|
17
|
+
// Build pool bit ids without intermediate arrays.
|
|
12
18
|
const poolToBit = new Map();
|
|
13
19
|
let bitCounter = BigInt(0);
|
|
14
|
-
for (const
|
|
15
|
-
|
|
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
|
|
21
|
-
for (const
|
|
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
|
|
34
|
+
for (const pool of quote.route.pools) {
|
|
24
35
|
mask |= poolToBit.get(pool.id);
|
|
25
36
|
}
|
|
26
|
-
routeToMask.set(
|
|
37
|
+
routeToMask.set(quote, mask);
|
|
27
38
|
}
|
|
28
39
|
}
|
|
29
|
-
|
|
30
|
-
// Сортируем маршруты для каждого процента
|
|
31
40
|
const percentToSortedQuotes = {};
|
|
32
41
|
for (const percent in percentToQuotes) {
|
|
33
|
-
|
|
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
|
-
|
|
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
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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: [
|
|
66
|
+
curRoutes: [seed],
|
|
83
67
|
percentIndex: i,
|
|
84
68
|
remainingPercent: 100 - percent,
|
|
85
|
-
usedMask: routeToMask.get(
|
|
86
|
-
|
|
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
|
-
|
|
91
|
+
quoteSoFar
|
|
120
92
|
} = queue.dequeue();
|
|
121
93
|
for (let i = percentIndex; i >= 0; i--) {
|
|
122
|
-
const
|
|
123
|
-
if (
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
if (
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
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
|
-
|
|
137
|
+
result.push(candidate);
|
|
138
|
+
if (result.length >= limit) return result;
|
|
170
139
|
}
|
|
171
140
|
}
|
|
172
|
-
return
|
|
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
|
|
216
|
-
let tradeIdx = 0;
|
|
218
|
+
const quotesByPercent = {};
|
|
217
219
|
for (let i = 0; i < percents.length; i++) {
|
|
218
220
|
const percent = percents[i];
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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
|
|
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.
|
|
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 {
|
|
2
|
+
import { Route } from '../entities/route';
|
|
3
|
+
import { CurrencyAmount } from '../entities/fractions';
|
|
3
4
|
import { TradeType } from '../internalConstants';
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
24
|
+
// Build pool bit ids without intermediate arrays.
|
|
19
25
|
const poolToBit = new Map();
|
|
20
26
|
let bitCounter = BigInt(0);
|
|
21
|
-
for (const
|
|
22
|
-
|
|
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
|
|
28
|
-
for (const
|
|
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
|
|
41
|
+
for (const pool of quote.route.pools) {
|
|
31
42
|
mask |= poolToBit.get(pool.id);
|
|
32
43
|
}
|
|
33
|
-
routeToMask.set(
|
|
44
|
+
routeToMask.set(quote, mask);
|
|
34
45
|
}
|
|
35
46
|
}
|
|
36
|
-
|
|
37
|
-
// Сортируем маршруты для каждого процента
|
|
38
47
|
const percentToSortedQuotes = {};
|
|
39
48
|
for (const percent in percentToQuotes) {
|
|
40
|
-
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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: [
|
|
73
|
+
curRoutes: [seed],
|
|
90
74
|
percentIndex: i,
|
|
91
75
|
remainingPercent: 100 - percent,
|
|
92
|
-
usedMask: routeToMask.get(
|
|
93
|
-
|
|
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
|
-
|
|
98
|
+
quoteSoFar
|
|
127
99
|
} = queue.dequeue();
|
|
128
100
|
for (let i = percentIndex; i >= 0; i--) {
|
|
129
|
-
const
|
|
130
|
-
if (
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
const
|
|
134
|
-
if (
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
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
|
-
|
|
144
|
+
result.push(candidate);
|
|
145
|
+
if (result.length >= limit) return result;
|
|
177
146
|
}
|
|
178
147
|
}
|
|
179
|
-
return
|
|
148
|
+
return result;
|
|
180
149
|
};
|
|
@@ -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
|
|
222
|
-
let tradeIdx = 0;
|
|
224
|
+
const quotesByPercent = {};
|
|
223
225
|
for (let i = 0; i < percents.length; i++) {
|
|
224
226
|
const percent = percents[i];
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
|
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.
|
|
251
|
+
percent: trade.percent
|
|
252
252
|
}));
|
|
253
253
|
return _entities.Trade.createUncheckedTradeWithMultipleRoutes({
|
|
254
254
|
routes: routeData,
|