adding cbData param

This commit is contained in:
2025-11-19 14:55:09 -04:00
parent d209742127
commit 66d854fb75
4 changed files with 452 additions and 0 deletions

11
.env-secret Normal file
View File

@@ -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

View File

@@ -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 }
);
}
}

View File

@@ -351,6 +351,7 @@ export function useSwap() {
limitPrice,
deadline,
false, // unwrap
'0x', // cbData (empty bytes)
],
});

404
src/init_pools.js Normal file
View File

@@ -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 <pools>]
Options:
--pools <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);
});