Files
237-combo/examples/ts/uniswap-v3-oracle.ts
2026-02-09 21:51:30 -08:00

187 lines
5.7 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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<Address> {
// 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 };