UNPKG

@swaptoshi/dex-module

Version:

Klayr decentralized exchange (dex) on-chain module

227 lines 11.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Quoter = void 0; const int_1 = require("../int"); const pool_1 = require("../../pool"); const Path = require("../periphery/path"); const PoolTicksCounter = require("../periphery/pool_ticks_counter"); const TickMath = require("../core/tick_math"); const tick_bitmap_1 = require("../../tick_bitmap"); const utils_1 = require("../../../utils"); const token_symbol_1 = require("../../token_symbol"); class Quoter { constructor(context, stores) { this.senderAddress = Buffer.alloc(0); this.immutableContext = context; this.poolStore = stores.get(pool_1.PoolStore); this.tickBitmapStore = stores.get(tick_bitmap_1.TickBitmapStore); this.tokenSymbolStore = stores.get(token_symbol_1.TokenSymbolStore); } async getPopulatedTicksInWord(tokenA, tokenB, fee, tickBitmapIndex) { const pool = await this.poolStore.getImmutablePool(this.immutableContext, tokenA, tokenB, fee); const bitmap = int_1.Uint256.from(await pool.getTickBitmap(tickBitmapIndex)); let numberOfPopulatedTicks = int_1.Uint256.from(0); for (let i = 0; i < 256; i += 1) { if (bitmap.and(int_1.Uint256.from(1).shl(i)).gt(0)) numberOfPopulatedTicks = numberOfPopulatedTicks.add(1); } const populatedTicks = new Array(numberOfPopulatedTicks.toNumber()); const { tickSpacing } = pool; for (let i = 0; i < 256; i += 1) { if (bitmap.and(int_1.Uint256.from(1).shl(i)).gt(0)) { const populatedTick = int_1.Int24.from(tickBitmapIndex).shl(8).add(i).mul(tickSpacing); const { liquidityGross, liquidityNet } = await pool.getTick(populatedTick.toString()); numberOfPopulatedTicks = numberOfPopulatedTicks.sub(1); populatedTicks[numberOfPopulatedTicks.toNumber()] = { tick: populatedTick.toString(), liquidityNet, liquidityGross, }; } } return populatedTicks; } async quoteExactInputSingle(params) { const zeroForOne = Buffer.from(params.tokenIn, 'hex').compare(Buffer.from(params.tokenOut, 'hex')) < 0; const pool = await this.poolStore.getImmutablePool(this.immutableContext, Buffer.from(params.tokenIn, 'hex'), Buffer.from(params.tokenOut, 'hex'), params.fee); try { await pool .createEmulator() .swap(this.senderAddress, zeroForOne, params.amountIn, params.sqrtPriceLimitX96 === '0' ? zeroForOne ? int_1.Uint160.from(TickMath.MIN_SQRT_RATIO).add(1).toString() : int_1.Uint160.from(TickMath.MAX_SQRT_RATIO).sub(1).toString() : params.sqrtPriceLimitX96, this._createPayload(params.tokenIn, params.tokenOut, params.fee, pool.slot0.tick), this._swapCallback.bind(this)); } catch (err) { const result = await this._handleRevert(err.message, pool); return { ...result, amountOut: result.amount, }; } throw new Error('Did not throw properly'); } async quoteExactInput(path, amountIn) { let _path = path; const numPools = parseInt(Path.numPools(_path), 10); const sqrtPriceX96AfterList = new Array(numPools); const initializedTicksCrossedList = new Array(numPools); let i = int_1.Uint256.from(0); while (true) { const [tokenIn, tokenOut, fee] = Path.decodeFirstPool(_path); const { amountOut, sqrtPriceX96After, initializedTicksCrossed } = await this.quoteExactInputSingle({ tokenIn: tokenIn.toString('hex'), tokenOut: tokenOut.toString('hex'), fee, amountIn, sqrtPriceLimitX96: '0', }); sqrtPriceX96AfterList[i.toNumber()] = sqrtPriceX96After; initializedTicksCrossedList[i.toNumber()] = initializedTicksCrossed; amountIn = amountOut; i = i.add(1); if (Path.hasMultiplePools(_path)) { _path = Path.skipToken(_path); } else { return { amountOut: amountIn, sqrtPriceX96AfterList, initializedTicksCrossedList }; } } } async quoteExactOutputSingle(params) { const zeroForOne = Buffer.from(params.tokenIn, 'hex').compare(Buffer.from(params.tokenOut, 'hex')) < 0; const pool = await this.poolStore.getImmutablePool(this.immutableContext, Buffer.from(params.tokenIn, 'hex'), Buffer.from(params.tokenOut, 'hex'), params.fee); let amountOutCached; if (params.sqrtPriceLimitX96 === '0') amountOutCached = params.amount; try { await pool .createEmulator() .swap(Buffer.alloc(0), zeroForOne, int_1.Int256.from(params.amount).mul(-1).toString(), params.sqrtPriceLimitX96 === '0' ? zeroForOne ? int_1.Uint160.from(TickMath.MIN_SQRT_RATIO).add(1).toString() : int_1.Uint160.from(TickMath.MAX_SQRT_RATIO).sub(1).toString() : params.sqrtPriceLimitX96, this._createPayload(params.tokenOut, params.tokenIn, params.fee, pool.slot0.tick, amountOutCached), this._swapCallback.bind(this)); } catch (err) { const result = await this._handleRevert(err.message, pool); return { ...result, amountIn: result.amount, }; } throw new Error('Did not throw properly'); } async quoteExactOutput(path, amountOut) { let _path = path; const numPools = parseInt(Path.numPools(_path), 10); const sqrtPriceX96AfterList = new Array(numPools); const initializedTicksCrossedList = new Array(numPools); let i = int_1.Uint256.from(0); while (true) { const [tokenOut, tokenIn, fee] = Path.decodeFirstPool(_path); const { amountIn, sqrtPriceX96After, initializedTicksCrossed } = await this.quoteExactOutputSingle({ tokenIn: tokenIn.toString('hex'), tokenOut: tokenOut.toString('hex'), fee, amount: amountOut, sqrtPriceLimitX96: '0', }); sqrtPriceX96AfterList[i.toNumber()] = sqrtPriceX96After; initializedTicksCrossedList[i.toNumber()] = initializedTicksCrossed; amountOut = amountIn; i = i.add(1); if (Path.hasMultiplePools(_path)) { _path = Path.skipToken(_path); } else { return { amountIn: amountOut, sqrtPriceX96AfterList, initializedTicksCrossedList }; } } } async quotePrice(path) { let price = 1; let _path = path; let baseToken = ''; while (true) { const [tokenIn, tokenOut, fee] = Path.decodeFirstPool(_path); const { token0, slot0 } = await this.poolStore.getImmutablePool(this.immutableContext, tokenIn, tokenOut, fee); const tokenInInfo = await this.tokenSymbolStore.get(this.immutableContext.context, this.tokenSymbolStore.getKey(tokenIn)); const tokenOutInfo = await this.tokenSymbolStore.get(this.immutableContext.context, this.tokenSymbolStore.getKey(tokenOut)); if (!baseToken) baseToken = tokenInInfo.symbol; const decodedPrice = parseFloat((0, utils_1.decodePriceSqrt)(slot0.sqrtPriceX96, tokenInInfo.decimal, tokenOutInfo.decimal, tokenIn.compare(token0) !== 0)); price *= decodedPrice; if (Path.hasMultiplePools(_path)) { _path = Path.skipToken(_path); } else { return { price, pair: `${baseToken}/${tokenOutInfo.symbol}` }; } } } _createPayload(tokenIn, tokenOut, fee, tickBefore, amountOutCached) { const feeBuff = Buffer.allocUnsafe(3); feeBuff.writeUIntBE(parseInt(fee, 10), 0, 3); return JSON.stringify({ path: Buffer.concat([Buffer.from(tokenIn, 'hex'), feeBuff, Buffer.from(tokenOut, 'hex')]).toString('hex'), amountOutCached, tickBefore, }); } async _swapCallback(amount0Delta, amount1Delta, _data, pool) { if (pool === undefined) throw new Error('no pool supplied'); const payload = JSON.parse(_data); if (int_1.Int256.from(amount0Delta).lte(0) && int_1.Int256.from(amount1Delta).lte(0)) throw new Error('swaps entirely within 0-liquidity regions are not supported'); const [tokenIn, tokenOut] = Path.decodeFirstPool(Buffer.from(payload.path, 'hex')); const [isExactInput, amountToPay, amountReceived] = int_1.Int256.from(amount0Delta).gt(0) ? [tokenIn.compare(tokenOut) < 0, int_1.Uint256.from(0).add(amount0Delta).toString(), int_1.Uint256.from(0).sub(amount1Delta).toString()] : [tokenOut.compare(tokenIn) < 0, int_1.Uint256.from(amount1Delta).toString(), int_1.Uint256.from(0).sub(amount0Delta).toString()]; const { sqrtPriceX96: sqrtPriceX96After, tick: tickAfter } = pool.slot0; if (isExactInput) { const data = { amount: amountReceived, sqrtPriceX96After, tickAfter, tickBefore: payload.tickBefore, }; throw new Error(JSON.stringify(data)); } else { if (payload.amountOutCached !== undefined && amountReceived !== payload.amountOutCached) throw new Error('full output amount must be received'); const data = { amount: amountToPay, sqrtPriceX96After, tickAfter, tickBefore: payload.tickBefore, }; throw new Error(JSON.stringify(data)); } } _parseRevertReason(jsonData) { try { const data = JSON.parse(jsonData); const { amount } = data; const { sqrtPriceX96After } = data; const { tickAfter } = data; const { tickBefore } = data; return [amount.toString(), sqrtPriceX96After.toString(), tickAfter.toString(), tickBefore.toString()]; } catch (err) { throw new Error(jsonData); } } async _handleRevert(reason, pool) { const [amount, sqrtPriceX96After, _tickAfter, _tickBefore] = this._parseRevertReason(reason); const tickBefore = _tickBefore; const tickAfter = _tickAfter; const initializedTicksCrossed = await PoolTicksCounter.countInitializedTicksCrossed(this.tickBitmapStore, this.immutableContext.context, pool, tickBefore, tickAfter); return { amount, sqrtPriceX96After, initializedTicksCrossed }; } } exports.Quoter = Quoter; //# sourceMappingURL=quoter.js.map