#!/usr/bin/env node /** * Adjust Pool Prices Script * Rebalances an existing LiqP pool's prices to match market prices. * Uses IPartyInfo.swapToLimitAmounts() to compute the required swap size, * then executes via pool.swap() (swapToLimit has a known implementation bug). * Runs two passes to compensate for LMSR price coupling across assets. * Optionally basket-stakes a configurable USD amount via mint(). */ import { ethers } from 'ethers'; import { readFile } from 'fs/promises'; import { config } from 'dotenv'; import { execSync } from 'child_process'; config({ path: new URL('../.env', import.meta.url).pathname }); // ============================================================================ // CONFIGURATION // ============================================================================ const NETWORK_CONFIG = { mockchain: { rpcUrl: 'http://localhost:8545', chainId: '31337', tokens: { USDT: { address: '0xbdEd0D2bf404bdcBa897a74E6657f1f12e5C6fb6', coingeckoId: 'tether', decimals: 6 }, USDC: { address: '0x2910E325cf29dd912E3476B61ef12F49cb931096', coingeckoId: 'usd-coin', decimals: 6 } } }, mainnet: { rpcUrl: process.env.MAINNET_RPC_URL, chainId: '1', tokens: { USDT: { address: '0xdAC17F958D2ee523a2206206994597C13D831ec7', coingeckoId: 'tether', decimals: 6 }, USDC: { address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', coingeckoId: 'usd-coin', decimals: 6 }, WBTC: { address: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', coingeckoId: 'wrapped-bitcoin', decimals: 8 }, WETH: { address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', coingeckoId: 'weth', decimals: 18 }, UNI: { address: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984', coingeckoId: 'uniswap', decimals: 18 }, WSOL: { address: '0xD31a59c85aE9D8edEFeC411D448f90841571b89c', coingeckoId: 'solana', decimals: 9 }, TRX: { address: '0x50327c6c5a14DCaDE707ABad2E27eB517df87AB5', coingeckoId: 'tron', decimals: 6 }, AAVE: { address: '0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9', coingeckoId: 'aave', decimals: 18 }, PEPE: { address: '0x6982508145454Ce325dDbE47a25d4ec3d2311933', coingeckoId: 'pepe', decimals: 18 }, SHIB: { address: '0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE', coingeckoId: 'shiba-inu', decimals: 18 } }, uniswap: { swapRouter02: '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45', factory: '0x1F98431c8aD98523631AE4a59f267346ea31F984', // Only 0.05% (500) and 0.3% (3000) tiers are used — 1% pools are avoided. defaultFeeTier: 3000, feeTierOverrides: { USDT: 500, USDC: 500, WBTC: 3000, WETH: 500 } } } }; const NETWORK = process.env.NETWORK || 'mainnet'; const currentConfig = NETWORK_CONFIG[NETWORK]; if (!currentConfig) { console.error(`[!] Invalid NETWORK: ${NETWORK}. Must be 'mockchain' or 'mainnet'`); process.exit(1); } const TOKEN_CONFIG = currentConfig.tokens; // Price deviation threshold: only swap if price is off by more than this const PRICE_DEVIATION_THRESHOLD = 0.001; // 0.1% // ============================================================================ // ABIs (minimal subset needed) // ============================================================================ const ERC20ABI = [ { type: 'function', name: 'balanceOf', stateMutability: 'view', inputs: [{ name: 'account', type: 'address' }], outputs: [{ name: '', type: 'uint256' }] }, { type: 'function', name: 'decimals', stateMutability: 'view', inputs: [], outputs: [{ name: '', type: 'uint8' }] }, { type: 'function', name: 'symbol', stateMutability: 'view', inputs: [], outputs: [{ name: '', type: 'string' }] }, { type: 'function', name: 'approve', stateMutability: 'nonpayable', inputs: [{ name: 'spender', type: 'address' }, { name: 'amount', type: 'uint256' }], outputs: [{ name: '', type: 'bool' }] }, { type: 'function', name: 'allowance', stateMutability: 'view', inputs: [{ name: 'owner', type: 'address' }, { name: 'spender', type: 'address' }], outputs: [{ name: '', type: 'uint256' }] } ]; const IPartyPoolABI = [ { type: 'function', name: 'allTokens', stateMutability: 'view', inputs: [], outputs: [{ name: '', type: 'address[]' }] }, { type: 'function', name: 'totalSupply', stateMutability: 'view', inputs: [], outputs: [{ name: '', type: 'uint256' }] }, { type: 'function', name: 'balances', stateMutability: 'view', inputs: [], outputs: [{ name: '', type: 'uint256[]' }] }, { type: 'function', name: 'balanceOf', stateMutability: 'view', inputs: [{ name: 'account', type: 'address' }], outputs: [{ name: '', type: 'uint256' }] }, { type: 'function', name: 'denominators', stateMutability: 'view', inputs: [], outputs: [{ name: '', type: 'uint256[]' }] } ]; const IPartyInfoABI = [ { type: 'function', name: 'price', stateMutability: 'view', inputs: [{ name: 'pool', type: 'address' }, { name: 'baseTokenIndex', type: 'uint256' }, { name: 'quoteTokenIndex', type: 'uint256' }], outputs: [{ name: '', type: 'uint256' }] }, { type: 'function', name: 'swapToLimitAmounts', stateMutability: 'view', inputs: [{ name: 'pool', type: 'address' }, { name: 'inputTokenIndex', type: 'uint256' }, { name: 'outputTokenIndex', type: 'uint256' }, { name: 'limitPrice', type: 'int128' }], outputs: [{ name: 'amountIn', type: 'uint256' }, { name: 'amountOut', type: 'uint256' }, { name: 'inFee', type: 'uint256' }] }, { type: 'function', name: 'mintAmounts', stateMutability: 'view', inputs: [{ name: 'pool', type: 'address' }, { name: 'lpTokenAmount', type: 'uint256' }], outputs: [{ name: 'depositAmounts', type: 'uint256[]' }] } ]; const UniswapV3FactoryABI = [ { type: 'function', name: 'getPool', stateMutability: 'view', inputs: [{ name: 'tokenA', type: 'address' }, { name: 'tokenB', type: 'address' }, { name: 'fee', type: 'uint24' }], outputs: [{ name: 'pool', type: 'address' }] } ]; const UniswapV3PoolABI = [ { type: 'function', name: 'liquidity', stateMutability: 'view', inputs: [], outputs: [{ name: '', type: 'uint128' }] } ]; const UniswapSwapRouter02ABI = [ { type: 'function', name: 'exactOutputSingle', stateMutability: 'payable', inputs: [{ name: 'params', type: 'tuple', components: [ { name: 'tokenIn', type: 'address' }, { name: 'tokenOut', type: 'address' }, { name: 'fee', type: 'uint24' }, { name: 'recipient', type: 'address' }, { name: 'amountOut', type: 'uint256' }, { name: 'amountInMaximum', type: 'uint256' }, { name: 'sqrtPriceLimitX96', type: 'uint160' } ] }], outputs: [{ name: 'amountIn', type: 'uint256' }] } ]; // ============================================================================ // SIGNER ABSTRACTION // ============================================================================ /** * Build a signer from parsed CLI options. * * mode 'privateKey': all writes go through ethers.Wallet (no cast required). * mode 'trezor': all writes go through `cast send --trezor` (no private key stored). */ function buildSigner(opts, provider) { if (opts.privateKey) { const wallet = new ethers.Wallet(opts.privateKey, provider); return { mode: 'privateKey', address: wallet.address, wallet, rpcUrl: currentConfig.rpcUrl }; } else { return { mode: 'trezor', address: opts.trezorAddress, wallet: null, rpcUrl: currentConfig.rpcUrl }; } } /** Execute a cast send command (Trezor mode). Throws on failure. */ function runCast(signer, to, sigStr, argStr) { const nonce = signer.nextNonce(); const cmd = [ `cast send ${to}`, ` "${sigStr}"`, ` ${argStr}`, ` --rpc-url '${signer.rpcUrl}'`, ` --from ${signer.address}`, ` --nonce ${nonce}`, ` --trezor --mnemonic-index 0` ].join(' \\\n'); console.log(` [cast] ${cmd.replace(/\s+/g, ' ')}`); const output = execSync(cmd, { encoding: 'utf-8' }); if (output) console.log(output.trim()); return output; } // ============================================================================ // HELPERS // ============================================================================ const Q64 = 2n ** 64n; /** * Convert a floating-point value to Q64.64 BigInt. * Uses 1e9-scaled integer arithmetic to avoid float precision loss. */ function floatToQ64(x) { return BigInt(Math.round(x * 1e9)) * Q64 / 1_000_000_000n; } /** * Compute the limitPrice argument for swapToLimitAmounts. * * Derived from swapAmountsForPriceLimit: setting r0_new = r0_target in * r0_new = r0² / (L×(1+r0) - r0²) and solving for L gives: * * L = r0i² × (1 + r0t) / (r0t × (1 + r0i)) * * Requires r0tQ64 < r0iQ64 (target price lower than current in swap direction). * Both arguments are Q64.64 BigInts (raw internal LMSR price ratios, no denom scaling). */ function computeLimitPrice(r0iQ64, r0tQ64) { return r0iQ64 * r0iQ64 * (r0tQ64 + Q64) / (r0tQ64 * (r0iQ64 + Q64)); } async function fetchCoinGeckoPrices(symbols) { const relevantTokens = Object.entries(TOKEN_CONFIG).filter(([sym]) => symbols.includes(sym)); const ids = relevantTokens.map(([, t]) => t.coingeckoId).join(','); const url = `https://api.coingecko.com/api/v3/simple/price?ids=${ids}&vs_currencies=usd`; console.log(`[~] Fetching prices from CoinGecko...`); const response = await fetch(url); if (!response.ok) throw new Error(`CoinGecko API error: ${response.statusText}`); const data = await response.json(); const prices = {}; for (const [symbol, tokenInfo] of relevantTokens) { prices[symbol] = data[tokenInfo.coingeckoId]?.usd; if (!prices[symbol]) throw new Error(`No price returned for ${symbol} (${tokenInfo.coingeckoId})`); } return prices; } async function diagnoseGasError(err, signer, provider) { if (err.code !== 'UNPREDICTABLE_GAS_LIMIT') return; const [balance, feeData] = await Promise.all([ provider.getBalance(signer.address), provider.getFeeData(), ]); const maxFee = feeData.maxFeePerGas || feeData.gasPrice || ethers.BigNumber.from(0); if (maxFee.isZero()) return; const affordableGas = balance.div(maxFee).toNumber(); if (affordableGas < 100_000) { const needed = maxFee.mul(100_000).sub(balance); throw new Error( `Out of ETH for gas: wallet has ${ethers.utils.formatEther(balance)} ETH ` + `(~${affordableGas.toLocaleString()} gas units at ${ethers.utils.formatUnits(maxFee, 'gwei')} gwei maxFee). ` + `Top up at least ${ethers.utils.formatEther(needed)} ETH.` ); } } async function approveToken(signer, provider, tokenAddress, tokenSymbol, spenderAddress, amount) { const decimals = TOKEN_CONFIG[tokenSymbol]?.decimals ?? 18; const tokenReadOnly = new ethers.Contract(tokenAddress, ERC20ABI, provider); const currentAllowance = await tokenReadOnly.allowance(signer.address, spenderAddress); if (currentAllowance.gte(amount)) { console.log(` [=] ${tokenSymbol} allowance already sufficient (${ethers.utils.formatUnits(currentAllowance, decimals)})`); return; } if (signer.mode === 'privateKey') { try { const token = new ethers.Contract(tokenAddress, ERC20ABI, signer.wallet); if (tokenSymbol === 'USDT' && currentAllowance.gt(0)) { const tx = await token.approve(spenderAddress, 0, { nonce: signer.nextNonce() }); await tx.wait(); console.log(` [+] ${tokenSymbol} allowance reset to 0`); } const tx = await token.approve(spenderAddress, amount, { nonce: signer.nextNonce() }); await tx.wait(); console.log(` [+] ${tokenSymbol} approved (tx: ${tx.hash})`); } catch (err) { await diagnoseGasError(err, signer, provider); throw err; } } else { if (tokenSymbol === 'USDT' && currentAllowance.gt(0)) { runCast(signer, tokenAddress, 'approve(address,uint256)', `${spenderAddress} 0`); console.log(` [+] ${tokenSymbol} allowance reset to 0`); } runCast(signer, tokenAddress, 'approve(address,uint256)', `${spenderAddress} ${amount.toString()}`); console.log(` [+] ${tokenSymbol} approved`); } } async function checkPoolLiquidity(provider, tokenA, tokenB, feeTier) { const uniswapConfig = currentConfig.uniswap; const factory = new ethers.Contract(uniswapConfig.factory, UniswapV3FactoryABI, provider); const poolAddress = await factory.getPool(tokenA, tokenB, feeTier); if (poolAddress === ethers.constants.AddressZero) { throw new Error( `No Uniswap V3 pool exists for this pair at fee tier ${feeTier / 10000}% — ` + `try a different fee tier or route through an intermediate token` ); } const pool = new ethers.Contract(poolAddress, UniswapV3PoolABI, provider); const liquidity = await pool.liquidity(); if (liquidity.isZero()) { throw new Error( `Uniswap V3 pool ${poolAddress} has no active in-range liquidity at fee tier ${feeTier / 10000}% — ` + `the pool exists but all liquidity is out of range` ); } } async function buyFromUniswap(signer, provider, tokenOut, tokenOutSymbol, amountOut, tokenIn, tokenInSymbol, maxAmountIn, dryRun) { const uniswapConfig = currentConfig.uniswap; if (!uniswapConfig) { throw new Error(`Uniswap not configured for network '${NETWORK}' — cannot buy ${tokenOutSymbol}`); } const feeTier = uniswapConfig.feeTierOverrides[tokenOutSymbol] ?? uniswapConfig.defaultFeeTier; const outDecimals = TOKEN_CONFIG[tokenOutSymbol]?.decimals ?? 18; const inDecimals = TOKEN_CONFIG[tokenInSymbol]?.decimals ?? 6; console.log(` [~] Buying ${ethers.utils.formatUnits(amountOut, outDecimals)} ${tokenOutSymbol} from Uniswap V3 (fee tier: ${feeTier / 10000}%)`); console.log(` [~] Max spending: ${ethers.utils.formatUnits(maxAmountIn, inDecimals)} ${tokenInSymbol}`); await checkPoolLiquidity(provider, tokenIn, tokenOut, feeTier); if (dryRun) { console.log(` [DRY-RUN] Would buy ${tokenOutSymbol} via Uniswap V3`); return; } await approveToken(signer, provider, tokenIn, tokenInSymbol, uniswapConfig.swapRouter02, maxAmountIn); if (signer.mode === 'privateKey') { const router = new ethers.Contract(uniswapConfig.swapRouter02, UniswapSwapRouter02ABI, signer.wallet); let tx; try { tx = await router.exactOutputSingle({ tokenIn, tokenOut, fee: feeTier, recipient: signer.address, amountOut, amountInMaximum: maxAmountIn, sqrtPriceLimitX96: 0 }, { nonce: signer.nextNonce() }); } catch (err) { await diagnoseGasError(err, signer, provider); throw err; } await tx.wait(); console.log(` [+] Bought ${tokenOutSymbol} via Uniswap (tx: ${tx.hash})`); } else { // cast send with tuple arg: "(tokenIn,tokenOut,fee,recipient,amountOut,amountInMaximum,sqrtLimit)" const tupleArg = `"(${tokenIn},${tokenOut},${feeTier},${signer.address},${amountOut.toString()},${maxAmountIn.toString()},0)"`; runCast( signer, uniswapConfig.swapRouter02, 'exactOutputSingle((address,address,uint24,address,uint256,uint256,uint160))', tupleArg ); console.log(` [+] Bought ${tokenOutSymbol} via Uniswap`); } } // ============================================================================ // REBALANCING LOGIC // ============================================================================ /** Query the current pool price deviation for a single token vs CoinGecko. */ async function getTokenDeviation(partyInfo, poolAddr, token, quoteToken, usdPrices) { const currentQ128BN = await partyInfo.price(poolAddr, token.idx, quoteToken.idx); const currentQ128 = BigInt(currentQ128BN.toString()); // partyInfo.price returns r0 * 10^(quoteDec-baseDec) in Q128 format. const currentQ64n = currentQ128 / Q64; const poolPriceRaw = Number(currentQ64n) / Number(Q64); const poolPriceHuman = poolPriceRaw * Math.pow(10, token.decimals - quoteToken.decimals); const poolUSD = poolPriceHuman * usdPrices[quoteToken.symbol]; const targetPriceHuman = usdPrices[token.symbol] / usdPrices[quoteToken.symbol]; const deviation = targetPriceHuman === 0 ? 0 : (poolPriceHuman - targetPriceHuman) / targetPriceHuman * 100; return { token, deviation, deviationAbs: Math.abs(deviation), poolUSD }; } /** * Execute a rebalancing swap for a single token via pool.swap(). * Returns true if a swap was executed, false if price is already within threshold. */ async function rebalanceTokenIfNeeded( { signer, provider, partyInfo, poolAddr, quoteToken, usdPrices, dryRun, denominators }, token ) { const currentQ128BN = await partyInfo.price(poolAddr, token.idx, quoteToken.idx); const currentQ128 = BigInt(currentQ128BN.toString()); const currentQ64n = currentQ128 / Q64; const poolPriceRaw = Number(currentQ64n) / Number(Q64); const poolPriceHuman = poolPriceRaw * Math.pow(10, token.decimals - quoteToken.decimals); const targetPriceHuman = usdPrices[token.symbol] / usdPrices[quoteToken.symbol]; const deviation = targetPriceHuman === 0 ? 0 : (poolPriceHuman - targetPriceHuman) / targetPriceHuman * 100; if (Math.abs(deviation) < PRICE_DEVIATION_THRESHOLD * 100) { return false; } // LMSR behaves like a regular AMM: injecting a token makes it cheaper. // Overpriced base: inject TOKEN, extract QUOTE → lowers TOKEN price. // Underpriced base: inject QUOTE, extract TOKEN → raises TOKEN price. const overpriced = poolPriceHuman > targetPriceHuman; const inputToken = overpriced ? token : quoteToken; const outputToken = overpriced ? quoteToken : token; const inputIdx = overpriced ? token.idx : quoteToken.idx; const outputIdx = overpriced ? quoteToken.idx : token.idx; const direction = overpriced ? 'lower-price' : 'raise-price'; console.log(` → ${direction}: inject ${inputToken.symbol}, extract ${outputToken.symbol}`); // Compute raw internal Q64.64 r0 for the swap direction using actual pool denominators. // partyInfo.price(i,j) = r0_internal(i,j) × D[j] / D[i] in Q128.128 // → r0_internal Q64.64 = price_Q128 × D[i] / D[j] / Q64 const r0PriceQ128 = BigInt((await partyInfo.price(poolAddr, inputIdx, outputIdx)).toString()); const r0InitialQ64 = r0PriceQ128 * denominators[inputIdx] / denominators[outputIdx] / Q64; // Scale r0_initial to get r0_target using the human-price ratio in the swap direction. // overpriced (TOKEN→QUOTE): target price is lower → ratio = target/current < 1 // underpriced (QUOTE→TOKEN): we want lower r0(QUOTE,TOKEN) → ratio = current/target < 1 const numPrice = overpriced ? targetPriceHuman : poolPriceHuman; const denPrice = overpriced ? poolPriceHuman : targetPriceHuman; const r0TargetQ64 = r0InitialQ64 * BigInt(Math.round(numPrice * 1e9)) / BigInt(Math.round(denPrice * 1e9)); const limitPriceQ64 = computeLimitPrice(r0InitialQ64, r0TargetQ64); const r0iFloat = Number(r0InitialQ64) / Number(Q64); const r0tFloat = Number(r0TargetQ64) / Number(Q64); console.log(` r0Initial=${r0iFloat.toExponential(4)} r0Target=${r0tFloat.toExponential(4)} limitPrice=${limitPriceQ64.toString()}`); const REDUCE_SIZE_RETRIES = 5; let amountIn, amountOut, inFee, effectiveLimitPrice; let r0TargetRetry = r0TargetQ64; for (let attempt = 0; ; attempt++) { const retryLimitPrice = attempt === 0 ? limitPriceQ64 : computeLimitPrice(r0InitialQ64, r0TargetRetry); try { const result = await partyInfo.swapToLimitAmounts(poolAddr, inputIdx, outputIdx, retryLimitPrice.toString()); amountIn = result.amountIn; amountOut = result.amountOut; inFee = result.inFee; effectiveLimitPrice = retryLimitPrice; break; } catch (err) { if ( attempt < REDUCE_SIZE_RETRIES && err.message.includes('arithmetic underflow or overflow') ) { r0TargetRetry = r0InitialQ64 - (r0InitialQ64 - r0TargetRetry) / 10n; console.log(` [!] swapToLimitAmounts overflow — retrying with reduced target (attempt ${attempt + 2}/${REDUCE_SIZE_RETRIES + 1})`); continue; } console.log(` [!] swapToLimitAmounts reverted: ${err.message} — skipping`); return false; } } if (amountIn.isZero()) { console.log(` [+] amountIn=0, nothing to do`); return false; } console.log(` amountIn: ${ethers.utils.formatUnits(amountIn, inputToken.decimals)} ${inputToken.symbol}`); console.log(` amountOut: ${ethers.utils.formatUnits(amountOut, outputToken.decimals)} ${outputToken.symbol}`); console.log(` fee: ${ethers.utils.formatUnits(inFee, inputToken.decimals)} ${inputToken.symbol}`); // Ensure the signer holds enough of the input token; buy shortfall from Uniswap if needed const inputERC20 = new ethers.Contract(inputToken.address, ERC20ABI, provider); const inputBalance = await inputERC20.balanceOf(signer.address); if (inputBalance.lt(amountIn)) { const deficit = amountIn.sub(inputBalance); console.log(` [!] Insufficient ${inputToken.symbol}: ` + `have ${ethers.utils.formatUnits(inputBalance, inputToken.decimals)}, ` + `need ${ethers.utils.formatUnits(amountIn, inputToken.decimals)}, ` + `deficit ${ethers.utils.formatUnits(deficit, inputToken.decimals)}`); if (inputToken.symbol === quoteToken.symbol) { console.log(` [!] Input is the quote token — no way to acquire more, skipping`); return false; } const deficitUSD = Number(ethers.utils.formatUnits(deficit, inputToken.decimals)) * usdPrices[inputToken.symbol]; const maxQuoteIn = ethers.utils.parseUnits( (deficitUSD * 1.02 / usdPrices[quoteToken.symbol]).toFixed(quoteToken.decimals), quoteToken.decimals ); await buyFromUniswap( signer, provider, inputToken.address, inputToken.symbol, deficit, quoteToken.address, quoteToken.symbol, maxQuoteIn, dryRun ); } console.log(` [~] Approving ${inputToken.symbol} for pool...`); if (!dryRun) { await approveToken(signer, provider, inputToken.address, inputToken.symbol, poolAddr, amountIn.mul(101).div(100)); } else { console.log(` [DRY-RUN] Would approve ${inputToken.symbol}`); } // Execute via pool.swap() with the computed amountIn and the effective limit price. const deadline = Math.floor(Date.now() / 1000) + 300; if (!dryRun) { if (signer.mode === 'privateKey') { const poolContract = new ethers.Contract(poolAddr, [ { type: 'function', name: 'swap', stateMutability: 'payable', inputs: [ { name: 'payer', type: 'address' }, { name: 'fundingSelector', type: 'bytes4' }, { name: 'receiver', type: 'address' }, { name: 'inputTokenIndex', type: 'uint256' }, { name: 'outputTokenIndex', type: 'uint256' }, { name: 'maxAmountIn', type: 'uint256' }, { name: 'limitPrice', type: 'int128' }, { name: 'deadline', type: 'uint256' }, { name: 'unwrap', type: 'bool' }, { name: 'cbData', type: 'bytes' } ], outputs: [{ name: 'amountIn', type: 'uint256' }, { name: 'amountOut', type: 'uint256' }, { name: 'inFee', type: 'uint256' }] } ], signer.wallet); let tx; let swapAmountIn = amountIn; for (let attempt = 0; ; attempt++) { const nonce = signer.nextNonce(); try { tx = await poolContract.swap( signer.address, '0x00000000', signer.address, inputIdx, outputIdx, swapAmountIn, effectiveLimitPrice.toString(), deadline, false, '0x', { nonce } ); break; } catch (err) { if ( attempt < REDUCE_SIZE_RETRIES && err.code === 'UNPREDICTABLE_GAS_LIMIT' && err.message.includes('arithmetic underflow or overflow') ) { signer._nonce--; // tx never submitted; reuse nonce swapAmountIn = swapAmountIn.div(10); console.log(` [!] Arithmetic overflow — retrying with ${ethers.utils.formatUnits(swapAmountIn, inputToken.decimals)} ${inputToken.symbol} (attempt ${attempt + 2}/${REDUCE_SIZE_RETRIES})`); continue; } await diagnoseGasError(err, signer, provider); throw err; } } await tx.wait(); console.log(` [+] Swap executed (tx: ${tx.hash})`); } else { runCast( signer, poolAddr, 'swap(address,bytes4,address,uint256,uint256,uint256,int128,uint256,bool,bytes)', `${signer.address} "0x00000000" ${signer.address} ${inputIdx} ${outputIdx} ${amountIn.toString()} ${effectiveLimitPrice.toString()} ${deadline} false "0x"` ); console.log(` [+] Swap executed`); } const newQ128BN = await partyInfo.price(poolAddr, token.idx, quoteToken.idx); const newQ64n = BigInt(newQ128BN.toString()) / Q64; const newPoolUSD = Number(newQ64n) / Number(Q64) * Math.pow(10, token.decimals - quoteToken.decimals) * usdPrices[quoteToken.symbol]; const residual = (newPoolUSD - usdPrices[token.symbol]) / usdPrices[token.symbol] * 100; console.log(` [+] Updated pool price: $${newPoolUSD.toPrecision(6)} (market: $${usdPrices[token.symbol].toPrecision(6)} residual: ${residual.toFixed(3)}%)`); } else { console.log(` [DRY-RUN] Would swap ${inputToken.symbol} → ${outputToken.symbol} via pool.swap()`); } return true; } // ============================================================================ // CLI ARGUMENT PARSING // ============================================================================ function parseArgs() { const args = process.argv.slice(2); if (args.includes('--help') || args.includes('-h')) { console.log(` Usage: node adjust_pool_prices_and_stake.js --pool
(--private-key