import { Contract } from 'ethers'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import { QuoterV2 } from '@uniswap/v3-periphery'; import { PoolState, QuoteEngine, QuoteExactInputSingleParams, QuoteExactInputSingleResult, } from './quote-engine'; export type ThickV2QuoteEngineExtras = { tickSpacing?: number | string; sqrtPriceLimitX96?: bigint | number; }; export class ThickV2QuoteEngine implements QuoteEngine { constructor(private quoter: QuoterV2, private pool?: Contract) {} setPool(pool: Contract): void { this.pool = pool; } async quoteExactInputSingle( params: QuoteExactInputSingleParams, ): Promise { const tickSpacing = await this.resolveTickSpacing(params); const sqrtPriceLimitX96 = await this.resolveSqrtPriceLimit(params); const quoteFn = this.quoter.getFunction('quoteExactInputSingle'); const q = await quoteFn.staticCall({ tokenIn: params.tokenIn, tokenOut: params.tokenOut, amountIn: params.amountIn, tickSpacing: Number(tickSpacing), sqrtPriceLimitX96, }); return { amountOut: BigInt(q.amountOut.toString()), sqrtPriceX96After: BigInt(q.sqrtPriceX96After.toString()), }; } async getCurrentPoolState(): Promise { if (!this.pool) { throw new Error('ThickV2QuoteEngine: pool contract not set'); } const slot0 = this.pool.slot0 ? await this.pool.slot0() : this.pool.globalState ? await this.pool.globalState() : undefined; if (!slot0) { throw new Error('ThickV2QuoteEngine: unable to fetch pool state'); } const sqrtCandidate = slot0.sqrtPriceX96 ?? slot0.price ?? slot0.sqrtPrice ?? slot0[0]; if (!sqrtCandidate) { throw new Error( 'ThickV2QuoteEngine: missing sqrt price in pool state response', ); } const sqrtPriceX96 = BigInt(sqrtCandidate.toString()); const tickCandidate = slot0.tick ?? slot0.currentTick ?? slot0[1]; const tick = tickCandidate === undefined || tickCandidate === null ? undefined : Number( typeof tickCandidate === 'bigint' ? tickCandidate.toString() : tickCandidate.toString(), ); return { sqrtPriceX96, tick, }; } private resolveTickSpacing(params: QuoteExactInputSingleParams): bigint { const extras = params.extras as ThickV2QuoteEngineExtras | undefined; const candidate = extras?.tickSpacing ?? params.extras?.['tick_spacing']; if (candidate === undefined || candidate === null) { throw new Error('ThickV2QuoteEngine: tickSpacing is required'); } const tickSpacing: bigint = typeof candidate === 'string' ? BigInt(candidate) : BigInt((candidate as number | bigint).toString()); if (typeof tickSpacing !== 'bigint') { throw new Error('ThickV2QuoteEngine: invalid tickSpacing value'); } return tickSpacing; } private resolveSqrtPriceLimit(params: QuoteExactInputSingleParams): bigint { const extras = params.extras as ThickV2QuoteEngineExtras | undefined; const fromExtras = (extras?.sqrtPriceLimitX96 as bigint | number | undefined) ?? (params.extras?.['sqrt_price_limit_x96'] as bigint | number | undefined); return ( (params.sqrtPriceLimitX96 as bigint | undefined) ?? (typeof fromExtras === 'number' ? BigInt(fromExtras) : fromExtras) ?? 0n ); } }