@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.
- package/build/entities/pool.d.ts +3 -1
- package/build/entities/pool.js +4 -1
- package/build/entities/position.js +4 -0
- package/build/entities/trade.d.ts +9 -1
- package/build/entities/trade.js +33 -8
- package/build/utils/common.d.ts +2 -2
- package/build/utils/common.js +14 -5
- package/build/utils/getBestSwapRoute.d.ts +9 -0
- package/build/utils/getBestSwapRoute.js +150 -0
- package/examples/getPositionAmounts.ts +2 -2
- package/examples/getTrateRoute.ts +7 -5
- package/package.json +4 -2
- package/src/entities/pool.ts +6 -0
- package/src/entities/position.ts +4 -0
- package/src/entities/trade.ts +55 -13
- package/src/utils/common.ts +21 -7
- package/src/utils/getBestSwapRoute.ts +223 -0
package/build/entities/pool.d.ts
CHANGED
|
@@ -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
|
package/build/entities/pool.js
CHANGED
|
@@ -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
|
}
|
package/build/entities/trade.js
CHANGED
|
@@ -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;
|
package/build/utils/common.d.ts
CHANGED
|
@@ -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
|
-
|
|
8
|
-
route: any;
|
|
8
|
+
executionPriceStr: any;
|
|
9
9
|
executionPrice: {
|
|
10
10
|
numerator: any;
|
|
11
11
|
denominator: any;
|
package/build/utils/common.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
60
|
-
const
|
|
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.
|
|
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.
|
|
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",
|
package/src/entities/pool.ts
CHANGED
|
@@ -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,
|
package/src/entities/position.ts
CHANGED
|
@@ -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,
|
package/src/entities/trade.ts
CHANGED
|
@@ -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
|
|
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
|
}
|
package/src/utils/common.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
23
|
-
|
|
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
|
+
}
|