#!/usr/bin/env node /** * Uniswap V4 Quote Script * Connects to Anvil and gets swap quotes from multiple Uniswap V4 pools */ import { ethers } from 'ethers'; import { Token } from '@uniswap/sdk-core'; // ============================================================================ // CONFIGURATION // ============================================================================ const ANVIL_RPC_URL = 'http://127.0.0.1:8545'; // Hardcoded private key for Anvil testing (default Anvil account #0) const PRIVATE_KEY = '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80'; // Contract addresses const QUOTER_ADDRESS = '0x52f0e24d1c21c8a0cb1e5a5dd6198556bd9e1203'; const STATE_VIEW_ADDRESS = '0x7ffe42c4a5deea5b0fec41c94c136cf115597227'; const POSITION_MANAGER_ADDRESS = '0xbD216513d74C8cf14cf4747E6AaA6420FF64ee9e'; // Chain ID const ChainId = { MAINNET: 1 }; // Token definitions const TOKENS = { 'USDC': new Token( ChainId.MAINNET, '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6, 'USDC', 'USDC' ), 'WETH': new Token( ChainId.MAINNET, '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 18, 'WETH', 'WETH' ), 'USDT': new Token( ChainId.MAINNET, '0xdAC17F958D2ee523a2206206994597C13D831ec7', 6, 'USDT', 'USDT' ), 'WBTC': new Token( ChainId.MAINNET, '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', 8, 'WBTC', 'WBTC' ) }; // Pool definitions const POOLS = { // 'WBTC/USDT': '0x9Db9e0e53058C89e5B94e29621a205198648425B', 'ETH/USDT': '0x4e68Ccd3E89f51C3074ca5072bbAC773960dFa36' }; // ============================================================================ // CONTRACT ABIs // ============================================================================ const QUOTER_ABI = [ { inputs: [ { components: [ { components: [ { name: 'currency0', type: 'address' }, { name: 'currency1', type: 'address' }, { name: 'fee', type: 'uint24' }, { name: 'tickSpacing', type: 'int24' }, { name: 'hooks', type: 'address' } ], name: 'poolKey', type: 'tuple' }, { name: 'zeroForOne', type: 'bool' }, { name: 'exactAmount', type: 'uint128' }, { name: 'hookData', type: 'bytes' } ], name: 'params', type: 'tuple' } ], name: 'quoteExactInputSingle', outputs: [ { components: [ { name: 'amountOut', type: 'uint256' } ], name: 'result', type: 'tuple' } ], stateMutability: 'view', type: 'function' } ]; const STATE_VIEW_ABI = [ { inputs: [ { name: 'poolId', type: 'bytes32' } ], name: 'getSlot0', outputs: [ { name: 'sqrtPriceX96', type: 'uint160' }, { name: 'tick', type: 'int24' }, { name: 'protocolFee', type: 'uint24' }, { name: 'lpFee', type: 'uint24' } ], stateMutability: 'view', type: 'function' } ]; const POSITION_MANAGER_ABI = [ { inputs: [{ name: 'poolId', type: 'bytes25' }], name: 'poolKeys', outputs: [ { components: [ { name: 'currency0', type: 'address' }, { name: 'currency1', type: 'address' }, { name: 'fee', type: 'uint24' }, { name: 'tickSpacing', type: 'int24' }, { name: 'hooks', type: 'address' } ], name: 'poolKey', type: 'tuple' } ], stateMutability: 'view', type: 'function' } ]; // ============================================================================ // HELPER FUNCTIONS // ============================================================================ function formatAmount(amountWei, decimals) { return ethers.utils.formatUnits(amountWei, decimals); } function parseAmount(amountStr, decimals) { return ethers.utils.parseUnits(amountStr, decimals); } async function getPoolKey(provider, poolId) { try { const positionManager = new ethers.Contract( POSITION_MANAGER_ADDRESS, POSITION_MANAGER_ABI, provider ); const poolIdBytes25 = poolId.slice(0, 52); const poolKey = await positionManager.poolKeys(poolIdBytes25); return { currency0: poolKey.currency0, currency1: poolKey.currency1, fee: poolKey.fee, tickSpacing: poolKey.tickSpacing, hooks: poolKey.hooks }; } catch (error) { console.error(`[!] Error fetching pool key: ${error.message}`); return null; } } async function getPoolPrice(provider, poolId) { try { const stateView = new ethers.Contract( STATE_VIEW_ADDRESS, STATE_VIEW_ABI, provider ); const slot0 = await stateView.getSlot0(poolId); return { sqrtPriceX96: slot0.sqrtPriceX96, tick: slot0.tick, protocolFee: slot0.protocolFee, lpFee: slot0.lpFee }; } catch (error) { console.error(`[!] Error fetching pool price: ${error.message}`); return null; } } async function getSwapQuote(provider, poolKey, amountIn, tokenInAddress, tokenOutAddress) { try { const quoter = new ethers.Contract( QUOTER_ADDRESS, QUOTER_ABI, provider ); // Determine swap direction (zeroForOne) const currency0 = poolKey.currency0.toLowerCase(); const currency1 = poolKey.currency1.toLowerCase(); const tokenInLower = tokenInAddress.toLowerCase(); let zeroForOne; if (tokenInLower === currency0) { zeroForOne = true; } else if (tokenInLower === currency1) { zeroForOne = false; } else { // Check by comparison if tokens aren't matching pool currencies exactly zeroForOne = tokenInLower < tokenOutAddress.toLowerCase(); } // Build quote params const params = { poolKey: { currency0: poolKey.currency0, currency1: poolKey.currency1, fee: poolKey.fee, tickSpacing: poolKey.tickSpacing, hooks: poolKey.hooks }, zeroForOne: zeroForOne, exactAmount: amountIn.toString(), hookData: '0x00' }; // Call quoter const result = await quoter.callStatic.quoteExactInputSingle(params); return result.amountOut; } catch (error) { console.error(`[!] Error getting quote: ${error.message}`); return null; } } function findTokenByAddress(address) { const addressLower = address.toLowerCase(); for (const [symbol, token] of Object.entries(TOKENS)) { if (token.address.toLowerCase() === addressLower) { return token; } } return null; } function printHelp() { console.log(` Usage: node init_pool.js [--pools ] Options: --pools Comma-separated pool names (default: all pools) --help Show this help message Note: Amount is hardcoded to 100 USDT Examples: node init_pool.js node init_pool.js --pools "WBTC/USDT,ETH/USDT" `); } // ============================================================================ // MAIN FUNCTION // ============================================================================ async function main() { // Parse command line arguments const args = process.argv.slice(2); if (args.includes('--help') || args.includes('-h')) { printHelp(); process.exit(0); } // Hardcoded values const amount = "100"; const tokenSymbol = "USDT"; let poolsStr = Object.keys(POOLS).join(','); for (let i = 0; i < args.length; i++) { if (args[i] === '--pools' && i + 1 < args.length) { poolsStr = args[i + 1]; i++; } } // Setup provider and wallet const provider = new ethers.providers.JsonRpcProvider(ANVIL_RPC_URL); const wallet = new ethers.Wallet(PRIVATE_KEY, provider); console.log(`[+] Connected to Anvil at ${ANVIL_RPC_URL}`); console.log(`[*] Using wallet: ${wallet.address}`); const tokenIn = TOKENS[tokenSymbol]; const amountInWei = parseAmount(amount, tokenIn.decimals); console.log(`\n${'='.repeat(70)}`); console.log(`[~] Getting quotes for swapping ${amount} ${tokenSymbol}`); console.log(`${'='.repeat(70)}\n`); // Parse pool selection const selectedPools = poolsStr.split(',').map(p => p.trim()); // Process each pool for (const poolName of selectedPools) { if (!POOLS[poolName]) { console.log(`[!] Unknown pool: ${poolName}, skipping...`); continue; } const poolAddress = POOLS[poolName]; console.log(`\n[>] Pool: ${poolName}`); console.log(` Address: ${poolAddress}`); console.log(` ${'-'.repeat(66)}`); // Get pool key const poolKey = await getPoolKey(provider, poolAddress); if (!poolKey) { console.log(` [!] Failed to fetch pool key\n`); continue; } console.log(` [+] Pool key fetched`); console.log(` Currency0: ${poolKey.currency0}`); console.log(` Currency1: ${poolKey.currency1}`); console.log(` Fee: ${poolKey.fee} (${(poolKey.fee / 10000).toFixed(2)}%)`); // Get pool price info const poolPrice = await getPoolPrice(provider, poolAddress); if (poolPrice) { console.log(` Current Tick: ${poolPrice.tick}`); console.log(` LP Fee: ${poolPrice.lpFee} (${(poolPrice.lpFee / 10000).toFixed(2)}%)`); } // Identify tokens in the pool const token0Info = findTokenByAddress(poolKey.currency0); const token1Info = findTokenByAddress(poolKey.currency1); if (!token0Info || !token1Info) { console.log(` [!] Unknown token addresses in pool\n`); continue; } console.log(` Token pair: ${token0Info.symbol}/${token1Info.symbol}`); // Determine which token to swap to let tokenOut; if (tokenIn.address.toLowerCase() === token0Info.address.toLowerCase()) { tokenOut = token1Info; } else if (tokenIn.address.toLowerCase() === token1Info.address.toLowerCase()) { tokenOut = token0Info; } else { console.log(` [!] Input token ${tokenIn.symbol} not in this pool\n`); continue; } // Get quote console.log(`\n [~] Quote: ${amount} ${tokenIn.symbol} -> ${tokenOut.symbol}`); const amountOutWei = await getSwapQuote( provider, poolKey, amountInWei, tokenIn.address, tokenOut.address ); if (amountOutWei) { const amountOut = formatAmount(amountOutWei, tokenOut.decimals); const exchangeRate = parseFloat(amountOut) / parseFloat(amount); console.log(` [+] Amount Out: ${amountOut} ${tokenOut.symbol}`); console.log(` [+] Exchange Rate: 1 ${tokenIn.symbol} = ${exchangeRate} ${tokenOut.symbol}`); } else { console.log(` [!] Failed to get quote`); } } console.log(`\n${'='.repeat(70)}\n`); } // Run the main function main().catch(error => { console.error('[!] Error:', error); process.exit(1); });