@devasher/kuru-sdk
Version:
Ethers v6 SDK to interact with Kuru (forked from @kuru-labs/kuru-sdk)
179 lines • 8.3 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.PathFinder = void 0;
// ============ External Imports ============
const ethers_1 = require("ethers");
// ============ Internal Imports ============
const market_1 = require("../market");
const OrderBook_json_1 = __importDefault(require("../../abi/OrderBook.json"));
const KuruUtils_json_1 = __importDefault(require("../../abi/KuruUtils.json"));
class PathFinder {
static async findBestPath(providerOrSigner, tokenIn, tokenOut, amountIn, amountType = 'amountIn', poolFetcher, pools, estimatorContractAddress) {
// Normalize input addresses to lowercase
const normalizedTokenIn = tokenIn.toLowerCase();
const normalizedTokenOut = tokenOut.toLowerCase();
if (!pools) {
if (!poolFetcher) {
throw new Error('Either pools or poolFetcher must be provided');
}
pools = await poolFetcher.getAllPools(normalizedTokenIn, normalizedTokenOut);
}
else {
// Normalize pool addresses
pools = pools.map((pool) => ({
...pool,
orderbook: pool.orderbook.toLowerCase(),
baseToken: pool.baseToken.toLowerCase(),
quoteToken: pool.quoteToken.toLowerCase(),
}));
}
const routes = computeAllRoutes(normalizedTokenIn, normalizedTokenOut, pools);
let bestRoute = {
route: {
path: [],
tokenIn: '',
tokenOut: '',
},
isBuy: [],
nativeSend: [],
output: 0,
priceImpact: 0,
feeInBase: 0,
};
let bestOutput = 0;
const routeOutputs = await Promise.all(routes.map(async (route) => amountType === 'amountOut'
? await computeRouteInput(providerOrSigner, route, amountIn)
: await computeRouteOutput(providerOrSigner, route, amountIn)));
for (const routeOutput of routeOutputs) {
if (routeOutput.output > bestOutput) {
bestRoute = routeOutput;
bestOutput = routeOutput.output;
}
}
if (estimatorContractAddress) {
bestRoute.priceImpact = await calculatePriceImpact(providerOrSigner, estimatorContractAddress, bestRoute, amountIn);
}
return bestRoute;
}
}
exports.PathFinder = PathFinder;
async function calculatePriceImpact(providerOrSigner, estimatorContractAddress, route, amountIn) {
const estimatorContract = new ethers_1.ethers.Contract(estimatorContractAddress, KuruUtils_json_1.default.abi, providerOrSigner);
const orderbookAddresses = route.route.path.map((pool) => pool.orderbook);
const price = await estimatorContract.calculatePriceOverRoute(orderbookAddresses, route.isBuy);
const priceInUnits = parseFloat((0, ethers_1.formatUnits)(price, 18));
const actualPrice = parseFloat((amountIn / route.output).toFixed(18));
return parseFloat(((100 * actualPrice) / priceInUnits - 100).toFixed(2));
}
function computeAllRoutes(tokenIn, tokenOut, pools, currentPath = [], allPaths = [], startTokenIn = tokenIn, maxHops = 2) {
for (const pool of pools) {
if (currentPath.indexOf(pool) !== -1 || !involvesToken(pool, tokenIn))
continue;
const outputToken = pool.baseToken === tokenIn ? pool.quoteToken : pool.baseToken;
if (outputToken === tokenOut) {
allPaths.push({
path: [...currentPath, pool],
tokenIn: startTokenIn,
tokenOut,
});
}
else if (maxHops > 1) {
computeAllRoutes(outputToken, tokenOut, pools, [...currentPath, pool], allPaths, startTokenIn, maxHops - 1);
}
}
return allPaths;
}
async function computeRouteInput(providerOrSigner, route, amountOut, marketParamsMap) {
let currentToken = route.tokenIn;
let output = amountOut;
let feeInBase = amountOut;
let isBuy = [];
let nativeSend = [];
let priceImpact = 0;
for (const pool of route.path) {
const orderbookAddress = pool.orderbook;
// Get market parameters from map if available, otherwise fetch them
let poolMarketParams = marketParamsMap === null || marketParamsMap === void 0 ? void 0 : marketParamsMap.get(orderbookAddress);
if (!poolMarketParams) {
poolMarketParams = await market_1.ParamFetcher.getMarketParams(providerOrSigner, orderbookAddress);
}
const orderbook = new ethers_1.ethers.Contract(orderbookAddress, OrderBook_json_1.default.abi, providerOrSigner);
const l2Book = await orderbook.getL2Book({
from: ethers_1.ZeroAddress,
});
const vaultParams = await orderbook.getVaultParams({
from: ethers_1.ZeroAddress,
});
currentToken === ethers_1.ZeroAddress ? nativeSend.push(true) : nativeSend.push(false);
if (currentToken === pool.baseToken) {
// If the current token is the base token, we are selling base for quote
output = await market_1.CostEstimator.estimateRequiredBaseForSell(providerOrSigner, orderbookAddress, poolMarketParams, output, l2Book, vaultParams);
currentToken = pool.quoteToken; // Update current token to quote token
isBuy.push(false);
}
else {
// If the current token is the quote token, we are buying base with quote
output = await market_1.CostEstimator.estimateRequiredQuoteForBuy(providerOrSigner, orderbookAddress, poolMarketParams, output, l2Book, vaultParams);
currentToken = pool.baseToken; // Update current token to base token
isBuy.push(true);
}
const takerFeesBps = Number(poolMarketParams.takerFeeBps.toString());
feeInBase = (feeInBase * takerFeesBps) / 10000;
}
return {
route,
output,
nativeSend,
isBuy,
feeInBase,
priceImpact,
};
}
async function computeRouteOutput(providerOrSigner, route, amountIn, marketParamsMap) {
let currentToken = route.tokenIn;
let output = amountIn;
let feeInBase = amountIn;
let isBuy = [];
let nativeSend = [];
let priceImpact = 1;
for (const pool of route.path) {
const orderbookAddress = pool.orderbook;
// Get market parameters from map if available, otherwise fetch them
let poolMarketParams = marketParamsMap === null || marketParamsMap === void 0 ? void 0 : marketParamsMap.get(orderbookAddress);
if (!poolMarketParams) {
poolMarketParams = await market_1.ParamFetcher.getMarketParams(providerOrSigner, orderbookAddress);
}
currentToken === ethers_1.ZeroAddress ? nativeSend.push(true) : nativeSend.push(false);
if (currentToken === pool.baseToken) {
// If the current token is the base token, we are selling base for quote
output = (await market_1.CostEstimator.estimateMarketSell(providerOrSigner, orderbookAddress, poolMarketParams, output)).output;
currentToken = pool.quoteToken; // Update current token to quote token
isBuy.push(false);
}
else {
// If the current token is the quote token, we are buying base with quote
output = (await market_1.CostEstimator.estimateMarketBuy(providerOrSigner, orderbookAddress, poolMarketParams, output)).output;
currentToken = pool.baseToken; // Update current token to base token
isBuy.push(true);
}
const takerFeesBps = Number(poolMarketParams.takerFeeBps.toString());
feeInBase = (feeInBase * takerFeesBps) / 10000;
}
return {
route,
output,
nativeSend,
isBuy,
priceImpact,
feeInBase,
};
}
function involvesToken(pool, token) {
// Make comparison case insensitive
const normalizedToken = token.toLowerCase();
return pool.baseToken.toLowerCase() === normalizedToken || pool.quoteToken.toLowerCase() === normalizedToken;
}
//# sourceMappingURL=path.js.map