UNPKG

@alcorexchange/alcor-swap-sdk

Version:

## Installation ​​ **npm** ``` npm i @alcorexchange/alcor-swap-sdk ``` **yarn** ``` yarn add @alcorexchange/alcor-swap-sdk ``` ## Usage ### Import:

565 lines (549 loc) 24.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Trade = void 0; exports.tradeComparator = tradeComparator; var _lodash = _interopRequireDefault(require("lodash")); var _tinyInvariant = _interopRequireDefault(require("tiny-invariant")); var _fractions = require("./fractions"); var _utils = require("../utils"); var _internalConstants = require("../internalConstants"); var _getBestSwapRoute = require("../utils/getBestSwapRoute"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; } function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; } function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); } function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } } function _createClass(e, r, t) { return r && _defineProperties(e.prototype, r), t && _defineProperties(e, t), Object.defineProperty(e, "prototype", { writable: !1 }), e; } function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; } function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } /** * Trades comparator, an extension of the input output comparator that also considers other dimensions of the trade in ranking them * @template TInput The input token, either Ether or an ERC-20 * @template TOutput The output token, either Ether or an ERC-20 * @template TTradeType The trade type, either exact input or exact output * @param a The first trade to compare * @param b The second trade to compare * @returns A sorted ordering for two neighboring elements in a trade array */ function tradeComparator(a, b) { // must have same input and output token for comparison (0, _tinyInvariant.default)(a.inputAmount.currency.equals(b.inputAmount.currency), 'INPUT_CURRENCY'); (0, _tinyInvariant.default)(a.outputAmount.currency.equals(b.outputAmount.currency), 'OUTPUT_CURRENCY'); if (a.outputAmount.equalTo(b.outputAmount)) { if (a.inputAmount.equalTo(b.inputAmount)) { // consider the number of hops since each hop costs cpu const aHops = a.swaps.reduce((total, cur) => total + cur.route.tokenPath.length, 0); const bHops = b.swaps.reduce((total, cur) => total + cur.route.tokenPath.length, 0); return aHops - bHops; } // trade A requires less input than trade B, so A should come first if (a.inputAmount.lessThan(b.inputAmount)) { return -1; } else { return 1; } } else { // tradeA has less output than trade B, so should come second if (a.outputAmount.lessThan(b.outputAmount)) { return 1; } else { return -1; } } } /** * Represents a trade executed against a set of routes where some percentage of the input is * split across each route. * * Each route has its own set of pools. Pools can not be re-used across routes. * * Does not account for slippage, i.e., changes in price environment that can occur between * the time the trade is submitted and when it is executed. * @template TInput The input token, either Ether or an ERC-20 * @template TOutput The output token, either Ether or an ERC-20 * @template TTradeType The trade type, either exact input or exact output */ let Trade = exports.Trade = /*#__PURE__*/function () { /** * Construct a trade by passing in the pre-computed property values * @param routes The routes through which the trade occurs * @param tradeType The type of trade, exact input or exact output */ function Trade(_ref) { let { routes, tradeType } = _ref; _classCallCheck(this, Trade); /** * The swaps of the trade, i.e. which routes and how much is swapped in each that * make up the trade. */ _defineProperty(this, "swaps", void 0); /** * The type of the trade, either exact in or exact out. */ _defineProperty(this, "tradeType", void 0); /** * The cached result of the input amount computation * @private */ _defineProperty(this, "_inputAmount", void 0); /** * The cached result of the output amount computation * @private */ _defineProperty(this, "_outputAmount", void 0); /** * The cached result of the computed execution price * @private */ _defineProperty(this, "_executionPrice", void 0); /** * The cached result of the price impact computation * @private */ _defineProperty(this, "_priceImpact", void 0); const inputCurrency = routes[0].inputAmount.currency; const outputCurrency = routes[0].outputAmount.currency; (0, _tinyInvariant.default)(routes.every(_ref2 => { let { route } = _ref2; return inputCurrency.equals(route.input); }), 'INPUT_CURRENCY_MATCH'); (0, _tinyInvariant.default)(routes.every(_ref3 => { let { route } = _ref3; return outputCurrency.equals(route.output); }), 'OUTPUT_CURRENCY_MATCH'); const numPools = routes.map(_ref4 => { let { route } = _ref4; return route.pools.length; }).reduce((total, cur) => total + cur, 0); const poolAddressSet = new Set(); for (const { route } of routes) { for (const pool of route.pools) { poolAddressSet.add(pool.id); } } (0, _tinyInvariant.default)(numPools == poolAddressSet.size, 'POOLS_DUPLICATED'); this.swaps = routes; this.tradeType = tradeType; } /** * Get the minimum amount that must be received from this trade for the given slippage tolerance * @param slippageTolerance The tolerance of unfavorable slippage from the execution price of this trade * @returns The amount out */ return _createClass(Trade, [{ key: "route", get: /** * @deprecated Deprecated in favor of 'swaps' property. If the trade consists of multiple routes * this will return an error. * * When the trade consists of just a single route, this returns the route of the trade, * i.e. which pools the trade goes through. */ function () { (0, _tinyInvariant.default)(this.swaps.length == 1, 'MULTIPLE_ROUTES'); return this.swaps[0].route; } }, { key: "inputAmount", get: /** * The input amount for the trade assuming no slippage. */ function () { if (this._inputAmount) { return this._inputAmount; } const inputCurrency = this.swaps[0].inputAmount.currency; const totalInputFromRoutes = this.swaps.map(_ref5 => { let { inputAmount } = _ref5; return inputAmount; }).reduce((total, cur) => total.add(cur), _fractions.CurrencyAmount.fromRawAmount(inputCurrency, 0)); this._inputAmount = totalInputFromRoutes; return this._inputAmount; } }, { key: "outputAmount", get: /** * The output amount for the trade assuming no slippage. */ function () { if (this._outputAmount) { return this._outputAmount; } const outputCurrency = this.swaps[0].outputAmount.currency; const totalOutputFromRoutes = this.swaps.map(_ref6 => { let { outputAmount } = _ref6; return outputAmount; }).reduce((total, cur) => total.add(cur), _fractions.CurrencyAmount.fromRawAmount(outputCurrency, 0)); this._outputAmount = totalOutputFromRoutes; return this._outputAmount; } }, { key: "executionPrice", get: /** * The price expressed in terms of output amount/input amount. */ function () { var _this$_executionPrice; return (_this$_executionPrice = this._executionPrice) !== null && _this$_executionPrice !== void 0 ? _this$_executionPrice : this._executionPrice = new _fractions.Price(this.inputAmount.currency, this.outputAmount.currency, this.inputAmount.quotient, this.outputAmount.quotient); } }, { key: "priceImpact", get: /** * Returns the percent difference between the route's mid price and the price impact */ function () { if (this._priceImpact) { return this._priceImpact; } let spotOutputAmount = _fractions.CurrencyAmount.fromRawAmount(this.outputAmount.currency, 0); for (const { route, inputAmount } of this.swaps) { const midPrice = route.midPrice; spotOutputAmount = spotOutputAmount.add(midPrice.quote(inputAmount)); } const priceImpact = spotOutputAmount.subtract(this.outputAmount).divide(spotOutputAmount); this._priceImpact = new _fractions.Percent(priceImpact.numerator, priceImpact.denominator); return this._priceImpact; } /** * Constructs an exact in trade with the given amount in and route * @template TInput The input token, either Ether or an ERC-20 * @template TOutput The output token, either Ether or an ERC-20 * @param route The route of the exact in trade * @param amountIn The amount being passed in * @returns The exact in trade */ }, { key: "minimumAmountOut", value: function minimumAmountOut(slippageTolerance) { let amountOut = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.outputAmount; (0, _tinyInvariant.default)(!slippageTolerance.lessThan(_internalConstants.ZERO), 'SLIPPAGE_TOLERANCE'); if (this.tradeType === _internalConstants.TradeType.EXACT_OUTPUT) { return amountOut; } else { const slippageAdjustedAmountOut = new _fractions.Fraction(_internalConstants.ONE).add(slippageTolerance).invert().multiply(amountOut.quotient).quotient; return _fractions.CurrencyAmount.fromRawAmount(amountOut.currency, slippageAdjustedAmountOut); } } /** * Get the maximum amount in that can be spent via this trade for the given slippage tolerance * @param slippageTolerance The tolerance of unfavorable slippage from the execution price of this trade * @returns The amount in */ }, { key: "maximumAmountIn", value: function maximumAmountIn(slippageTolerance) { let amountIn = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.inputAmount; (0, _tinyInvariant.default)(!slippageTolerance.lessThan(_internalConstants.ZERO), 'SLIPPAGE_TOLERANCE'); if (this.tradeType === _internalConstants.TradeType.EXACT_INPUT) { return amountIn; } else { const slippageAdjustedAmountIn = new _fractions.Fraction(_internalConstants.ONE).add(slippageTolerance).multiply(amountIn.quotient).quotient; return _fractions.CurrencyAmount.fromRawAmount(amountIn.currency, slippageAdjustedAmountIn); } } /** * Return the execution price after accounting for slippage tolerance * @param slippageTolerance the allowed tolerated slippage * @returns The execution price */ }, { key: "worstExecutionPrice", value: function worstExecutionPrice(slippageTolerance) { return new _fractions.Price(this.inputAmount.currency, this.outputAmount.currency, this.maximumAmountIn(slippageTolerance).quotient, this.minimumAmountOut(slippageTolerance).quotient); } }], [{ key: "exactIn", value: function exactIn(route, amountIn) { return Trade.fromRoute(route, amountIn, _internalConstants.TradeType.EXACT_INPUT); } /** * Constructs an exact out trade with the given amount out and route * @template TInput The input token, either Ether or an ERC-20 * @template TOutput The output token, either Ether or an ERC-20 * @param route The route of the exact out trade * @param amountOut The amount returned by the trade * @returns The exact out trade */ }, { key: "exactOut", value: function exactOut(route, amountOut) { return Trade.fromRoute(route, amountOut, _internalConstants.TradeType.EXACT_OUTPUT); } /** * Constructs a trade by simulating swaps through the given route * @template TInput The input token, either Ether or an ERC-20. * @template TOutput The output token, either Ether or an ERC-20. * @template TTradeType The type of the trade, either exact in or exact out. * @param route route to swap through * @param amount the amount specified, either input or output, depending on tradeType * @param tradeType whether the trade is an exact input or exact output swap * @returns The route */ }, { key: "fromRoute", value: function fromRoute(route, amount, tradeType) { let percent = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 100; const amounts = new Array(route.tokenPath.length); let inputAmount; let outputAmount; if (tradeType === _internalConstants.TradeType.EXACT_INPUT) { amounts[0] = amount; // Переиспользуем amount напрямую for (let i = 0; i < route.tokenPath.length - 1; i++) { amounts[i + 1] = route.pools[i].getOutputAmount(amounts[i]); } inputAmount = amount; // Без создания нового объекта outputAmount = amounts[amounts.length - 1]; } else { amounts[amounts.length - 1] = amount; for (let i = route.tokenPath.length - 1; i > 0; i--) { amounts[i - 1] = route.pools[i - 1].getInputAmount(amounts[i]); } inputAmount = amounts[0]; outputAmount = amount; } return new Trade({ routes: [{ inputAmount, outputAmount, route, percent }], tradeType }); } /** * Constructs a trade from routes by simulating swaps * * @template TInput The input token, either Ether or an ERC-20. * @template TOutput The output token, either Ether or an ERC-20. * @template TTradeType The type of the trade, either exact in or exact out. * @param routes the routes to swap through and how much of the amount should be routed through each * @param tradeType whether the trade is an exact input or exact output swap * @returns The trade */ }, { key: "fromRoutes", value: function fromRoutes(routes, tradeType) { const populatedRoutes = []; for (const { route, amount, percent } of routes) { const amounts = new Array(route.tokenPath.length); let inputAmount; let outputAmount; if (tradeType === _internalConstants.TradeType.EXACT_INPUT) { (0, _tinyInvariant.default)(amount.currency.equals(route.input), 'INPUT'); inputAmount = _fractions.CurrencyAmount.fromFractionalAmount(route.input, amount.numerator, amount.denominator); amounts[0] = _fractions.CurrencyAmount.fromFractionalAmount(route.input, amount.numerator, amount.denominator); for (let i = 0; i < route.tokenPath.length - 1; i++) { const pool = route.pools[i]; const outputAmount = pool.getOutputAmount(amounts[i]); amounts[i + 1] = outputAmount; } outputAmount = _fractions.CurrencyAmount.fromFractionalAmount(route.output, amounts[amounts.length - 1].numerator, amounts[amounts.length - 1].denominator); } else { (0, _tinyInvariant.default)(amount.currency.equals(route.output), 'OUTPUT'); outputAmount = _fractions.CurrencyAmount.fromFractionalAmount(route.output, amount.numerator, amount.denominator); amounts[amounts.length - 1] = _fractions.CurrencyAmount.fromFractionalAmount(route.output, amount.numerator, amount.denominator); for (let i = route.tokenPath.length - 1; i > 0; i--) { const pool = route.pools[i - 1]; const inputAmount = pool.getInputAmount(amounts[i]); amounts[i - 1] = inputAmount; } inputAmount = _fractions.CurrencyAmount.fromFractionalAmount(route.input, amounts[0].numerator, amounts[0].denominator); } populatedRoutes.push({ route, inputAmount, outputAmount, percent }); } return new Trade({ routes: populatedRoutes, tradeType }); } /** * Creates a trade without computing the result of swapping through the route. Useful when you have simulated the trade * elsewhere and do not have any tick data * @template TInput The input token, either Ether or an ERC-20 * @template TOutput The output token, either Ether or an ERC-20 * @template TTradeType The type of the trade, either exact in or exact out * @param constructorArguments The arguments passed to the trade constructor * @returns The unchecked trade */ }, { key: "createUncheckedTrade", value: function createUncheckedTrade(constructorArguments) { return new Trade(_objectSpread(_objectSpread({}, constructorArguments), {}, { routes: [{ percent: constructorArguments.percent, inputAmount: constructorArguments.inputAmount, outputAmount: constructorArguments.outputAmount, route: constructorArguments.route }] })); } /** * Creates a trade without computing the result of swapping through the routes. Useful when you have simulated the trade * elsewhere and do not have any tick data * @template TInput The input token, either Ether or an ERC-20 * @template TOutput The output token, either Ether or an ERC-20 * @template TTradeType The type of the trade, either exact in or exact out * @param constructorArguments The arguments passed to the trade constructor * @returns The unchecked trade */ }, { key: "createUncheckedTradeWithMultipleRoutes", value: function createUncheckedTradeWithMultipleRoutes(constructorArguments) { return new Trade(constructorArguments); } }, { key: "bestTradeExactIn", value: function bestTradeExactIn(routes, currencyAmountIn) { let maxNumResults = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1; (0, _tinyInvariant.default)(routes.length > 0, 'ROUTES'); const bestTrades = []; for (const route of routes) { let trade; try { trade = Trade.fromRoute(route, currencyAmountIn, _internalConstants.TradeType.EXACT_INPUT); } catch (error) { // not enough liquidity in this pair if (error.isInsufficientInputAmountError) { continue; } throw error; } // FIXME! Sorting bug multiple pools if (!trade.inputAmount.greaterThan(0) || !trade.priceImpact.greaterThan(0)) continue; (0, _utils.sortedInsert)(bestTrades, trade, maxNumResults, tradeComparator); } return bestTrades; } }, { key: "bestTradeExactOut", value: function bestTradeExactOut(routes, currencyAmountOut) { let maxNumResults = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1; (0, _tinyInvariant.default)(routes.length > 0, 'ROUTES'); const bestTrades = []; for (const route of routes) { let trade; try { trade = Trade.fromRoute(route, currencyAmountOut, _internalConstants.TradeType.EXACT_OUTPUT); } catch (error) { // not enough liquidity in this pair if (error.isInsufficientReservesError) { continue; } throw error; } if (!trade.inputAmount.greaterThan(0) || !trade.priceImpact.greaterThan(0)) continue; (0, _utils.sortedInsert)(bestTrades, trade, maxNumResults, tradeComparator); } return bestTrades; } }, { key: "bestTradeWithSplit", value: function bestTradeWithSplit(_routes, amount, percents, tradeType) { let swapConfig = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : { minSplits: 1, maxSplits: 10 }; (0, _tinyInvariant.default)(_routes.length > 0, 'ROUTES'); (0, _tinyInvariant.default)(percents.length > 0, 'PERCENTS'); // Предварительно вычисляем splitAmount для всех процентов const percentToAmount = new Map(); for (const percent of percents) { percentToAmount.set(percent, amount.multiply(percent).divide(100)); } // Используем Map вместо объекта для лучшей производительности const percentToTrades = new Map(); for (const percent of percents) { percentToTrades.set(percent, []); } // Оптимизируем внутренний цикл - группируем вычисления по маршрутам for (const route of _routes) { // Для каждого маршрута проходим по всем процентам for (const percent of percents) { const splitAmount = percentToAmount.get(percent); try { const trade = Trade.fromRoute(route, splitAmount, tradeType, percent); if (trade.inputAmount.greaterThan(0) && trade.priceImpact.greaterThan(0)) { percentToTrades.get(percent).push(trade); } } catch (error) { if (error.isInsufficientReservesError || error.isInsufficientInputAmountError) { continue; } throw error; } } } // Преобразуем Map обратно в объект для совместимости с getBestSwapRoute const percentToTradesObj = {}; percentToTrades.forEach((trades, percent) => { percentToTradesObj[percent] = trades; }); const bestTrades = (0, _getBestSwapRoute.getBestSwapRoute)(tradeType, percentToTradesObj, percents, swapConfig); if (!bestTrades) return null; const routes = bestTrades.map(_ref7 => { let { inputAmount, outputAmount, route, swaps } = _ref7; return { inputAmount, outputAmount, route, percent: swaps[0].percent }; }); // Check missing input after splitting // TODO Do we need it for exact out? if (tradeType === _internalConstants.TradeType.EXACT_INPUT) { const totalAmount = _lodash.default.reduce(routes, (total, route) => total.add(route.inputAmount), _fractions.CurrencyAmount.fromRawAmount(routes[0].route.input, 0)); const missingAmount = amount.subtract(totalAmount); if (missingAmount.greaterThan(0)) { console.log("MISSING AMOUNT!!!", missingAmount.toFixed()); routes[0].inputAmount = routes[0].inputAmount.add(missingAmount); } } return new Trade({ routes, tradeType }); } }]); }();