import { Contract, Provider } from 'ethers'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore import { QuoterV2 } from '@uniswap/v3-periphery'; import { QuoteEngine, QuoteExactInputSingleParams, QuoteExactInputSingleResult, QuoteExactOutputSingleParams, QuoteExactOutputSingleResult, PoolState, } from './quote-engine'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - v4-sdk may not be installed import { Pool } from '@uniswap/v4-sdk'; import { Token } from '@uniswap/sdk-core'; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore - StateView.json may not exist in all environments import { abi as StateViewAbi } from '../../../../artifacts/abis/StateView.json'; export type UniswapV4PoolKey = { currency0: string; currency1: string; fee: number; tickSpacing: number; hooks: string; }; export type UniswapV4QuoteParams = { poolKey: UniswapV4PoolKey; zeroForOne: boolean; exactAmount: bigint; hookData: string; }; export class UniswapV4QuoteEngine implements QuoteEngine { public quoter: QuoterV2; private poolKey: UniswapV4PoolKey; private chainId: number; private provider: Provider; private stateViewAddress: string; constructor( quoter: QuoterV2, poolKey: UniswapV4PoolKey, chainId: number, stateViewAddress: string, provider: Provider, ) { this.quoter = quoter; this.poolKey = poolKey; this.chainId = chainId; this.stateViewAddress = stateViewAddress; this.provider = provider; } async quoteExactInputSingle( params: QuoteExactInputSingleParams, ): Promise { // Determine swap direction const zeroForOne = params.tokenIn.toLowerCase() === this.poolKey.currency0.toLowerCase(); // Build V4 quote parameters const quoteParams: UniswapV4QuoteParams = { poolKey: { currency0: this.poolKey.currency0, currency1: this.poolKey.currency1, fee: this.poolKey.fee, tickSpacing: this.poolKey.tickSpacing, hooks: this.poolKey.hooks, }, zeroForOne, exactAmount: params.amountIn, hookData: (params.extras?.hookData as string) || '0x', }; try { const quoteFn = this.quoter.getFunction('quoteExactInputSingleModified'); const q = await quoteFn.staticCall(quoteParams); return { amountOut: BigInt(q.amountOut.toString()), sqrtPriceX96After: BigInt(q.sqrtPriceX96.toString()), }; } catch (error) { throw new Error( `UniswapV4QuoteEngine.quoteExactInputSingle failed: ${error}`, ); } } async quoteExactOutputSingle( params: QuoteExactOutputSingleParams, ): Promise { // Determine swap direction const zeroForOne = params.tokenIn.toLowerCase() === this.poolKey.currency0.toLowerCase(); // Build V4 quote parameters for exact output const quoteParams = { poolKey: { currency0: this.poolKey.currency0, currency1: this.poolKey.currency1, fee: this.poolKey.fee, tickSpacing: this.poolKey.tickSpacing, hooks: this.poolKey.hooks, }, zeroForOne, exactAmount: params.amountOut, hookData: (params.extras?.hookData as string) || '0x', }; try { const quoteFn = this.quoter.getFunction('quoteExactOutputSingle'); const q = await quoteFn.staticCall(quoteParams); return { amountIn: BigInt(q.amountIn.toString()), sqrtPriceX96After: BigInt(q.sqrtPriceX96After.toString()), }; } catch (error) { throw new Error( `UniswapV4QuoteEngine.quoteExactOutputSingle failed: ${error}`, ); } } /** * Updates the pool key for this engine instance */ updatePoolKey(poolKey: UniswapV4PoolKey): void { this.poolKey = poolKey; } /** * Gets the current pool key */ getPoolKey(): UniswapV4PoolKey { return { ...this.poolKey }; } getPoolId() { const currency0 = new Token(this.chainId, this.poolKey.currency0, 18); const currency1 = new Token(this.chainId, this.poolKey.currency1, 18); return Pool.getPoolId( currency0, currency1, this.poolKey.fee, this.poolKey.tickSpacing, this.poolKey.hooks, ); } /** * Gets the current pool state for Uniswap V4 * This attempts to use a zero-amount quote to get current state without impact */ async getCurrentPoolState(): Promise { const stateView = new Contract( this.stateViewAddress, StateViewAbi, this.provider, ); // const poolKey = await this.getPoolId(); const poolKey = await this.getPoolId(); // eslint-disable-next-line @typescript-eslint/no-explicit-any const slot0 = await (stateView as any).getSlot0(poolKey); return { sqrtPriceX96: BigInt(slot0.sqrtPriceX96.toString()), tick: slot0.tick, }; } }