/** * 批量用户买入模拟脚本 * * 功能: * 1. 从 HD 钱包派生多个测试账户 * 2. 从主账户给测试账户转 WUSD * 3. 批量执行 depositYT 买入交易 * * 使用方法: * npx tsx scripts/batch-buy-simulation.ts */ import { createPublicClient, createWalletClient, http, parseUnits, formatUnits, type Address, type Hex } from 'viem' import { arbitrumSepolia } from 'viem/chains' import { privateKeyToAccount, mnemonicToAccount } from 'viem/accounts' // ============ 配置 ============ // 合约地址 const CONTRACTS = { WUSD: '0x6d2bf81a631dFE19B2f348aE92cF6Ef41ca2DF98' as Address, FACTORY: '0x982716f32F10BCB5B5944c1473a8992354bF632b' as Address, VAULTS: { YT_A: '0x0cA35994F033685E7a57ef9bc5d00dd3cf927330' as Address, YT_B: '0x333805C9EE75f59Aa2Cc79DfDe2499F920c7b408' as Address, YT_C: '0x6DF0ED6f0345F601A206974973dE9fC970598587' as Address, } } // 模拟配置 const CONFIG = { // 主账户私钥 (用于分发 WUSD) MAIN_PRIVATE_KEY: '0xa082a7037105ebd606bee80906687e400d89899bbb6ba0273a61528c2f5fab89' as Hex, // HD 钱包助记词 (用于生成测试账户) // 测试用,请勿在生产环境使用 TEST_MNEMONIC: 'test test test test test test test test test test test junk', // 模拟用户数量 USER_COUNT: 3, // 每个用户的买入金额范围 (WUSD) MIN_BUY_AMOUNT: 10, MAX_BUY_AMOUNT: 100, // 目标金库 TARGET_VAULT: CONTRACTS.VAULTS.YT_A, // 交易间隔 (毫秒) TX_INTERVAL: 2000, // RPC URL RPC_URL: 'https://sepolia-rollup.arbitrum.io/rpc', } // ============ ABI ============ const WUSD_ABI = [ { inputs: [{ name: 'to', type: 'address' }, { name: 'amount', type: 'uint256' }], name: 'transfer', outputs: [{ type: 'bool' }], stateMutability: 'nonpayable', type: 'function', }, { inputs: [{ name: 'spender', type: 'address' }, { name: 'amount', type: 'uint256' }], name: 'approve', outputs: [{ type: 'bool' }], stateMutability: 'nonpayable', type: 'function', }, { inputs: [{ name: 'account', type: 'address' }], name: 'balanceOf', outputs: [{ type: 'uint256' }], stateMutability: 'view', type: 'function', }, ] as const const VAULT_ABI = [ { inputs: [{ name: '_wusdAmount', type: 'uint256' }], name: 'depositYT', outputs: [], stateMutability: 'nonpayable', type: 'function', }, { inputs: [{ name: '_wusdAmount', type: 'uint256' }], name: 'previewBuy', outputs: [{ type: 'uint256' }], stateMutability: 'view', type: 'function', }, { inputs: [{ name: 'account', type: 'address' }], name: 'balanceOf', outputs: [{ type: 'uint256' }], stateMutability: 'view', type: 'function', }, { inputs: [], name: 'symbol', outputs: [{ type: 'string' }], stateMutability: 'view', type: 'function', }, ] as const // ============ 工具函数 ============ function randomAmount(min: number, max: number): bigint { const amount = Math.random() * (max - min) + min return parseUnits(amount.toFixed(2), 18) } function sleep(ms: number): Promise { return new Promise(resolve => setTimeout(resolve, ms)) } // ============ 主逻辑 ============ async function main() { console.log('🚀 批量用户买入模拟开始\n') // 创建客户端 const publicClient = createPublicClient({ chain: arbitrumSepolia, transport: http(CONFIG.RPC_URL), }) // 主账户 const mainAccount = privateKeyToAccount(CONFIG.MAIN_PRIVATE_KEY) const mainWalletClient = createWalletClient({ account: mainAccount, chain: arbitrumSepolia, transport: http(CONFIG.RPC_URL), }) console.log(`📍 主账户: ${mainAccount.address}`) // 检查主账户 WUSD 余额 const mainBalance = await publicClient.readContract({ address: CONTRACTS.WUSD, abi: WUSD_ABI, functionName: 'balanceOf', args: [mainAccount.address], }) console.log(`💰 主账户 WUSD 余额: ${formatUnits(mainBalance, 18)}`) // 获取金库 symbol const vaultSymbol = await publicClient.readContract({ address: CONFIG.TARGET_VAULT, abi: VAULT_ABI, functionName: 'symbol', }) console.log(`🏦 目标金库: ${vaultSymbol} (${CONFIG.TARGET_VAULT})`) // 生成测试账户 console.log(`\n👥 生成 ${CONFIG.USER_COUNT} 个测试账户...\n`) const testAccounts = [] for (let i = 0; i < CONFIG.USER_COUNT; i++) { const account = mnemonicToAccount(CONFIG.TEST_MNEMONIC, { addressIndex: i }) const buyAmount = randomAmount(CONFIG.MIN_BUY_AMOUNT, CONFIG.MAX_BUY_AMOUNT) testAccounts.push({ account, buyAmount }) console.log(` 账户 ${i + 1}: ${account.address} | 计划买入: ${formatUnits(buyAmount, 18)} WUSD`) } // 计算总需要的 WUSD const totalNeeded = testAccounts.reduce((sum, a) => sum + a.buyAmount, 0n) console.log(`\n📊 总计需要: ${formatUnits(totalNeeded, 18)} WUSD`) if (mainBalance < totalNeeded) { console.error(`❌ 主账户余额不足! 需要 ${formatUnits(totalNeeded, 18)}, 只有 ${formatUnits(mainBalance, 18)}`) return } // 第一步: 给测试账户转 ETH (gas 费) console.log('\n⛽ 步骤 1: 分发 ETH (gas 费) 到测试账户...\n') const gasAmount = parseUnits('0.05', 18) // 0.05 ETH 足够多次交易 (Arbitrum Sepolia gas 较高) for (let i = 0; i < testAccounts.length; i++) { const { account } = testAccounts[i] // 检查 ETH 余额 const ethBalance = await publicClient.getBalance({ address: account.address }) if (ethBalance >= gasAmount) { console.log(` ✓ 账户 ${i + 1} 已有足够 ETH: ${formatUnits(ethBalance, 18)} ETH`) continue } const transferAmount = gasAmount - ethBalance console.log(` → 转 ETH 给账户 ${i + 1}: ${formatUnits(transferAmount, 18)} ETH...`) try { const hash = await mainWalletClient.sendTransaction({ to: account.address, value: transferAmount, }) await publicClient.waitForTransactionReceipt({ hash }) console.log(` ✓ ETH 转账成功`) } catch (error: any) { console.error(` ✗ ETH 转账失败: ${error.message}`) } await sleep(500) } // 第二步: 给测试账户转 WUSD console.log('\n📤 步骤 2: 分发 WUSD 到测试账户...\n') for (let i = 0; i < testAccounts.length; i++) { const { account, buyAmount } = testAccounts[i] // 检查是否已有足够余额 const currentBalance = await publicClient.readContract({ address: CONTRACTS.WUSD, abi: WUSD_ABI, functionName: 'balanceOf', args: [account.address], }) if (currentBalance >= buyAmount) { console.log(` ✓ 账户 ${i + 1} 已有足够余额: ${formatUnits(currentBalance, 18)} WUSD`) continue } const transferAmount = buyAmount - currentBalance console.log(` → 转账给账户 ${i + 1}: ${formatUnits(transferAmount, 18)} WUSD...`) try { const hash = await mainWalletClient.writeContract({ address: CONTRACTS.WUSD, abi: WUSD_ABI, functionName: 'transfer', args: [account.address, transferAmount], }) await publicClient.waitForTransactionReceipt({ hash }) console.log(` ✓ 转账成功: ${hash}`) } catch (error: any) { console.error(` ✗ 转账失败: ${error.message}`) } await sleep(1000) } // 第三步: 批量买入 console.log('\n🛒 步骤 3: 执行批量买入...\n') const results: { address: string; amount: string; ytReceived: string; status: string; hash?: string }[] = [] for (let i = 0; i < testAccounts.length; i++) { const { account, buyAmount } = testAccounts[i] console.log(`\n[${i + 1}/${testAccounts.length}] 账户: ${account.address}`) console.log(` 买入金额: ${formatUnits(buyAmount, 18)} WUSD`) const walletClient = createWalletClient({ account, chain: arbitrumSepolia, transport: http(CONFIG.RPC_URL), }) try { // 预览买入 const previewYT = await publicClient.readContract({ address: CONFIG.TARGET_VAULT, abi: VAULT_ABI, functionName: 'previewBuy', args: [buyAmount], }) console.log(` 预计获得: ${formatUnits(previewYT, 18)} ${vaultSymbol}`) // 授权 console.log(` → 授权 WUSD...`) const approveHash = await walletClient.writeContract({ address: CONTRACTS.WUSD, abi: WUSD_ABI, functionName: 'approve', args: [CONFIG.TARGET_VAULT, buyAmount], }) await publicClient.waitForTransactionReceipt({ hash: approveHash }) console.log(` ✓ 授权成功`) // 买入 console.log(` → 执行买入...`) const buyHash = await walletClient.writeContract({ address: CONFIG.TARGET_VAULT, abi: VAULT_ABI, functionName: 'depositYT', args: [buyAmount], }) await publicClient.waitForTransactionReceipt({ hash: buyHash }) // 检查 YT 余额 const ytBalance = await publicClient.readContract({ address: CONFIG.TARGET_VAULT, abi: VAULT_ABI, functionName: 'balanceOf', args: [account.address], }) console.log(` ✓ 买入成功! YT 余额: ${formatUnits(ytBalance, 18)} ${vaultSymbol}`) console.log(` 交易哈希: ${buyHash}`) results.push({ address: account.address, amount: formatUnits(buyAmount, 18), ytReceived: formatUnits(ytBalance, 18), status: 'success', hash: buyHash, }) } catch (error: any) { console.error(` ✗ 买入失败: ${error.message}`) results.push({ address: account.address, amount: formatUnits(buyAmount, 18), ytReceived: '0', status: 'failed', }) } // 交易间隔 if (i < testAccounts.length - 1) { console.log(` ⏳ 等待 ${CONFIG.TX_INTERVAL / 1000} 秒...`) await sleep(CONFIG.TX_INTERVAL) } } // 输出汇总 console.log('\n' + '='.repeat(60)) console.log('📊 模拟结果汇总') console.log('='.repeat(60)) const successCount = results.filter(r => r.status === 'success').length const failCount = results.filter(r => r.status === 'failed').length const totalBought = results .filter(r => r.status === 'success') .reduce((sum, r) => sum + parseFloat(r.amount), 0) const totalYT = results .filter(r => r.status === 'success') .reduce((sum, r) => sum + parseFloat(r.ytReceived), 0) console.log(`\n成功: ${successCount} 笔`) console.log(`失败: ${failCount} 笔`) console.log(`总买入 WUSD: ${totalBought.toFixed(2)}`) console.log(`总获得 ${vaultSymbol}: ${totalYT.toFixed(2)}`) console.log('\n详细记录:') console.table(results.map(r => ({ 地址: r.address.slice(0, 10) + '...', 买入WUSD: r.amount, 获得YT: r.ytReceived, 状态: r.status, }))) console.log('\n✅ 模拟完成!') } // 运行 main().catch(console.error)