@swaptoshi/dex-module
Version:
Klayr decentralized exchange (dex) on-chain module
227 lines • 11.1 kB
JavaScript
;
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