/** * Uniswap v3: TWAP Oracle usage * * This example demonstrates how to use Uniswap v3 pools as price oracles * by querying time-weighted average prices (TWAP). * * Note: Always use TWAP, not spot prices, to protect against manipulation. */ import { createRpcClient } from '../../src/utils/chain-config.js'; import { getTokenMetadata } from '../../src/utils/tokens.js'; import type { Address } from 'viem'; const CHAIN_ID = 1; // Mainnet // Uniswap v3 Pool ABI const POOL_ABI = [ { name: 'slot0', type: 'function', stateMutability: 'view', inputs: [], outputs: [ { name: 'sqrtPriceX96', type: 'uint160' }, { name: 'tick', type: 'int24' }, { name: 'observationIndex', type: 'uint16' }, { name: 'observationCardinality', type: 'uint16' }, { name: 'observationCardinalityNext', type: 'uint16' }, { name: 'feeProtocol', type: 'uint8' }, { name: 'unlocked', type: 'bool' }, ], }, { name: 'observations', type: 'function', stateMutability: 'view', inputs: [{ name: 'index', type: 'uint16' }], outputs: [ { name: 'blockTimestamp', type: 'uint32' }, { name: 'tickCumulative', type: 'int56' }, { name: 'secondsPerLiquidityCumulativeX128', type: 'uint160' }, { name: 'initialized', type: 'bool' }, ], }, { name: 'token0', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ name: '', type: 'address' }], }, { name: 'token1', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ name: '', type: 'address' }], }, { name: 'fee', type: 'function', stateMutability: 'view', inputs: [], outputs: [{ name: '', type: 'uint24' }], }, ] as const; /** * Calculate price from sqrtPriceX96 * price = (sqrtPriceX96 / 2^96)^2 */ function calculatePriceFromSqrtPriceX96(sqrtPriceX96: bigint): number { const Q96 = 2n ** 96n; const price = Number(sqrtPriceX96) ** 2 / Number(Q96) ** 2; return price; } /** * Calculate TWAP from observations * * TWAP = (tickCumulative1 - tickCumulative0) / (time1 - time0) */ function calculateTWAP( tickCumulative0: bigint, tickCumulative1: bigint, time0: number, time1: number ): number { if (time1 === time0) { throw new Error('Time difference cannot be zero'); } const tickDelta = Number(tickCumulative1 - tickCumulative0); const timeDelta = time1 - time0; const avgTick = tickDelta / timeDelta; // Convert tick to price: price = 1.0001^tick const price = 1.0001 ** avgTick; return price; } /** * Get pool address from Uniswap v3 Factory * In production, use the official Uniswap v3 SDK to compute pool addresses */ async function getPoolAddress( client: any, token0: Address, token1: Address, fee: number ): Promise
{ // This is a simplified example. In production, use: // 1. Uniswap v3 Factory to get pool address // 2. Or compute pool address using CREATE2 (see Uniswap v3 SDK) // For now, this is a placeholder throw new Error('Implement pool address resolution using Factory or SDK'); } async function queryOracle() { const publicClient = createRpcClient(CHAIN_ID); const token0 = getTokenMetadata(CHAIN_ID, 'USDC'); const token1 = getTokenMetadata(CHAIN_ID, 'WETH'); const fee = 3000; // 0.3% fee tier console.log(`Querying Uniswap v3 TWAP oracle for ${token0.symbol}/${token1.symbol}`); console.log(`Fee tier: ${fee} (0.3%)`); // Note: In production, you need to: // 1. Get the pool address from Uniswap v3 Factory // 2. Or use the Uniswap v3 SDK to compute it // For this example, we'll demonstrate the concept // Example: Query current slot0 (spot price - not recommended for production!) // const poolAddress = await getPoolAddress(publicClient, token0.address, token1.address, fee); // const slot0 = await publicClient.readContract({ // address: poolAddress, // abi: POOL_ABI, // functionName: 'slot0', // }); // const sqrtPriceX96 = slot0[0]; // const currentPrice = calculatePriceFromSqrtPriceX96(sqrtPriceX96); // console.log(`Current spot price: ${currentPrice} ${token1.symbol} per ${token0.symbol}`); // Example: Query TWAP from observations // const observationIndex = slot0[2]; // const observation0 = await publicClient.readContract({ // address: poolAddress, // abi: POOL_ABI, // functionName: 'observations', // args: [observationIndex], // }); // Query a previous observation (e.g., 1 hour ago) // const previousIndex = (observationIndex - 3600) % observationCardinality; // const observation1 = await publicClient.readContract({ // address: poolAddress, // abi: POOL_ABI, // functionName: 'observations', // args: [previousIndex], // }); // const twap = calculateTWAP( // observation0.tickCumulative, // observation1.tickCumulative, // observation0.blockTimestamp, // observation1.blockTimestamp // ); // console.log(`TWAP (1 hour): ${twap} ${token1.symbol} per ${token0.symbol}`); console.log('\n⚠️ This is a conceptual example.'); console.log('In production, use:'); console.log(' 1. Uniswap v3 OracleLibrary (see Uniswap v3 periphery contracts)'); console.log(' 2. Uniswap v3 SDK for price calculations'); console.log(' 3. Always use TWAP, never spot prices'); console.log(' 4. Ensure sufficient observation cardinality for your TWAP window'); } // Run if executed directly if (import.meta.url === `file://${process.argv[1]}`) { queryOracle().catch(console.error); } export { queryOracle, calculatePriceFromSqrtPriceX96, calculateTWAP };