#!/usr/bin/env node /** * Test Copy Script * Directly test the copy functionality for a specific parent position * * Usage: * node test-copy.js [target_usd_value] * * Examples: * # Use parent position value (default) * node test-copy.js Cbgjt5zfXd9QTpneDdADc9cKYwXxXRfe57cyfmBbQM41 * * # Use specific target value (e.g., from transaction analysis) * node test-copy.js Cbgjt5zfXd9QTpneDdADc9cKYwXxXRfe57cyfmBbQM41 13.55 */ import { ByrealAPI, JupiterSwapper } from './src/services/index.js'; import { CONFIG } from './src/config/index.js'; import { logger, setLogLevel, formatUsd } from './src/utils/index.js'; // Set log level to see detailed info setLogLevel('info'); const PARENT_POSITION = process.argv[2] || '7rxWLdhk6Hk914QSBBztXJ35DUs4exr7mFJJdYWR2YNw'; const MANUAL_TARGET_VALUE = parseFloat(process.argv[3]) || 0; // Optional: manual target value async function testCopy() { console.log('═══════════════════════════════════════════'); console.log('🧪 Test Copy Script'); console.log(`📍 Parent Position: ${PARENT_POSITION}`); if (MANUAL_TARGET_VALUE > 0) { console.log(`💰 Using Manual Target Value: $${MANUAL_TARGET_VALUE}`); } console.log('═══════════════════════════════════════════\n'); // Step 1: Fetch parent position details console.log('Step 1: Fetching parent position details...'); const parentDetail = await ByrealAPI.getPositionDetail(PARENT_POSITION); if (!parentDetail) { console.error('❌ Failed to fetch position detail'); process.exit(1); } const poolInfo = parentDetail.pool; if (!poolInfo) { console.error('❌ Pool info not found'); process.exit(1); } console.log('✅ Position details:'); console.log(` Pool: ${poolInfo.mintA?.symbol}/${poolInfo.mintB?.symbol}`); console.log(` Token A: ${poolInfo.mintA?.address}`); console.log(` Token B: ${poolInfo.mintB?.address}`); // Get correct field names const nftMint = parentDetail.nftMintAddress || parentDetail.nftMint; const poolAddress = parentDetail.pool?.poolAddress || parentDetail.poolAddress; // Get base value for calculation const parentUsdValue = parseFloat(parentDetail.totalDeposit || parentDetail.totalUsdValue || parentDetail.liquidityUsd || 0); // Use manual target value if provided, otherwise use parent value const targetUsdValue = MANUAL_TARGET_VALUE > 0 ? MANUAL_TARGET_VALUE : parentUsdValue; console.log(` NFT Mint: ${nftMint}`); console.log(` Pool Address: ${poolAddress}`); console.log(` Parent Position Value: $${parentUsdValue}`); if (MANUAL_TARGET_VALUE > 0) { console.log(` ⚠️ Using Target Value: $${targetUsdValue} (not parent value!)`); } else { console.log(` Using Value: $${targetUsdValue}`); } // Step 2: Calculate copy amount console.log('\nStep 2: Calculating copy amount...'); const copyUsdValue = Math.min( targetUsdValue * CONFIG.COPY_MULTIPLIER, CONFIG.MAX_USD_VALUE ); console.log(` Base Value: $${targetUsdValue}`); console.log(` Multiplier: ${CONFIG.COPY_MULTIPLIER}x`); console.log(` Calculated: $${(targetUsdValue * CONFIG.COPY_MULTIPLIER).toFixed(2)}`); console.log(` Max Allowed: $${CONFIG.MAX_USD_VALUE}`); console.log(` ✅ Final Copy Amount: $${copyUsdValue}`); if (copyUsdValue < CONFIG.MIN_USD_VALUE) { console.error(`❌ Copy value $${copyUsdValue} below minimum $${CONFIG.MIN_USD_VALUE}`); process.exit(1); } // Step 3: Calculate token amounts console.log('\nStep 3: Calculating token amounts...'); console.log(` Calling Byreal API with maxUsdValue: $${copyUsdValue}`); const calculation = await ByrealAPI.calculatePosition( PARENT_POSITION, copyUsdValue, nftMint ); if (!calculation) { console.error('❌ Calculation failed'); process.exit(1); } console.log('✅ Calculation result from Byreal API:'); console.log(` Token A: ${calculation.tokenA?.amount} ${calculation.tokenA?.symbol} ($${calculation.tokenA?.valueUsd})`); console.log(` Token B: ${calculation.tokenB?.amount} ${calculation.tokenB?.symbol} ($${calculation.tokenB?.valueUsd})`); console.log(` Estimated Total: $${calculation.estimatedValue}`); console.log(`\n ⚠️ Note: These amounts are based on copy value $${copyUsdValue},`); console.log(` not the full parent position value ($${parentUsdValue})!`); // Debug: Check if amount is token quantity or USD value console.log('\n 🔍 Checking token amounts:'); console.log(` Token A amount field: ${calculation.tokenA?.amount} (type: ${typeof calculation.tokenA?.amount})`); console.log(` Token A valueUsd field: ${calculation.tokenA?.valueUsd}`); console.log(` Token B amount field: ${calculation.tokenB?.amount} (type: ${typeof calculation.tokenB?.amount})`); console.log(` Token B valueUsd field: ${calculation.tokenB?.valueUsd}`); console.log(` If amount ≈ valueUsd, then amount is USD value, not token quantity!`); // Step 4: Swap by valueUsd + 20% buffer, 仅当当前持仓不足时才 swap console.log('\nStep 4: Check balance & swap (valueUsd + 20% buffer)...'); const swapper = new JupiterSwapper(); const tokenA = calculation.tokenA; const tokenB = calculation.tokenB; const BUFFER_PCT = 1.2; const checkAndSwap = async (label, token, valueUsd) => { const balance = await swapper.getTokenBalance(token.address); const requiredAmount = parseFloat(token.amount) || 0; const bufferedAmount = requiredAmount * BUFFER_PCT; const bufferedValueUsd = valueUsd * BUFFER_PCT; console.log(` ${label} (${token.symbol || token.address}): 余额=${balance}, 需要=${requiredAmount}, 含缓冲=${bufferedAmount.toFixed(6)}, valueUsd=$${valueUsd.toFixed(2)}, priceUsd=${token.priceUsd}`); if (requiredAmount > 0 && balance >= bufferedAmount) { console.log(` ✅ ${label}: 余额充足 ${balance} >= ${bufferedAmount.toFixed(6)},跳过 swap`); return true; } let swapUsd = bufferedValueUsd; if (requiredAmount > 0 && balance > 0) { const ratio = balance / bufferedAmount; swapUsd = bufferedValueUsd * (1 - ratio); } console.log(` ${label}: 余额不足,需 swap $${swapUsd.toFixed(2)}`); console.log(' Ready to swap. Continue? (Ctrl+C to cancel)'); await new Promise(resolve => setTimeout(resolve, 3000)); const success = await swapper.swapIfNeeded(token.address, swapUsd); if (!success) { console.error(`❌ Failed to acquire ${label}`); process.exit(1); } return true; }; const valueA = parseFloat(tokenA.valueUsd) || 0; if (valueA > 0) { await checkAndSwap('Token A', tokenA, valueA); } const valueB = parseFloat(tokenB.valueUsd) || 0; if (valueB > 0) { await checkAndSwap('Token B', tokenB, valueB); } // Step 5: Execute copy console.log('\n═══════════════════════════════════════════'); console.log('Step 5: Ready to execute copy!'); console.log('═══════════════════════════════════════════'); console.log(`Parent Position: ${PARENT_POSITION}`); console.log(`NFT Mint: ${nftMint}`); console.log(`Copy Amount: $${copyUsdValue}`); console.log('\n⚠️ This will execute the actual copy transaction!'); console.log('Executing copy...'); const success = await ByrealAPI.copyPosition( nftMint, PARENT_POSITION, copyUsdValue ); if (success) { console.log('\n✅ Copy executed successfully!'); } else { console.log('\n❌ Copy failed!'); process.exit(1); } console.log('\n═══════════════════════════════════════════'); console.log('Test completed'); console.log('═══════════════════════════════════════════'); } testCopy().catch(error => { console.error('Fatal error:', error); process.exit(1); });