@alcorexchange/alcor-swap-sdk 1.0.33 → 1.0.35
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 +22 -14
- package/build/entities/trade.d.ts +9 -1
- package/build/entities/trade.js +65 -10
- package/build/errors.d.ts +16 -0
- package/build/errors.js +33 -0
- package/build/utils/common.d.ts +3 -2
- package/build/utils/common.js +20 -7
- package/build/utils/getBestSwapRoute.d.ts +9 -0
- package/build/utils/getBestSwapRoute.js +150 -0
- package/package.json +4 -2
- package/src/entities/pool.ts +31 -24
- package/src/entities/trade.ts +85 -15
- package/src/errors.ts +30 -0
- package/src/utils/common.ts +28 -9
- package/src/utils/getBestSwapRoute.ts +223 -0
package/build/entities/pool.js
CHANGED
|
@@ -17,6 +17,7 @@ const swapMath_1 = require("../utils/swapMath");
|
|
|
17
17
|
const tickMath_1 = require("../utils/tickMath");
|
|
18
18
|
const tickDataProvider_1 = require("./tickDataProvider");
|
|
19
19
|
const tickListDataProvider_1 = require("./tickListDataProvider");
|
|
20
|
+
const errors_1 = require("../errors");
|
|
20
21
|
/**
|
|
21
22
|
* By default, pools will not allow operations that require ticks.
|
|
22
23
|
*/
|
|
@@ -97,8 +98,14 @@ class Pool {
|
|
|
97
98
|
getOutputAmount(inputAmount, sqrtPriceLimitX64) {
|
|
98
99
|
(0, tiny_invariant_1.default)(this.involvesToken(inputAmount.currency), "TOKEN");
|
|
99
100
|
const zeroForOne = inputAmount.currency.equals(this.tokenA);
|
|
100
|
-
const {
|
|
101
|
+
const { amountA, amountB } = this.swap(zeroForOne, inputAmount.quotient, sqrtPriceLimitX64);
|
|
101
102
|
const outputToken = zeroForOne ? this.tokenB : this.tokenA;
|
|
103
|
+
const outputAmount = zeroForOne ? amountB : amountA;
|
|
104
|
+
const amountIn = zeroForOne ? amountA : amountB;
|
|
105
|
+
//console.log(JSBI.equal(amountIn, inputAmount.quotient))
|
|
106
|
+
if (!jsbi_1.default.equal(amountIn, inputAmount.quotient)) {
|
|
107
|
+
throw new errors_1.InsufficientInputAmountError;
|
|
108
|
+
}
|
|
102
109
|
return fractions_1.CurrencyAmount.fromRawAmount(outputToken, jsbi_1.default.multiply(outputAmount, internalConstants_2.NEGATIVE_ONE));
|
|
103
110
|
}
|
|
104
111
|
/**
|
|
@@ -109,8 +116,13 @@ class Pool {
|
|
|
109
116
|
*/
|
|
110
117
|
getInputAmount(outputAmount, sqrtPriceLimitX64) {
|
|
111
118
|
const zeroForOne = outputAmount.currency.equals(this.tokenB);
|
|
112
|
-
const {
|
|
119
|
+
const { amountA, amountB } = this.swap(zeroForOne, jsbi_1.default.multiply(outputAmount.quotient, internalConstants_2.NEGATIVE_ONE), sqrtPriceLimitX64);
|
|
113
120
|
const inputToken = zeroForOne ? this.tokenA : this.tokenB;
|
|
121
|
+
const inputAmount = zeroForOne ? amountA : amountB;
|
|
122
|
+
const amountOutReceived = jsbi_1.default.multiply(zeroForOne ? amountB : amountA, internalConstants_2.NEGATIVE_ONE);
|
|
123
|
+
if (!jsbi_1.default.equal(amountOutReceived, outputAmount.quotient)) {
|
|
124
|
+
throw new errors_1.InsufficientReservesError;
|
|
125
|
+
}
|
|
114
126
|
return fractions_1.CurrencyAmount.fromRawAmount(inputToken, inputAmount);
|
|
115
127
|
}
|
|
116
128
|
/**
|
|
@@ -137,7 +149,6 @@ class Pool {
|
|
|
137
149
|
(0, tiny_invariant_1.default)(jsbi_1.default.greaterThan(sqrtPriceLimitX64, this.sqrtPriceX64), "RATIO_CURRENT");
|
|
138
150
|
}
|
|
139
151
|
const exactInput = jsbi_1.default.greaterThanOrEqual(amountSpecified, internalConstants_2.ZERO);
|
|
140
|
-
// keep track of swap state
|
|
141
152
|
const state = {
|
|
142
153
|
amountSpecifiedRemaining: amountSpecified,
|
|
143
154
|
amountCalculated: internalConstants_2.ZERO,
|
|
@@ -145,14 +156,10 @@ class Pool {
|
|
|
145
156
|
tick: this.tickCurrent,
|
|
146
157
|
liquidity: this.liquidity,
|
|
147
158
|
};
|
|
148
|
-
// start swap while loop
|
|
149
159
|
while (jsbi_1.default.notEqual(state.amountSpecifiedRemaining, internalConstants_2.ZERO) &&
|
|
150
160
|
state.sqrtPriceX64 != sqrtPriceLimitX64) {
|
|
151
161
|
const step = {};
|
|
152
162
|
step.sqrtPriceStartX64 = state.sqrtPriceX64;
|
|
153
|
-
// because each iteration of the while loop rounds, we can't optimize this code (relative to the smart contract)
|
|
154
|
-
// by simply traversing to the next available tick, we instead need to exactly replicate
|
|
155
|
-
// tickBitmap.nextInitializedTickWithinOneWord
|
|
156
163
|
[step.tickNext, step.initialized] =
|
|
157
164
|
this.tickDataProvider.nextInitializedTickWithinOneWord(state.tick, zeroForOne, this.tickSpacing);
|
|
158
165
|
if (step.tickNext < tickMath_1.TickMath.MIN_TICK) {
|
|
@@ -176,13 +183,9 @@ class Pool {
|
|
|
176
183
|
state.amountSpecifiedRemaining = jsbi_1.default.add(state.amountSpecifiedRemaining, step.amountOut);
|
|
177
184
|
state.amountCalculated = jsbi_1.default.add(state.amountCalculated, jsbi_1.default.add(step.amountIn, step.feeAmount));
|
|
178
185
|
}
|
|
179
|
-
// TODO
|
|
180
186
|
if (jsbi_1.default.equal(state.sqrtPriceX64, step.sqrtPriceNextX64)) {
|
|
181
|
-
// if the tick is initialized, run the tick transition
|
|
182
187
|
if (step.initialized) {
|
|
183
188
|
let liquidityNet = jsbi_1.default.BigInt((this.tickDataProvider.getTick(step.tickNext)).liquidityNet);
|
|
184
|
-
// if we're moving leftward, we interpret liquidityNet as the opposite sign
|
|
185
|
-
// safe because liquidityNet cannot be type(int128).min
|
|
186
189
|
if (zeroForOne)
|
|
187
190
|
liquidityNet = jsbi_1.default.multiply(liquidityNet, internalConstants_2.NEGATIVE_ONE);
|
|
188
191
|
state.liquidity = liquidityMath_1.LiquidityMath.addDelta(state.liquidity, liquidityNet);
|
|
@@ -190,13 +193,18 @@ class Pool {
|
|
|
190
193
|
state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext;
|
|
191
194
|
}
|
|
192
195
|
else if (jsbi_1.default.notEqual(state.sqrtPriceX64, step.sqrtPriceStartX64)) {
|
|
193
|
-
// updated comparison function
|
|
194
|
-
// recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved
|
|
195
196
|
state.tick = tickMath_1.TickMath.getTickAtSqrtRatio(state.sqrtPriceX64);
|
|
196
197
|
}
|
|
197
198
|
}
|
|
199
|
+
const amountA = zeroForOne == exactInput
|
|
200
|
+
? jsbi_1.default.subtract(amountSpecified, state.amountSpecifiedRemaining)
|
|
201
|
+
: state.amountCalculated;
|
|
202
|
+
const amountB = zeroForOne == exactInput
|
|
203
|
+
? state.amountCalculated
|
|
204
|
+
: jsbi_1.default.subtract(amountSpecified, state.amountSpecifiedRemaining);
|
|
198
205
|
return {
|
|
199
|
-
|
|
206
|
+
amountA,
|
|
207
|
+
amountB,
|
|
200
208
|
sqrtPriceX64: state.sqrtPriceX64,
|
|
201
209
|
liquidity: state.liquidity,
|
|
202
210
|
tickCurrent: state.tick,
|
|
@@ -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> | 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
|
|
@@ -329,11 +331,20 @@ class Trade {
|
|
|
329
331
|
(0, tiny_invariant_1.default)(routes.length > 0, 'ROUTES');
|
|
330
332
|
const bestTrades = [];
|
|
331
333
|
for (const route of routes) {
|
|
332
|
-
|
|
334
|
+
let trade;
|
|
335
|
+
try {
|
|
336
|
+
trade = Trade.fromRoute(route, currencyAmountIn, internalConstants_1.TradeType.EXACT_INPUT);
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
// not enough liquidity in this pair
|
|
340
|
+
if (error.isInsufficientInputAmountError) {
|
|
341
|
+
continue;
|
|
342
|
+
}
|
|
343
|
+
throw error;
|
|
344
|
+
}
|
|
333
345
|
// FIXME! Sorting bug multiple pools
|
|
334
|
-
if (!trade.inputAmount.greaterThan(0) || !trade.priceImpact.greaterThan(0))
|
|
346
|
+
if (!trade.inputAmount.greaterThan(0) || !trade.priceImpact.greaterThan(0))
|
|
335
347
|
continue;
|
|
336
|
-
}
|
|
337
348
|
(0, utils_1.sortedInsert)(bestTrades, trade, maxNumResults, tradeComparator);
|
|
338
349
|
}
|
|
339
350
|
return bestTrades;
|
|
@@ -342,13 +353,57 @@ class Trade {
|
|
|
342
353
|
(0, tiny_invariant_1.default)(routes.length > 0, 'ROUTES');
|
|
343
354
|
const bestTrades = [];
|
|
344
355
|
for (const route of routes) {
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
356
|
+
let trade;
|
|
357
|
+
try {
|
|
358
|
+
trade = Trade.fromRoute(route, currencyAmountOut, internalConstants_1.TradeType.EXACT_OUTPUT);
|
|
348
359
|
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
// not enough liquidity in this pair
|
|
362
|
+
if (error.isInsufficientReservesError) {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
throw error;
|
|
366
|
+
}
|
|
367
|
+
if (!trade.inputAmount.greaterThan(0) || !trade.priceImpact.greaterThan(0))
|
|
368
|
+
continue;
|
|
349
369
|
(0, utils_1.sortedInsert)(bestTrades, trade, maxNumResults, tradeComparator);
|
|
350
370
|
}
|
|
351
371
|
return bestTrades;
|
|
352
372
|
}
|
|
373
|
+
static bestTradeWithSplit(routes, amount, percents, tradeType, swapConfig = { minSplits: 1, maxSplits: 10 }) {
|
|
374
|
+
(0, tiny_invariant_1.default)(routes.length > 0, 'ROUTES');
|
|
375
|
+
(0, tiny_invariant_1.default)(percents.length > 0, 'PERCENTS');
|
|
376
|
+
// Compute routes for all percents for all routes
|
|
377
|
+
const percentToTrades = {};
|
|
378
|
+
for (const percent of percents) {
|
|
379
|
+
const splitAmount = amount.multiply(percent).divide(100);
|
|
380
|
+
for (const route of routes) {
|
|
381
|
+
let trade;
|
|
382
|
+
try {
|
|
383
|
+
trade = Trade.fromRoute(route, splitAmount, tradeType, percent);
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
// not enough liquidity in this pair
|
|
387
|
+
if (error.isInsufficientReservesError || error.isInsufficientInputAmountError) {
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
throw error;
|
|
391
|
+
}
|
|
392
|
+
if (!trade.inputAmount.greaterThan(0) || !trade.priceImpact.greaterThan(0))
|
|
393
|
+
continue;
|
|
394
|
+
if (!percentToTrades[percent]) {
|
|
395
|
+
percentToTrades[percent] = [];
|
|
396
|
+
}
|
|
397
|
+
percentToTrades[percent].push(trade);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
const bestTrades = (0, getBestSwapRoute_1.getBestSwapRoute)(tradeType, percentToTrades, percents, swapConfig);
|
|
401
|
+
if (!bestTrades)
|
|
402
|
+
return null;
|
|
403
|
+
return new Trade({
|
|
404
|
+
routes: bestTrades.map(({ inputAmount, outputAmount, route, swaps }) => ({ inputAmount, outputAmount, route, percent: swaps[0].percent })),
|
|
405
|
+
tradeType
|
|
406
|
+
});
|
|
407
|
+
}
|
|
353
408
|
}
|
|
354
409
|
exports.Trade = Trade;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Indicates that the pair has insufficient reserves for a desired output amount. I.e. the amount of output cannot be
|
|
3
|
+
* obtained by sending any amount of input.
|
|
4
|
+
*/
|
|
5
|
+
export declare class InsufficientReservesError extends Error {
|
|
6
|
+
readonly isInsufficientReservesError = true;
|
|
7
|
+
constructor();
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Indicates that the input amount is too small to produce any amount of output. I.e. the amount of input sent is less
|
|
11
|
+
* than the price of a single unit of output after fees.
|
|
12
|
+
*/
|
|
13
|
+
export declare class InsufficientInputAmountError extends Error {
|
|
14
|
+
readonly isInsufficientInputAmountError = true;
|
|
15
|
+
constructor();
|
|
16
|
+
}
|
package/build/errors.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.InsufficientInputAmountError = exports.InsufficientReservesError = void 0;
|
|
4
|
+
// see https://stackoverflow.com/a/41102306
|
|
5
|
+
const CAN_SET_PROTOTYPE = 'setPrototypeOf' in Object;
|
|
6
|
+
/**
|
|
7
|
+
* Indicates that the pair has insufficient reserves for a desired output amount. I.e. the amount of output cannot be
|
|
8
|
+
* obtained by sending any amount of input.
|
|
9
|
+
*/
|
|
10
|
+
class InsufficientReservesError extends Error {
|
|
11
|
+
constructor() {
|
|
12
|
+
super();
|
|
13
|
+
this.isInsufficientReservesError = true;
|
|
14
|
+
this.name = this.constructor.name;
|
|
15
|
+
if (CAN_SET_PROTOTYPE)
|
|
16
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.InsufficientReservesError = InsufficientReservesError;
|
|
20
|
+
/**
|
|
21
|
+
* Indicates that the input amount is too small to produce any amount of output. I.e. the amount of input sent is less
|
|
22
|
+
* than the price of a single unit of output after fees.
|
|
23
|
+
*/
|
|
24
|
+
class InsufficientInputAmountError extends Error {
|
|
25
|
+
constructor() {
|
|
26
|
+
super();
|
|
27
|
+
this.isInsufficientInputAmountError = true;
|
|
28
|
+
this.name = this.constructor.name;
|
|
29
|
+
if (CAN_SET_PROTOTYPE)
|
|
30
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.InsufficientInputAmountError = InsufficientInputAmountError;
|
package/build/utils/common.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
export declare function parseTrade(trade: any): {
|
|
2
|
+
route: never[];
|
|
3
|
+
memo: string;
|
|
4
|
+
swaps: any;
|
|
2
5
|
input: any;
|
|
3
6
|
output: any;
|
|
4
7
|
minReceived: any;
|
|
5
8
|
maxSent: any;
|
|
6
9
|
priceImpact: any;
|
|
7
|
-
memo: string;
|
|
8
|
-
route: any;
|
|
9
10
|
executionPrice: {
|
|
10
11
|
numerator: any;
|
|
11
12
|
denominator: any;
|
package/build/utils/common.js
CHANGED
|
@@ -7,24 +7,37 @@ 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
|
|
11
|
-
|
|
12
|
-
const maxSent = trade.inputAmount;
|
|
13
|
-
const minReceived = trade.minimumAmountOut(slippage);
|
|
14
|
-
const
|
|
10
|
+
const exactIn = trade.tradeType === internalConstants_1.TradeType.EXACT_INPUT;
|
|
11
|
+
console.log('trade.tradeType', trade.tradeType, internalConstants_1.TradeType.EXACT_INPUT);
|
|
12
|
+
const maxSent = exactIn ? trade.inputAmount : trade.maximumAmountIn(slippage);
|
|
13
|
+
const minReceived = exactIn ? trade.minimumAmountOut(slippage) : trade.outputAmount;
|
|
14
|
+
const tradeType = exactIn ? 'swapexactin' : 'swapexactout';
|
|
15
|
+
const swaps = trade.swaps.map(({ route, percent, inputAmount, outputAmount }) => {
|
|
16
|
+
route = route.pools.map(p => p.id);
|
|
17
|
+
const maxSent = exactIn ? inputAmount : trade.maximumAmountIn(slippage, inputAmount);
|
|
18
|
+
const minReceived = exactIn ? trade.minimumAmountOut(slippage, outputAmount) : outputAmount;
|
|
19
|
+
const input = inputAmount.toAsset();
|
|
20
|
+
const output = outputAmount.toAsset();
|
|
21
|
+
const memo = `${tradeType}#${route.join(',')}#${receiver}#${minReceived.toExtendedAsset()}#0`;
|
|
22
|
+
return { input, route, output, percent, memo, maxSent: maxSent.toFixed(), minReceived: minReceived.toFixed() };
|
|
23
|
+
});
|
|
15
24
|
const result = {
|
|
25
|
+
route: [],
|
|
26
|
+
memo: '',
|
|
27
|
+
swaps,
|
|
16
28
|
input: trade.inputAmount.toFixed(),
|
|
17
29
|
output: trade.outputAmount.toFixed(),
|
|
18
30
|
minReceived: minReceived.toFixed(),
|
|
19
31
|
maxSent: maxSent.toFixed(),
|
|
20
32
|
priceImpact: trade.priceImpact.toSignificant(2),
|
|
21
|
-
memo,
|
|
22
|
-
route,
|
|
23
33
|
executionPrice: {
|
|
24
34
|
numerator: trade.executionPrice.numerator.toString(),
|
|
25
35
|
denominator: trade.executionPrice.denominator.toString()
|
|
26
36
|
}
|
|
27
37
|
};
|
|
38
|
+
// FIXME DEPRECATED Hotfix for legacy v1
|
|
39
|
+
result.route = trade.swaps[0].route.pools.map((p) => p.id);
|
|
40
|
+
result.memo = `${tradeType}#${result.route.join(',')}#${receiver}#${minReceived.toExtendedAsset()}#0`;
|
|
28
41
|
return result;
|
|
29
42
|
}
|
|
30
43
|
exports.parseTrade = parseTrade;
|
|
@@ -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
|
+
};
|
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.35",
|
|
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
|
@@ -13,6 +13,7 @@ import { TickMath } from "../utils/tickMath";
|
|
|
13
13
|
import { Tick, TickConstructorArgs } from "./tick";
|
|
14
14
|
import { NoTickDataProvider, TickDataProvider } from "./tickDataProvider";
|
|
15
15
|
import { TickListDataProvider } from "./tickListDataProvider";
|
|
16
|
+
import {InsufficientReservesError, InsufficientInputAmountError} from "../errors";
|
|
16
17
|
|
|
17
18
|
export interface PoolConstructorArgs {
|
|
18
19
|
id: number,
|
|
@@ -190,10 +191,17 @@ export class Pool {
|
|
|
190
191
|
|
|
191
192
|
const zeroForOne = inputAmount.currency.equals(this.tokenA);
|
|
192
193
|
|
|
193
|
-
const {
|
|
194
|
-
|
|
195
|
-
} = this.swap(zeroForOne, inputAmount.quotient, sqrtPriceLimitX64);
|
|
194
|
+
const { amountA, amountB } = this.swap(zeroForOne, inputAmount.quotient, sqrtPriceLimitX64);
|
|
195
|
+
|
|
196
196
|
const outputToken = zeroForOne ? this.tokenB : this.tokenA;
|
|
197
|
+
const outputAmount = zeroForOne ? amountB : amountA;
|
|
198
|
+
const amountIn = zeroForOne ? amountA : amountB;
|
|
199
|
+
|
|
200
|
+
//console.log(JSBI.equal(amountIn, inputAmount.quotient))
|
|
201
|
+
if (!JSBI.equal(amountIn, inputAmount.quotient)) {
|
|
202
|
+
throw new InsufficientInputAmountError
|
|
203
|
+
}
|
|
204
|
+
|
|
197
205
|
return CurrencyAmount.fromRawAmount(
|
|
198
206
|
outputToken,
|
|
199
207
|
JSBI.multiply(outputAmount, NEGATIVE_ONE)
|
|
@@ -212,14 +220,16 @@ export class Pool {
|
|
|
212
220
|
): CurrencyAmount<Token> {
|
|
213
221
|
const zeroForOne = outputAmount.currency.equals(this.tokenB);
|
|
214
222
|
|
|
215
|
-
const {
|
|
216
|
-
|
|
217
|
-
} = this.swap(
|
|
218
|
-
zeroForOne,
|
|
219
|
-
JSBI.multiply(outputAmount.quotient, NEGATIVE_ONE),
|
|
220
|
-
sqrtPriceLimitX64
|
|
221
|
-
);
|
|
223
|
+
const { amountA, amountB } = this.swap(zeroForOne, JSBI.multiply(outputAmount.quotient, NEGATIVE_ONE), sqrtPriceLimitX64);
|
|
224
|
+
|
|
222
225
|
const inputToken = zeroForOne ? this.tokenA : this.tokenB;
|
|
226
|
+
const inputAmount = zeroForOne ? amountA : amountB;
|
|
227
|
+
const amountOutReceived = JSBI.multiply(zeroForOne ? amountB : amountA, NEGATIVE_ONE)
|
|
228
|
+
|
|
229
|
+
if (!JSBI.equal(amountOutReceived, outputAmount.quotient)) {
|
|
230
|
+
throw new InsufficientReservesError
|
|
231
|
+
}
|
|
232
|
+
|
|
223
233
|
return CurrencyAmount.fromRawAmount(inputToken, inputAmount)
|
|
224
234
|
}
|
|
225
235
|
|
|
@@ -238,7 +248,8 @@ export class Pool {
|
|
|
238
248
|
amountSpecified: JSBI,
|
|
239
249
|
sqrtPriceLimitX64?: JSBI
|
|
240
250
|
): {
|
|
241
|
-
|
|
251
|
+
amountA: JSBI;
|
|
252
|
+
amountB: JSBI;
|
|
242
253
|
sqrtPriceX64: JSBI;
|
|
243
254
|
liquidity: JSBI;
|
|
244
255
|
tickCurrent: number;
|
|
@@ -270,8 +281,6 @@ export class Pool {
|
|
|
270
281
|
|
|
271
282
|
const exactInput = JSBI.greaterThanOrEqual(amountSpecified, ZERO);
|
|
272
283
|
|
|
273
|
-
// keep track of swap state
|
|
274
|
-
|
|
275
284
|
const state = {
|
|
276
285
|
amountSpecifiedRemaining: amountSpecified,
|
|
277
286
|
amountCalculated: ZERO,
|
|
@@ -280,7 +289,6 @@ export class Pool {
|
|
|
280
289
|
liquidity: this.liquidity,
|
|
281
290
|
};
|
|
282
291
|
|
|
283
|
-
// start swap while loop
|
|
284
292
|
while (
|
|
285
293
|
JSBI.notEqual(state.amountSpecifiedRemaining, ZERO) &&
|
|
286
294
|
state.sqrtPriceX64 != sqrtPriceLimitX64
|
|
@@ -288,9 +296,6 @@ export class Pool {
|
|
|
288
296
|
const step: Partial<StepComputations> = {};
|
|
289
297
|
step.sqrtPriceStartX64 = state.sqrtPriceX64;
|
|
290
298
|
|
|
291
|
-
// because each iteration of the while loop rounds, we can't optimize this code (relative to the smart contract)
|
|
292
|
-
// by simply traversing to the next available tick, we instead need to exactly replicate
|
|
293
|
-
// tickBitmap.nextInitializedTickWithinOneWord
|
|
294
299
|
[step.tickNext, step.initialized] =
|
|
295
300
|
this.tickDataProvider.nextInitializedTickWithinOneWord(
|
|
296
301
|
state.tick,
|
|
@@ -340,15 +345,11 @@ export class Pool {
|
|
|
340
345
|
);
|
|
341
346
|
}
|
|
342
347
|
|
|
343
|
-
// TODO
|
|
344
348
|
if (JSBI.equal(state.sqrtPriceX64, step.sqrtPriceNextX64)) {
|
|
345
|
-
// if the tick is initialized, run the tick transition
|
|
346
349
|
if (step.initialized) {
|
|
347
350
|
let liquidityNet = JSBI.BigInt(
|
|
348
351
|
(this.tickDataProvider.getTick(step.tickNext)).liquidityNet
|
|
349
352
|
);
|
|
350
|
-
// if we're moving leftward, we interpret liquidityNet as the opposite sign
|
|
351
|
-
// safe because liquidityNet cannot be type(int128).min
|
|
352
353
|
if (zeroForOne)
|
|
353
354
|
liquidityNet = JSBI.multiply(liquidityNet, NEGATIVE_ONE);
|
|
354
355
|
|
|
@@ -360,14 +361,20 @@ export class Pool {
|
|
|
360
361
|
|
|
361
362
|
state.tick = zeroForOne ? step.tickNext - 1 : step.tickNext;
|
|
362
363
|
} else if (JSBI.notEqual(state.sqrtPriceX64, step.sqrtPriceStartX64)) {
|
|
363
|
-
// updated comparison function
|
|
364
|
-
// recompute unless we're on a lower tick boundary (i.e. already transitioned ticks), and haven't moved
|
|
365
364
|
state.tick = TickMath.getTickAtSqrtRatio(state.sqrtPriceX64);
|
|
366
365
|
}
|
|
367
366
|
}
|
|
368
367
|
|
|
368
|
+
const amountA = zeroForOne == exactInput
|
|
369
|
+
? JSBI.subtract(amountSpecified, state.amountSpecifiedRemaining)
|
|
370
|
+
: state.amountCalculated;
|
|
371
|
+
const amountB = zeroForOne == exactInput
|
|
372
|
+
? state.amountCalculated
|
|
373
|
+
: JSBI.subtract(amountSpecified, state.amountSpecifiedRemaining);
|
|
374
|
+
|
|
369
375
|
return {
|
|
370
|
-
|
|
376
|
+
amountA,
|
|
377
|
+
amountB,
|
|
371
378
|
sqrtPriceX64: state.sqrtPriceX64,
|
|
372
379
|
liquidity: state.liquidity,
|
|
373
380
|
tickCurrent: state.tick,
|
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>
|
|
@@ -490,12 +499,20 @@ export class Trade<TInput extends Currency, TOutput extends Currency, TTradeType
|
|
|
490
499
|
|
|
491
500
|
const bestTrades: Trade<TInput, TOutput, TradeType.EXACT_INPUT>[] = []
|
|
492
501
|
for (const route of routes) {
|
|
493
|
-
|
|
502
|
+
let trade
|
|
503
|
+
|
|
504
|
+
try {
|
|
505
|
+
trade = Trade.fromRoute(route, currencyAmountIn, TradeType.EXACT_INPUT)
|
|
506
|
+
} catch (error) {
|
|
507
|
+
// not enough liquidity in this pair
|
|
508
|
+
if ((error as any).isInsufficientInputAmountError) {
|
|
509
|
+
continue
|
|
510
|
+
}
|
|
511
|
+
throw error
|
|
512
|
+
}
|
|
494
513
|
|
|
495
514
|
// FIXME! Sorting bug multiple pools
|
|
496
|
-
if (!trade.inputAmount.greaterThan(0) || !trade.priceImpact.greaterThan(0))
|
|
497
|
-
continue
|
|
498
|
-
}
|
|
515
|
+
if (!trade.inputAmount.greaterThan(0) || !trade.priceImpact.greaterThan(0)) continue
|
|
499
516
|
|
|
500
517
|
sortedInsert(
|
|
501
518
|
bestTrades,
|
|
@@ -517,12 +534,19 @@ export class Trade<TInput extends Currency, TOutput extends Currency, TTradeType
|
|
|
517
534
|
|
|
518
535
|
const bestTrades: Trade<TInput, TOutput, TradeType.EXACT_OUTPUT>[] = []
|
|
519
536
|
for (const route of routes) {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
537
|
+
let trade
|
|
538
|
+
try {
|
|
539
|
+
trade = Trade.fromRoute(route, currencyAmountOut, TradeType.EXACT_OUTPUT)
|
|
540
|
+
} catch (error) {
|
|
541
|
+
// not enough liquidity in this pair
|
|
542
|
+
if ((error as any).isInsufficientReservesError) {
|
|
543
|
+
continue
|
|
544
|
+
}
|
|
545
|
+
throw error
|
|
524
546
|
}
|
|
525
547
|
|
|
548
|
+
if (!trade.inputAmount.greaterThan(0) || !trade.priceImpact.greaterThan(0)) continue
|
|
549
|
+
|
|
526
550
|
sortedInsert(
|
|
527
551
|
bestTrades,
|
|
528
552
|
trade,
|
|
@@ -533,4 +557,50 @@ export class Trade<TInput extends Currency, TOutput extends Currency, TTradeType
|
|
|
533
557
|
|
|
534
558
|
return bestTrades
|
|
535
559
|
}
|
|
560
|
+
|
|
561
|
+
public static bestTradeWithSplit<TInput extends Currency, TOutput extends Currency>(
|
|
562
|
+
routes: Route<TInput, TOutput>[],
|
|
563
|
+
amount: CurrencyAmount<Currency>,
|
|
564
|
+
percents: number[],
|
|
565
|
+
tradeType: TradeType,
|
|
566
|
+
swapConfig = { minSplits: 1, maxSplits: 10 }
|
|
567
|
+
): Trade<Currency, Currency, TradeType> | null {
|
|
568
|
+
invariant(routes.length > 0, 'ROUTES')
|
|
569
|
+
invariant(percents.length > 0, 'PERCENTS')
|
|
570
|
+
|
|
571
|
+
// Compute routes for all percents for all routes
|
|
572
|
+
const percentToTrades: { [percent: number]: Trade<Currency, Currency, TradeType>[] } = {};
|
|
573
|
+
for (const percent of percents) {
|
|
574
|
+
const splitAmount = amount.multiply(percent).divide(100)
|
|
575
|
+
|
|
576
|
+
for (const route of routes) {
|
|
577
|
+
let trade
|
|
578
|
+
try {
|
|
579
|
+
trade = Trade.fromRoute(route, splitAmount, tradeType, percent)
|
|
580
|
+
} catch (error) {
|
|
581
|
+
// not enough liquidity in this pair
|
|
582
|
+
if ((error as any).isInsufficientReservesError || (error as any).isInsufficientInputAmountError) {
|
|
583
|
+
continue
|
|
584
|
+
}
|
|
585
|
+
throw error
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
if (!trade.inputAmount.greaterThan(0) || !trade.priceImpact.greaterThan(0)) continue
|
|
589
|
+
|
|
590
|
+
if (!percentToTrades[percent]) {
|
|
591
|
+
percentToTrades[percent] = []
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
percentToTrades[percent].push(trade)
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
const bestTrades = getBestSwapRoute(tradeType, percentToTrades, percents, swapConfig)
|
|
598
|
+
if (!bestTrades) return null
|
|
599
|
+
|
|
600
|
+
return new Trade({
|
|
601
|
+
routes: bestTrades.map(({ inputAmount, outputAmount, route, swaps }) =>
|
|
602
|
+
({ inputAmount, outputAmount, route, percent: swaps[0].percent })),
|
|
603
|
+
tradeType }
|
|
604
|
+
)
|
|
605
|
+
}
|
|
536
606
|
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// see https://stackoverflow.com/a/41102306
|
|
2
|
+
const CAN_SET_PROTOTYPE = 'setPrototypeOf' in Object
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Indicates that the pair has insufficient reserves for a desired output amount. I.e. the amount of output cannot be
|
|
6
|
+
* obtained by sending any amount of input.
|
|
7
|
+
*/
|
|
8
|
+
export class InsufficientReservesError extends Error {
|
|
9
|
+
public readonly isInsufficientReservesError = true
|
|
10
|
+
|
|
11
|
+
public constructor() {
|
|
12
|
+
super()
|
|
13
|
+
this.name = this.constructor.name
|
|
14
|
+
if (CAN_SET_PROTOTYPE) Object.setPrototypeOf(this, new.target.prototype)
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Indicates that the input amount is too small to produce any amount of output. I.e. the amount of input sent is less
|
|
20
|
+
* than the price of a single unit of output after fees.
|
|
21
|
+
*/
|
|
22
|
+
export class InsufficientInputAmountError extends Error {
|
|
23
|
+
public readonly isInsufficientInputAmountError = true
|
|
24
|
+
|
|
25
|
+
public constructor() {
|
|
26
|
+
super()
|
|
27
|
+
this.name = this.constructor.name
|
|
28
|
+
if (CAN_SET_PROTOTYPE) Object.setPrototypeOf(this, new.target.prototype)
|
|
29
|
+
}
|
|
30
|
+
}
|
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,27 +6,46 @@ export function parseTrade(trade) {
|
|
|
6
6
|
const slippage = new Percent(3, 100) // 0.3%
|
|
7
7
|
const receiver = '<receiver>'
|
|
8
8
|
|
|
9
|
-
const
|
|
9
|
+
const exactIn = trade.tradeType === TradeType.EXACT_INPUT
|
|
10
|
+
console.log('trade.tradeType', trade.tradeType, TradeType.EXACT_INPUT)
|
|
10
11
|
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
const
|
|
12
|
+
const maxSent = exactIn ? trade.inputAmount : trade.maximumAmountIn(slippage)
|
|
13
|
+
const minReceived = exactIn ? trade.minimumAmountOut(slippage) : trade.outputAmount
|
|
14
|
+
|
|
15
|
+
const tradeType = exactIn ? 'swapexactin' : 'swapexactout'
|
|
16
|
+
|
|
17
|
+
const swaps = trade.swaps.map(({ route, percent, inputAmount, outputAmount }) => {
|
|
18
|
+
route = route.pools.map(p => p.id)
|
|
19
|
+
|
|
20
|
+
const maxSent = exactIn ? inputAmount : trade.maximumAmountIn(slippage, inputAmount)
|
|
21
|
+
const minReceived = exactIn ? trade.minimumAmountOut(slippage, outputAmount) : outputAmount
|
|
22
|
+
|
|
23
|
+
const input = inputAmount.toAsset()
|
|
24
|
+
const output = outputAmount.toAsset()
|
|
25
|
+
|
|
26
|
+
const memo = `${tradeType}#${route.join(',')}#${receiver}#${minReceived.toExtendedAsset()}#0`
|
|
27
|
+
return { input, route, output, percent, memo, maxSent: maxSent.toFixed(), minReceived: minReceived.toFixed() }
|
|
28
|
+
})
|
|
15
29
|
|
|
16
30
|
const result = {
|
|
31
|
+
route: [],
|
|
32
|
+
memo: '',
|
|
33
|
+
swaps,
|
|
17
34
|
input: trade.inputAmount.toFixed(),
|
|
18
35
|
output: trade.outputAmount.toFixed(),
|
|
19
36
|
minReceived: minReceived.toFixed(),
|
|
20
37
|
maxSent: maxSent.toFixed(),
|
|
21
38
|
priceImpact: trade.priceImpact.toSignificant(2),
|
|
22
|
-
|
|
23
|
-
route,
|
|
39
|
+
|
|
24
40
|
executionPrice: {
|
|
25
41
|
numerator: trade.executionPrice.numerator.toString(),
|
|
26
42
|
denominator: trade.executionPrice.denominator.toString()
|
|
27
43
|
}
|
|
28
44
|
}
|
|
29
45
|
|
|
46
|
+
// FIXME DEPRECATED Hotfix for legacy v1
|
|
47
|
+
result!.route = trade.swaps[0].route.pools.map((p) => p.id)
|
|
48
|
+
result!.memo = `${tradeType}#${result.route.join(',')}#${receiver}#${minReceived.toExtendedAsset()}#0`
|
|
49
|
+
|
|
30
50
|
return result
|
|
31
51
|
}
|
|
32
|
-
|
|
@@ -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
|
+
}
|