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,
|
limitPrice,
|
||||||
deadline,
|
deadline,
|
||||||
false, // unwrap
|
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