From 66d854fb759e16f0a9e59859e1f73509bb94d342 Mon Sep 17 00:00:00 2001 From: surbhi Date: Wed, 19 Nov 2025 14:55:09 -0400 Subject: [PATCH] adding cbData param --- .env-secret | 11 + src/app/api/gas-price/route.ts | 36 +++ src/hooks/usePartyPool.ts | 1 + src/init_pools.js | 404 +++++++++++++++++++++++++++++++++ 4 files changed, 452 insertions(+) create mode 100644 .env-secret create mode 100644 src/app/api/gas-price/route.ts create mode 100644 src/init_pools.js diff --git a/.env-secret b/.env-secret new file mode 100644 index 0000000..4ae6c36 --- /dev/null +++ b/.env-secret @@ -0,0 +1,11 @@ +# Secret environment variables - DO NOT COMMIT TO GIT +# Add this file to .gitignore + +# RPC Node Connection +MAINNET_RPC_URL=https://eth-1.dxod.org/joEnzz51UH6Bc2yU +ALCHEMY_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/o_eQWfo1Rb7qZKpl_vBRL + + +# Receiver Address +RECEIVER_ADDRESS=0xd3b310bd32d782f89eea49cb79656bcaccde7213 +PRIVATE_KEY=89c8f2542b5ff7f3cf0b73255e0a8d79d89c2be598e7f272a275a380ff56a212 \ No newline at end of file diff --git a/src/app/api/gas-price/route.ts b/src/app/api/gas-price/route.ts new file mode 100644 index 0000000..eb701b9 --- /dev/null +++ b/src/app/api/gas-price/route.ts @@ -0,0 +1,36 @@ +import { NextResponse } from 'next/server'; + +export const runtime = 'edge'; + +export async function GET() { + try { + const response = await fetch( + 'https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd', + { + headers: { + 'Accept': 'application/json', + }, + cache: 'no-store', + } + ); + + if (!response.ok) { + console.error(`CoinGecko API error: ${response.status} ${response.statusText}`); + throw new Error(`CoinGecko API error: ${response.statusText}`); + } + + const data = await response.json(); + + return NextResponse.json(data, { + headers: { + 'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=120', + }, + }); + } catch (error) { + console.error('Error fetching gas price:', error); + return NextResponse.json( + { error: 'Failed to fetch gas price', details: error instanceof Error ? error.message : 'Unknown error' }, + { status: 500 } + ); + } +} diff --git a/src/hooks/usePartyPool.ts b/src/hooks/usePartyPool.ts index bc159ea..6fcce89 100644 --- a/src/hooks/usePartyPool.ts +++ b/src/hooks/usePartyPool.ts @@ -351,6 +351,7 @@ export function useSwap() { limitPrice, deadline, false, // unwrap + '0x', // cbData (empty bytes) ], }); diff --git a/src/init_pools.js b/src/init_pools.js new file mode 100644 index 0000000..95016fe --- /dev/null +++ b/src/init_pools.js @@ -0,0 +1,404 @@ +#!/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); +}); \ No newline at end of file