adding cbData param
This commit is contained in:
11
.env-secret
Normal file
11
.env-secret
Normal 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
|
||||
36
src/app/api/gas-price/route.ts
Normal file
36
src/app/api/gas-price/route.ts
Normal 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 }
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -351,6 +351,7 @@ export function useSwap() {
|
||||
limitPrice,
|
||||
deadline,
|
||||
false, // unwrap
|
||||
'0x', // cbData (empty bytes)
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
404
src/init_pools.js
Normal file
404
src/init_pools.js
Normal 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);
|
||||
});
|
||||
Reference in New Issue
Block a user