diff --git a/scripts/create_pool_from_prices.js b/scripts/create_pool_from_prices.js index 74a6e00..f775840 100644 --- a/scripts/create_pool_from_prices.js +++ b/scripts/create_pool_from_prices.js @@ -7,7 +7,6 @@ import { ethers } from 'ethers'; import { readFile } from 'fs/promises'; import { config } from 'dotenv'; -import IPartyPlannerABI from "../src/contracts/IPartyPlannerABI.ts"; // Load environment variables from .env-secret config({ path: new URL('../.env-secret', import.meta.url).pathname }); @@ -16,85 +15,133 @@ config({ path: new URL('../.env-secret', import.meta.url).pathname }); // CONFIGURATION // ============================================================================ -const RPC_URL = process.env.MAINNET_RPC_URL; -const PRIVATE_KEY = process.env.PRIVATE_KEY; -const RECEIVER_ADDRESS = process.env.RECEIVER_ADDRESS; +// Network flag: 'mockchain' or 'mainnet' +const NETWORK = process.env.NETWORK || 'mainnet'; -if (!RPC_URL || !PRIVATE_KEY || !RECEIVER_ADDRESS) { - console.error('[!] Missing required environment variables in .env-secret file'); - console.error(' Required: MAINNET_RPC_URL, PRIVATE_KEY, RECEIVER_ADDRESS'); - process.exit(1); -} - -// Curated token list with real addresses (Ethereum Mainnet) -const TEST_TOKENS = { - USDT: { - address: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // Mainnet USDT - coingeckoId: 'tether', - decimals: 6, - feePpm: 400 // 0.00004 = 0.004% = 40 ppm +// Network-specific configuration +const NETWORK_CONFIG = { + mockchain: { + rpcUrl: 'http://localhost:8545', + privateKey: '0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a', // Dev wallet #4 (sender) + receiverPrivateKey: '0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356', // Receiver wallet + receiverAddress: '0x14dC79964da2C08b23698B3D3cc7Ca32193d9955', // Receiver public key + chainId: '31337', + tokens: { + USDT: { + address: '0xbdEd0D2bf404bdcBa897a74E6657f1f12e5C6fb6', // USXD on mockchain + coingeckoId: 'tether', + decimals: 6, + feePpm: 400 + }, + USDC: { + address: '0x2910E325cf29dd912E3476B61ef12F49cb931096', // FUSD on mockchain + coingeckoId: 'usd-coin', + decimals: 6, + feePpm: 400 + } + } }, - USDC: { - address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', // Mainnet USDC - coingeckoId: 'usd-coin', - decimals: 6, - feePpm: 400 // 0.00004 = 0.004% = 40 ppm - }, - WBTC: { - address: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', // Mainnet WBTC - coingeckoId: 'wrapped-bitcoin', - decimals: 8, - feePpm: 3000 // 0.00030 = 0.03% = 300 ppm - }, - WETH: { - address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // Mainnet WETH - coingeckoId: 'weth', - decimals: 18, - feePpm: 3500 // 0.00035 = 0.035% = 350 ppm - }, - BNB: { - address: '0xB8c77482e45F1F44dE1745F52C74426C631bDD52', // Mainnet BNB (Binance-Peg) - coingeckoId: 'binancecoin', - decimals: 18, - feePpm: 4500 // 0.00045 = 0.045% = 450 ppm - }, - WSOL: { - address: '0xD31a59c85aE9D8edEFeC411D448f90841571b89c', // Mainnet Wormhole Wrapped SOL - coingeckoId: 'solana', - decimals: 9, - feePpm: 9500 // 0.00095 = 0.095% = 950 ppm - }, - TRX: { - address: '0x50327c6c5a14DCaDE707ABad2E27eB517df87AB5', // Mainnet TRX (Wrapped) - coingeckoId: 'tron', - decimals: 6, - feePpm: 9500 // 0.00095 = 0.095% = 950 ppm - }, - AAVE: { - address: '0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9', // Mainnet AAVE - coingeckoId: 'aave', - decimals: 18, - feePpm: 14500 // 0.00145 = 0.145% = 1450 ppm - }, - PEPE: { - address: '0x6982508145454Ce325dDbE47a25d4ec3d2311933', // Mainnet PEPE - coingeckoId: 'pepe', - decimals: 18, - feePpm: 21500 // 0.00215 = 0.215% = 2150 ppm - }, - SHIB: { - address: '0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE', // Mainnet SHIB - coingeckoId: 'shiba-inu', - decimals: 18, - feePpm: 21500 // 0.00215 = 0.215% = 2150 ppm + mainnet: { + rpcUrl: process.env.MAINNET_RPC_URL, + privateKey: process.env.PRIVATE_KEY, + receiverAddress: '0xd3b310bd32d782f89eea49cb79656bcaccde7213', // Same as payer for mainnet + chainId: '1', + tokens: { + USDT: { + address: '0xdAC17F958D2ee523a2206206994597C13D831ec7', + coingeckoId: 'tether', + decimals: 6, + feePpm: 40 // 0.0004% + }, + USDC: { + address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', + coingeckoId: 'usd-coin', + decimals: 6, + feePpm: 40 // 0.0004% + }, + WBTC: { + address: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', + coingeckoId: 'wrapped-bitcoin', + decimals: 8, + feePpm: 300 // 0.00030% + }, + WETH: { + address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + coingeckoId: 'weth', + decimals: 18, + feePpm: 350 // 0.0035% + }, + UNI: { + address: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984', + coingeckoId: 'uniswap', + decimals: 18, + feePpm: 1450 // 0.00145% + }, + WSOL: { + address: '0xD31a59c85aE9D8edEFeC411D448f90841571b89c', // Wormhole Wrapped SOL + coingeckoId: 'solana', + decimals: 9, + feePpm: 950 // 0.00095% + }, + TRX: { + address: '0x50327c6c5a14DCaDE707ABad2E27eB517df87AB5', + coingeckoId: 'tron', + decimals: 6, + feePpm: 950 // 0.00095% + }, + AAVE: { + address: '0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9', + coingeckoId: 'aave', + decimals: 18, + feePpm: 1450 // 0.00145% + }, + PEPE: { + address: '0x6982508145454Ce325dDbE47a25d4ec3d2311933', + coingeckoId: 'pepe', + decimals: 18, + feePpm: 2150 // 0.00215% + }, + SHIB: { + address: '0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE', + coingeckoId: 'shiba-inu', + decimals: 18, + feePpm: 2150 // 0.00215% + } + } } }; +// Get current network config +const currentConfig = NETWORK_CONFIG[NETWORK]; +if (!currentConfig) { + console.error(`[!] Invalid NETWORK: ${NETWORK}. Must be 'mockchain' or 'mainnet'`); + process.exit(1); +} + +const RPC_URL = currentConfig.rpcUrl; +const PRIVATE_KEY = currentConfig.privateKey; +const RECEIVER_ADDRESS = currentConfig.receiverAddress || process.env.RECEIVER_ADDRESS; + +// Validate required config for mainnet +if (NETWORK === 'mainnet' && (!RPC_URL || !PRIVATE_KEY)) { + console.error('[!] Missing required environment variables for mainnet'); + console.error(' Required: MAINNET_RPC_URL, PRIVATE_KEY'); + process.exit(1); +} + +if (!RECEIVER_ADDRESS) { + console.error('[!] Missing RECEIVER_ADDRESS in .env-secret file'); + process.exit(1); +} + +// Use network-specific tokens +const TEST_TOKENS = currentConfig.tokens; + // Default pool parameters const DEFAULT_POOL_PARAMS = { - name: 'Staging LP Pool', - symbol: 'SLP', - kappa: ethers.BigNumber.from('10000000000000000'), // 0.01 * 1e18 + name: 'Liquidity Party POC', + symbol: 'POC.LP', + kappa: ethers.BigNumber.from('184467440737095520'), //0.01 * 2^64 swapFeesPpm: Object.values(TEST_TOKENS).map(t => t.feePpm), flashFeePpm: 5, // 0.0005% stable: false, @@ -110,7 +157,7 @@ const INPUT_USD_AMOUNT = 1; // ============================================================================ const chainInfoData = JSON.parse(await readFile(new URL('../src/contracts/liqp-deployments.json', import.meta.url), 'utf-8')); -const PARTY_PLANNER_ADDRESS = chainInfoData['1'].v1.PartyPlanner; +const PARTY_PLANNER_ADDRESS = chainInfoData[currentConfig.chainId].v1.PartyPlanner; const ERC20ABI = [ { "type": "function", "name": "balanceOf", "stateMutability": "view", "inputs": [{ "name": "account", "type": "address" }], "outputs": [{ "name": "", "type": "uint256" }] }, @@ -189,15 +236,15 @@ function calculateTokenAmounts(prices, usdAmount) { /** * Check token balances */ -async function checkBalances(provider, wallet, tokenAmounts) { - console.log(`\n[~] Checking token balances for wallet: ${wallet.address}`); +async function checkBalances(provider, wallet, tokenAmounts, receiverAddress) { + console.log(`\n[~] Checking token balances for receiver wallet: ${receiverAddress}`); const balances = {}; let hasEnoughBalance = true; for (const [symbol, tokenInfo] of Object.entries(TEST_TOKENS)) { const tokenContract = new ethers.Contract(tokenInfo.address, ERC20ABI, provider); - const balance = await tokenContract.balanceOf(wallet.address); + const balance = await tokenContract.balanceOf(receiverAddress); const requiredAmount = tokenAmounts[symbol]; balances[symbol] = balance; @@ -214,7 +261,7 @@ async function checkBalances(provider, wallet, tokenAmounts) { } if (!hasEnoughBalance) { - console.log(`\n[!] Insufficient token balance. Please ensure your wallet has enough tokens.`); + console.log(`\n[!] Insufficient token balance. Please ensure receiver wallet has enough tokens.`); throw new Error('Insufficient token balance'); } @@ -225,26 +272,30 @@ async function checkBalances(provider, wallet, tokenAmounts) { /** * Approve tokens for the PartyPlanner contract */ -async function approveTokens(wallet, tokenAmounts) { +async function approveTokens(provider, tokenAmounts, receiverPrivateKey) { console.log(`\n[~] Approving tokens for PartyPlanner contract...`); + // Connect with receiver wallet for approvals + const receiverWallet = new ethers.Wallet(receiverPrivateKey, provider); + console.log(`[~] Using receiver wallet for approvals: ${receiverWallet.address}`); + for (const [symbol, tokenInfo] of Object.entries(TEST_TOKENS)) { - const tokenContract = new ethers.Contract(tokenInfo.address, ERC20ABI, wallet); + const tokenContract = new ethers.Contract(tokenInfo.address, ERC20ABI, receiverWallet); // Approve 1% more than needed to account for fees/slippage const requiredAmount = tokenAmounts[symbol]; const approvalAmount = requiredAmount.mul(101).div(100); // 1% buffer - console.log(` [~] Approving ${symbol} (1% buffer)...`); + console.log(` [~] Approving ${symbol} ${tokenInfo.address} ${approvalAmount} (1% buffer)...`); try { - // USDT and some tokens require setting allowance to 0 before setting a new value - // Skip for BNB as it has a broken approve function - if (symbol !== 'BNB') { - const resetTx = await tokenContract.approve(PARTY_PLANNER_ADDRESS, 0); - await resetTx.wait(); - console.log(` [+] ${symbol} allowance reset to 0`); - } + // // USDT and some tokens require setting allowance to 0 before setting a new value + // // Skip for BNB as it has a broken approve function + // if (symbol !== 'BNB') { + // const resetTx = await tokenContract.approve(PARTY_PLANNER_ADDRESS, 0); + // await resetTx.wait(); + // console.log(` [+] ${symbol} allowance reset to 0`); + // } const tx = await tokenContract.approve(PARTY_PLANNER_ADDRESS, approvalAmount); await tx.wait(); @@ -268,16 +319,16 @@ async function createPool(wallet, tokenAmounts) { const tokenAddresses = Object.values(TEST_TOKENS).map(t => t.address); const initialDeposits = Object.keys(TEST_TOKENS).map(symbol => tokenAmounts[symbol].toString()); - // Set deadline to 1 hour from now - const deadline = Math.floor(Date.now() / 1000) + 3600; + // Set deadline to 5 minutes from now + const deadline = Math.floor(Date.now() / 1000) + 300; console.log(`[~] Pool parameters:`); console.log(` Name: ${DEFAULT_POOL_PARAMS.name}`); console.log(` Symbol: ${DEFAULT_POOL_PARAMS.symbol}`); console.log(` Tokens: ${tokenAddresses.join(', ')}`); console.log(` Swap Fees PPM: [${DEFAULT_POOL_PARAMS.swapFeesPpm.join(', ')}]`); - console.log(` Payer: ${wallet.address}`); - console.log(` Receiver: ${RECEIVER_ADDRESS}`); + console.log(` Payer (provides tokens): ${RECEIVER_ADDRESS}`); + console.log(` Receiver (gets LP tokens): ${wallet.address}`); console.log(` Deadline: ${new Date(deadline * 1000).toISOString()}`); // Build cast send command @@ -290,15 +341,14 @@ async function createPool(wallet, tokenAmounts) { "[${DEFAULT_POOL_PARAMS.swapFeesPpm.join(',')}]" \ ${DEFAULT_POOL_PARAMS.flashFeePpm} \ ${DEFAULT_POOL_PARAMS.stable} \ - ${wallet.address} \ ${RECEIVER_ADDRESS} \ + ${wallet.address} \ "[${initialDeposits.join(',')}]" \ ${DEFAULT_POOL_PARAMS.initialLpAmount.toString()} \ ${deadline} \ - --rpc-url http://localhost:8545 \ + --rpc-url '${RPC_URL}' \ --from 0x12db90820dafed100e40e21128e40dcd4ff6b331 \ - --trezor --mnemonic-index 0`; - // --private-key ${PRIVATE_KEY}; + --trezor --mnemonic-index 0` console.log(`\n[~] Cast command:\n${castCommand}\n`); @@ -346,6 +396,7 @@ Example: async function main() { console.log(`${'='.repeat(70)}`); console.log(`Create Pool from Real-Time Prices`); + console.log(`Network: ${NETWORK}`); console.log(`${'='.repeat(70)}\n`); // Parse command line arguments @@ -385,16 +436,23 @@ async function main() { const tokenAmounts = calculateTokenAmounts(prices, usdAmount); // // // Step 3: Connect to wallet - console.log(`\n[~] Connecting to test wallet at ${RPC_URL}...`); + console.log(`\n[~] Connecting to sender wallet at ${RPC_URL}...`); const provider = new ethers.providers.JsonRpcProvider(RPC_URL); const wallet = new ethers.Wallet(PRIVATE_KEY, provider); - console.log(`[+] Connected. Using wallet: ${wallet.address}`); + console.log(`[+] Connected. Using sender wallet: ${wallet.address}`); + console.log(`[+] Receiver wallet: ${RECEIVER_ADDRESS}`); // // Step 4: Check balances - await checkBalances(provider, wallet, tokenAmounts); + await checkBalances(provider, wallet, tokenAmounts, RECEIVER_ADDRESS); - // Step 5: Approve tokens - await approveTokens(wallet, tokenAmounts); + // // // Step 5: Approve tokens + // if (NETWORK === 'mockchain' && currentConfig.receiverPrivateKey) { + // // On mockchain, use receiver wallet for approvals + // await approveTokens(provider, tokenAmounts, currentConfig.receiverPrivateKey); + // } else if (NETWORK === 'mainnet') { + // // On mainnet, use the main wallet (payer and receiver are the same) + // await approveTokens(provider, tokenAmounts, PRIVATE_KEY); + // } // Step 6: Create pool await createPool(wallet, tokenAmounts);