187 lines
5.7 KiB
TypeScript
187 lines
5.7 KiB
TypeScript
/**
|
||
* 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 };
|
||
|