UNPKG

@alcorexchange/alcor-swap-sdk

Version:

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

180 lines (168 loc) 6.79 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getBestSwapRoute = getBestSwapRoute; var _queue = _interopRequireDefault(require("mnemonist/queue")); var _fixedReverseHeap = _interopRequireDefault(require("mnemonist/fixed-reverse-heap")); var _internalConstants = require("../internalConstants"); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } function getBestSwapRoute(routeType, percentToQuotes, percents) { let swapRouteConfig = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : { minSplits: 1, maxSplits: 8 }; // Извлекаем уникальные пулы из всех маршрутов const allPools = [...new Set(Object.values(percentToQuotes).flatMap(routes => routes.flatMap(r => r.route.pools)))]; // Создаем битовую карту для пулов const poolToBit = new Map(); let bitCounter = 0; for (const pool of allPools) { poolToBit.set(pool.id, 1 << bitCounter++); } // Предвычисляем маски для всех маршрутов const routeToMask = new Map(); for (const routes of Object.values(percentToQuotes)) { for (const route of routes) { let mask = 0; for (const pool of route.route.pools) { mask |= poolToBit.get(pool.id); } routeToMask.set(route, mask); } } // Сортируем маршруты для каждого процента const percentToSortedQuotes = {}; for (const percent in percentToQuotes) { percentToSortedQuotes[percent] = percentToQuotes[percent].sort((a, b) => routeType === _internalConstants.TradeType.EXACT_INPUT ? a.outputAmount.greaterThan(b.outputAmount) ? -1 : 1 : a.inputAmount.lessThan(b.inputAmount) ? -1 : 1); } // Функция сравнения для типа торговли const quoteCompFn = routeType === _internalConstants.TradeType.EXACT_INPUT ? (a, b) => a.greaterThan(b) : (a, b) => a.lessThan(b); // Функция суммирования CurrencyAmount const sumFn = currencyAmounts => { let sum = currencyAmounts[0]; for (let i = 1; i < currencyAmounts.length; i++) { sum = sum.add(currencyAmounts[i]); } return sum; }; let bestQuote; let bestSwap; // Храним лучшие маршруты для каждого уровня разбиения (максимум 3) const bestSwapsPerSplit = new _fixedReverseHeap.default(Array, (a, b) => quoteCompFn(a.quote, b.quote) ? -1 : 1, 3); const { minSplits, maxSplits } = swapRouteConfig; // Проверяем наличие маршрута для 100% и инициализируем начальные данные if (!percentToSortedQuotes[100] || percentToSortedQuotes[100].length === 0 || minSplits > 1) { console.log('Did not find a valid route without any splits. Continuing search anyway.'); } else { bestQuote = percentToSortedQuotes[100][0].outputAmount; bestSwap = [percentToSortedQuotes[100][0]]; for (const routeWithQuote of percentToSortedQuotes[100].slice(0, 5)) { bestSwapsPerSplit.push({ quote: routeWithQuote.outputAmount, routes: [routeWithQuote] }); } } // Очередь для обработки комбинаций маршрутов const queue = new _queue.default(); if (percents.length === 0) return null; // Инициализируем очередь с топ-2 маршрутами для каждого процента for (let i = percents.length - 1; i >= 0; i--) { const percent = percents[i]; if (!percentToSortedQuotes[percent] || percentToSortedQuotes[percent].length === 0) continue; const topRoutes = percentToSortedQuotes[percent].slice(0, 2); if (topRoutes[0]) { queue.enqueue({ curRoutes: [topRoutes[0]], percentIndex: i, remainingPercent: 100 - percent, special: false }); } if (topRoutes[1]) { queue.enqueue({ curRoutes: [topRoutes[1]], percentIndex: i, remainingPercent: 100 - percent, special: true }); } } let splits = 1; // Основной цикл поиска лучших маршрутов while (queue.size > 0) { bestSwapsPerSplit.clear(); let layer = queue.size; splits++; if (splits >= 3 && bestSwap && bestSwap.length < splits - 1) { break; } if (splits > maxSplits) { break; } while (layer > 0) { layer--; const { remainingPercent, curRoutes, percentIndex, special } = queue.dequeue(); for (let i = percentIndex; i >= 0; i--) { const percentA = percents[i]; if (percentA > remainingPercent) continue; if (!percentToSortedQuotes[percentA] || percentToSortedQuotes[percentA].length === 0) continue; const candidateRoutesA = percentToSortedQuotes[percentA]; const routeWithQuoteA = findFirstRouteNotUsingUsedPools(curRoutes, candidateRoutesA, routeToMask); if (!routeWithQuoteA) continue; const remainingPercentNew = remainingPercent - percentA; const curRoutesNew = curRoutes.slice(); curRoutesNew.push(routeWithQuoteA); if (remainingPercentNew === 0 && splits >= minSplits) { const quotesNew = curRoutesNew.map(r => r.outputAmount); const quoteNew = sumFn(quotesNew); bestSwapsPerSplit.push({ quote: quoteNew, routes: curRoutesNew }); if (!bestQuote || quoteCompFn(quoteNew, bestQuote)) { bestQuote = quoteNew; bestSwap = curRoutesNew; } } else { queue.enqueue({ curRoutes: curRoutesNew, remainingPercent: remainingPercentNew, percentIndex: i, special }); } } } } if (!bestSwap) { console.log('Could not find a valid swap'); return null; } const quote = sumFn(bestSwap.map(routeWithValidQuote => routeWithValidQuote.outputAmount)); const routeWithQuotes = bestSwap.sort((routeAmountA, routeAmountB) => routeAmountB.outputAmount.greaterThan(routeAmountA.outputAmount) ? 1 : -1); return routeWithQuotes; } // Вспомогательная функция для поиска маршрута без пересекающихся пулов const findFirstRouteNotUsingUsedPools = (usedRoutes, candidateRoutes, routeToMask) => { let usedMask = 0; for (const route of usedRoutes) { usedMask |= routeToMask.get(route); } for (const candidate of candidateRoutes) { const candidateMask = routeToMask.get(candidate); if ((candidateMask & usedMask) === 0) { return candidate; } } return null; };