diff --git a/bin/deploy b/bin/deploy
index 6650ead..92ac51f 100755
--- a/bin/deploy
+++ b/bin/deploy
@@ -26,6 +26,14 @@ else
echo Building
fi
+# Load secret env vars (including NEXT_PUBLIC_* vars baked into the static bundle)
+if [ -f .env-secret ]; then
+ set -a
+ # shellcheck disable=SC1091
+ source .env-secret
+ set +a
+fi
+
npm run build || exit 1
if [ "$BUILD" != "1" ]; then
diff --git a/scripts/adjust_pool_prices_and_stake.js b/scripts/adjust_pool_prices_and_stake.js
new file mode 100644
index 0000000..a0f93c8
--- /dev/null
+++ b/scripts/adjust_pool_prices_and_stake.js
@@ -0,0 +1,911 @@
+#!/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 | --trezor ) [OPTIONS]
+
+Signer (exactly one required):
+ --private-key Sign all transactions with this private key
+ --trezor Sign all transactions via Trezor hardware wallet at given address
+
+Options:
+ --pool Target LiqP pool address (required)
+ --quote Quote token symbol (default: auto-detect USDT/USDC)
+ --iterations Max rebalancing iterations, best-first (default: 10)
+ --stake USD amount to basket-stake via mint() after rebalancing
+ --skip-rebalance Skip price adjustment; only run staking (requires --stake)
+ --dry-run Preview only, no transactions executed
+ --help, -h Show this help message
+
+Environment:
+ NETWORK 'mainnet' (default) or 'mockchain'
+ MAINNET_RPC_URL RPC endpoint (required for mainnet)
+
+Examples:
+ node adjust_pool_prices_and_stake.js --pool 0x1234... --private-key 0xabc... --dry-run
+ node adjust_pool_prices_and_stake.js --pool 0x1234... --trezor 0xdef... --stake 500
+ node adjust_pool_prices_and_stake.js --pool 0x1234... --trezor 0xdef... --stake 500 --skip-rebalance
+`);
+ process.exit(0);
+ }
+
+ const opts = {
+ pool: null, quote: null, stake: null, iterations: 10,
+ privateKey: null, trezorAddress: null,
+ skipRebalance: false, dryRun: false
+ };
+
+ for (let i = 0; i < args.length; i++) {
+ if (args[i] === '--pool' && i + 1 < args.length) { opts.pool = args[++i]; }
+ else if (args[i] === '--quote' && i + 1 < args.length) { opts.quote = args[++i].toUpperCase(); }
+ else if (args[i] === '--iterations' && i + 1 < args.length) { opts.iterations = parseInt(args[++i], 10); }
+ else if (args[i] === '--stake' && i + 1 < args.length) { opts.stake = parseFloat(args[++i]); }
+ else if (args[i] === '--private-key' && i + 1 < args.length) { opts.privateKey = args[++i]; }
+ else if (args[i] === '--trezor' && i + 1 < args.length) { opts.trezorAddress = args[++i]; }
+ else if (args[i] === '--skip-rebalance') { opts.skipRebalance = true; }
+ else if (args[i] === '--dry-run') { opts.dryRun = true; }
+ }
+
+ if (!opts.pool) {
+ console.error('[!] --pool is required.');
+ process.exit(1);
+ }
+ if (!opts.privateKey && !opts.trezorAddress) {
+ console.error('[!] Signer required: provide --private-key or --trezor .');
+ process.exit(1);
+ }
+ if (opts.privateKey && opts.trezorAddress) {
+ console.error('[!] Provide --private-key OR --trezor, not both.');
+ process.exit(1);
+ }
+ if (opts.skipRebalance && !opts.stake) {
+ console.error('[!] --skip-rebalance requires --stake .');
+ process.exit(1);
+ }
+
+ return opts;
+}
+
+// ============================================================================
+// MAIN
+// ============================================================================
+
+async function main() {
+ const opts = parseArgs();
+ const { pool: poolAddr, dryRun } = opts;
+
+ const signerLabel = opts.trezorAddress ? `trezor:${opts.trezorAddress}` : 'private-key';
+ console.log(`${'='.repeat(70)}`);
+ console.log(`Adjust Pool Prices`);
+ console.log(`Network: ${NETWORK} Pool: ${poolAddr} Signer: ${signerLabel}${opts.skipRebalance ? ' [STAKE-ONLY]' : ''}${dryRun ? ' [DRY-RUN]' : ''}`);
+ console.log(`${'='.repeat(70)}\n`);
+
+ if (!currentConfig.rpcUrl) {
+ console.error(`[!] Missing RPC URL for network '${NETWORK}' (set MAINNET_RPC_URL)`);
+ process.exit(1);
+ }
+
+ // ── Phase 0: Setup ─────────────────────────────────────────────────────────
+
+ const provider = new ethers.providers.JsonRpcProvider(currentConfig.rpcUrl);
+ const signer = buildSigner(opts, provider);
+ console.log(`[+] Signer: ${signer.address} (${signer.mode})`);
+
+ if (!dryRun) {
+ signer._nonce = await provider.getTransactionCount(signer.address, "pending");
+ console.log(`[+] Nonce: ${signer._nonce} (pending)`);
+ } else {
+ signer._nonce = 0;
+ }
+ signer.nextNonce = () => signer._nonce++;
+
+ const chainInfoData = JSON.parse(
+ await readFile(new URL('../src/contracts/liqp-deployments.json', import.meta.url), 'utf-8')
+ );
+ const chainContracts = chainInfoData[currentConfig.chainId]?.v1;
+ if (!chainContracts) {
+ console.error(`[!] No contract addresses for chainId ${currentConfig.chainId}`);
+ process.exit(1);
+ }
+ const PARTY_INFO_ADDRESS = chainContracts.PartyInfo;
+ console.log(`[+] PartyInfo: ${PARTY_INFO_ADDRESS}`);
+
+ const pool = new ethers.Contract(poolAddr, IPartyPoolABI, provider);
+ const partyInfo = new ethers.Contract(PARTY_INFO_ADDRESS, IPartyInfoABI, provider);
+
+ const denomsBN = await pool.denominators();
+ const denominators = denomsBN.map(d => BigInt(d.toString()));
+ console.log(`[+] Denominators: [${denominators.join(', ')}]`);
+
+ const tokenAddresses = await pool.allTokens();
+ console.log(`[+] Pool tokens: ${tokenAddresses.length}`);
+
+ const tokenConfigByAddr = {};
+ for (const [sym, info] of Object.entries(TOKEN_CONFIG)) {
+ tokenConfigByAddr[info.address.toLowerCase()] = { symbol: sym, ...info };
+ }
+
+ const poolTokens = tokenAddresses.map((addr, idx) => {
+ const meta = tokenConfigByAddr[addr.toLowerCase()];
+ if (!meta) throw new Error(`Unknown token at pool index ${idx}: ${addr}`);
+ return { idx, address: addr, symbol: meta.symbol, decimals: meta.decimals, coingeckoId: meta.coingeckoId };
+ });
+
+ let quoteToken;
+ if (opts.quote) {
+ quoteToken = poolTokens.find(t => t.symbol === opts.quote);
+ if (!quoteToken) throw new Error(`Quote token ${opts.quote} not found in pool`);
+ } else {
+ quoteToken = poolTokens.find(t => t.symbol === 'USDT') ?? poolTokens.find(t => t.symbol === 'USDC');
+ if (!quoteToken) throw new Error('No USDT or USDC in pool; specify --quote ');
+ }
+ console.log(`[+] Quote: ${quoteToken.symbol} (index ${quoteToken.idx})\n`);
+
+ const nonQuoteTokens = poolTokens.filter(t => t.idx !== quoteToken.idx);
+
+ // ── Phase 1: Fetch Prices ───────────────────────────────────────────────────
+
+ const symbols = poolTokens.map(t => t.symbol);
+ const usdPrices = await fetchCoinGeckoPrices(symbols);
+
+ console.log(`\n[+] Market prices:`);
+ for (const sym of symbols) {
+ console.log(` ${sym.padEnd(6)}: $${usdPrices[sym].toLocaleString()}`);
+ }
+
+ const ctx = { signer, provider, partyInfo, poolAddr, poolTokens, quoteToken, usdPrices, dryRun, denominators };
+
+ // ── Phase 2: Rebalancing — best-first iterative (skipped with --skip-rebalance) ─
+
+ let swapCount = 0;
+
+ if (opts.skipRebalance) {
+ console.log(`\n[~] Skipping price adjustment (--skip-rebalance).`);
+ } else {
+ console.log(`\n${'─'.repeat(70)}`);
+ console.log(`Rebalancing — best-first, up to ${opts.iterations} iterations (threshold: ${PRICE_DEVIATION_THRESHOLD * 100}%)`);
+ console.log(`${'─'.repeat(70)}`);
+
+ let converged = false;
+ for (let iter = 1; iter <= opts.iterations; iter++) {
+ // Query all pool prices in parallel, find the most deviant token
+ const deviations = await Promise.all(
+ nonQuoteTokens.map(t => getTokenDeviation(partyInfo, poolAddr, t, quoteToken, usdPrices))
+ );
+
+ console.log(`\n[iter ${iter}/${opts.iterations}]`);
+ for (const d of deviations) {
+ const marker = d.deviationAbs >= PRICE_DEVIATION_THRESHOLD * 100 ? '!' : '=';
+ console.log(` [${marker}] ${d.token.symbol.padEnd(6)} pool=$${d.poolUSD.toPrecision(6)} market=$${usdPrices[d.token.symbol].toPrecision(6)} deviation=${d.deviation.toFixed(3)}%`);
+ }
+
+ const worst = deviations.reduce((a, b) => a.deviationAbs > b.deviationAbs ? a : b);
+
+ if (worst.deviationAbs < PRICE_DEVIATION_THRESHOLD * 100) {
+ console.log(`\n[+] All prices within threshold — converged after ${swapCount} swap(s).`);
+ converged = true;
+ break;
+ }
+
+ console.log(`\n Swapping ${worst.token.symbol} (${worst.deviation.toFixed(3)}% off)`);
+ const swapped = await rebalanceTokenIfNeeded(ctx, worst.token);
+ if (swapped) swapCount++;
+ }
+
+ if (!converged) {
+ console.log(`\n[~] Reached iteration limit (${opts.iterations}) with ${swapCount} swap(s) executed.`);
+ }
+ }
+
+ // ── Phase 4: Optional Basket Stake via mint() ──────────────────────────────
+
+ if (opts.stake) {
+ console.log(`\n${'='.repeat(70)}`);
+ console.log(`Phase 4: Basket Stake ($${opts.stake} USD)`);
+ console.log(`${'='.repeat(70)}`);
+
+ const totalSupplyBN = await pool.totalSupply();
+ const balancesBN = await pool.balances();
+
+ let tvlUSD = 0;
+ for (let i = 0; i < poolTokens.length; i++) {
+ tvlUSD += Number(ethers.utils.formatUnits(balancesBN[i], poolTokens[i].decimals)) * usdPrices[poolTokens[i].symbol];
+ }
+
+ const totalSupplyFloat = Number(ethers.utils.formatUnits(totalSupplyBN, 18));
+ const lpPriceUSD = tvlUSD / totalSupplyFloat;
+ console.log(`\n[~] Pool TVL: $${tvlUSD.toFixed(2)}, LP price: $${lpPriceUSD.toFixed(6)}`);
+
+ const lpAmountFloat = opts.stake / lpPriceUSD;
+ const lpAmount = ethers.utils.parseUnits(lpAmountFloat.toFixed(18), 18);
+ console.log(`[~] Target LP amount: ${lpAmountFloat.toFixed(6)} LP tokens (~$${opts.stake})`);
+
+ const depositAmounts = await partyInfo.mintAmounts(poolAddr, lpAmount);
+ console.log(`\n[~] Required deposits for mint:`);
+
+ const deficits = [];
+ let totalQuoteNeeded = ethers.BigNumber.from(0);
+
+ for (let i = 0; i < poolTokens.length; i++) {
+ const t = poolTokens[i];
+ const required = depositAmounts[i];
+ const tokenERC20 = new ethers.Contract(t.address, ERC20ABI, provider);
+ const balance = await tokenERC20.balanceOf(signer.address);
+ const sufficient = balance.gte(required);
+
+ console.log(
+ ` ${t.symbol.padEnd(6)}: ` +
+ `${ethers.utils.formatUnits(required, t.decimals).padStart(22)} required ` +
+ `${ethers.utils.formatUnits(balance, t.decimals).padStart(22)} in wallet ` +
+ `${sufficient ? '✓' : '✗'}`
+ );
+
+ if (!sufficient) {
+ const deficit = required.sub(balance);
+ const deficitUSD = Number(ethers.utils.formatUnits(deficit, t.decimals)) * usdPrices[t.symbol];
+ const quoteNeeded = ethers.utils.parseUnits(
+ (deficitUSD * 1.02 / usdPrices[quoteToken.symbol]).toFixed(quoteToken.decimals),
+ quoteToken.decimals
+ );
+ totalQuoteNeeded = totalQuoteNeeded.add(quoteNeeded);
+ deficits.push({ token: t, deficit, quoteNeeded });
+ }
+ }
+
+ // Pre-flight: fail before any transaction if quote balance is insufficient
+ if (deficits.length > 0) {
+ const quoteERC20 = new ethers.Contract(quoteToken.address, ERC20ABI, provider);
+ const quoteBalance = await quoteERC20.balanceOf(signer.address);
+
+ if (quoteBalance.lt(totalQuoteNeeded)) {
+ console.error(`\n[!] Pre-flight check FAILED — insufficient ${quoteToken.symbol} to cover token deficits:`);
+ for (const d of deficits) {
+ console.error(` ${d.token.symbol}: deficit ${ethers.utils.formatUnits(d.deficit, d.token.decimals)}` +
+ ` → costs ~${ethers.utils.formatUnits(d.quoteNeeded, quoteToken.decimals)} ${quoteToken.symbol}`);
+ }
+ console.error(` Total needed: ${ethers.utils.formatUnits(totalQuoteNeeded, quoteToken.decimals)} ${quoteToken.symbol}`);
+ console.error(` Wallet has: ${ethers.utils.formatUnits(quoteBalance, quoteToken.decimals)} ${quoteToken.symbol}`);
+ process.exit(1);
+ }
+ console.log(`\n[+] Pre-flight check passed`);
+ }
+
+ if (dryRun) {
+ console.log(`\n[DRY-RUN] Would buy ${deficits.length} deficit token(s) and mint ${lpAmountFloat.toFixed(6)} LP`);
+ } else {
+ for (const d of deficits) {
+ console.log(`\n[~] Buying ${d.token.symbol} deficit from Uniswap...`);
+ await buyFromUniswap(
+ signer, provider,
+ d.token.address, d.token.symbol, d.deficit,
+ quoteToken.address, quoteToken.symbol, d.quoteNeeded,
+ false
+ );
+ }
+
+ console.log(`\n[~] Approving tokens for pool mint...`);
+ for (let i = 0; i < poolTokens.length; i++) {
+ const t = poolTokens[i];
+ const required = depositAmounts[i];
+ if (required.gt(0)) {
+ await approveToken(signer, provider, t.address, t.symbol, poolAddr, required.mul(101).div(100));
+ }
+ }
+
+ const deadline = Math.floor(Date.now() / 1000) + 300;
+
+ if (signer.mode === 'privateKey') {
+ const poolContract = new ethers.Contract(poolAddr, [
+ { type: 'function', name: 'mint', stateMutability: 'payable',
+ inputs: [
+ { name: 'payer', type: 'address' }, { name: 'receiver', type: 'address' },
+ { name: 'lpTokenAmount', type: 'uint256' }, { name: 'deadline', type: 'uint256' }
+ ],
+ outputs: [{ name: 'lpMinted', type: 'uint256' }]
+ }
+ ], signer.wallet);
+ let tx;
+ try {
+ tx = await poolContract.mint(signer.address, signer.address, lpAmount, deadline, { nonce: signer.nextNonce() });
+ } catch (err) {
+ await diagnoseGasError(err, signer, provider);
+ throw err;
+ }
+ await tx.wait();
+ console.log(`[+] Mint executed (tx: ${tx.hash})`);
+ } else {
+ runCast(
+ signer, poolAddr,
+ 'mint(address,address,uint256,uint256)',
+ `${signer.address} ${signer.address} ${lpAmount.toString()} ${deadline}`
+ );
+ console.log(`[+] Mint executed`);
+ }
+
+ const newLpBalance = await pool.balanceOf(signer.address);
+ console.log(`[+] Wallet LP balance: ${ethers.utils.formatUnits(newLpBalance, 18)}`);
+ }
+ }
+
+ // ── Summary ────────────────────────────────────────────────────────────────
+
+ console.log(`\n${'='.repeat(70)}`);
+ console.log(`Summary`);
+ console.log(`${'='.repeat(70)}`);
+ if (opts.skipRebalance) {
+ console.log(`Rebalance: skipped (--skip-rebalance)`);
+ } else {
+ console.log(`Rebalance: ${swapCount} swap(s), up to ${opts.iterations} iterations${dryRun ? ' (dry-run)' : ''}`);
+ }
+ if (opts.stake) {
+ console.log(`Stake: $${opts.stake} USD${dryRun ? ' (dry-run)' : ''}`);
+ }
+ console.log(`Done.`);
+}
+
+main().catch(err => {
+ console.error('[!] Fatal error:', err.message || err);
+ process.exit(1);
+});
diff --git a/scripts/create_pool_from_prices.js b/scripts/create_pool_from_prices.js
index e968f08..e0e0b9f 100644
--- a/scripts/create_pool_from_prices.js
+++ b/scripts/create_pool_from_prices.js
@@ -8,117 +8,60 @@ import { ethers } from 'ethers';
import { readFile } from 'fs/promises';
import { config } from 'dotenv';
-// Load environment variables from .env-secret
-config({ path: new URL('../.env-secret', import.meta.url).pathname });
+// Load environment variables from .env
+config({ path: new URL('../.env', import.meta.url).pathname });
// ============================================================================
-// CONFIGURATION
+// LOAD POOL CONFIG
// ============================================================================
-// Default pool parameters
-const POOL_NAME = 'Original Genesis of Liquidity Party';
-const POOL_SYMBOL = 'OG.LP';
-const KAPPA = ethers.BigNumber.from('184467440737095520');
-const FLASH_FEE_PPM = 5;
-const INPUT_USD_AMOUNT = 75; // Size of initial mint in USD
+const args = process.argv.slice(2);
+
+function getArg(flag) {
+ const idx = args.indexOf(flag);
+ return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;
+}
+
+const configFile = getArg('--config') || 'pool-test.json';
+const poolConfig = JSON.parse(
+ await readFile(new URL(configFile, import.meta.url), 'utf-8')
+);
+
+const TEST_TOKENS = poolConfig.tokens;
+
+// Convert kappa from float to Q64.64 fixed-point BigInt
+// e.g. 0.01 -> round(0.01 * 2^64)
+function kappaToFixedPoint(kappaFloat) {
+ const str = kappaFloat.toString();
+ const [whole, frac = ''] = str.split('.');
+ const precision = frac.length;
+ const numerator = BigInt(whole + frac);
+ const denominator = BigInt(10 ** precision);
+ const scale = 2n ** 64n;
+ return (numerator * scale + denominator / 2n) / denominator;
+}
+
+const KAPPA = ethers.BigNumber.from(kappaToFixedPoint(poolConfig.kappa).toString());
+const FLASH_FEE_PPM = poolConfig.flashFeePpm;
+const INPUT_USD_AMOUNT = poolConfig.inputUsdAmount;
+
+// ============================================================================
+// NETWORK CONFIGURATION
+// ============================================================================
-// 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
+ rpcUrl: process.env.MOCKCHAIN_RPC_URL || 'http://localhost:8545',
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
- }
- }
},
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.4 bps
- },
- USDC: {
- address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
- coingeckoId: 'usd-coin',
- decimals: 6,
- feePpm: 40 // 0.4 bps
- },
- WBTC: {
- address: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
- coingeckoId: 'wrapped-bitcoin',
- decimals: 8,
- feePpm: 2_00 // 2 bps
- },
- WETH: {
- address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
- coingeckoId: 'weth',
- decimals: 18,
- feePpm: 2_50 // 2.5 bps
- },
- UNI: {
- address: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
- coingeckoId: 'uniswap',
- decimals: 18,
- feePpm: 9_50 // 9.5 bps
- },
- WSOL: {
- address: '0xD31a59c85aE9D8edEFeC411D448f90841571b89c', // Wormhole Wrapped SOL
- coingeckoId: 'solana',
- decimals: 9,
- feePpm: 9_50 // 9.5 bps
- },
- TRX: {
- address: '0x50327c6c5a14DCaDE707ABad2E27eB517df87AB5',
- coingeckoId: 'tron',
- decimals: 6,
- feePpm: 9_50 // 9.5 bps
- },
- AAVE: {
- address: '0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9',
- coingeckoId: 'aave',
- decimals: 18,
- feePpm: 12_50 // 12.5 bps
- },
- PEPE: {
- address: '0x6982508145454Ce325dDbE47a25d4ec3d2311933',
- coingeckoId: 'pepe',
- decimals: 18,
- feePpm: 18_50 // 18.5 bps
- },
- SHIB: {
- address: '0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE',
- coingeckoId: 'shiba-inu',
- decimals: 18,
- feePpm: 18_50 // 18.5 bps
- }
- }
}
};
-// Network flag: 'mockchain' or 'mainnet'
const NETWORK = process.env.NETWORK || 'mainnet';
-// Get current network config
const currentConfig = NETWORK_CONFIG[NETWORK];
if (!currentConfig) {
console.error(`[!] Invalid NETWORK: ${NETWORK}. Must be 'mockchain' or 'mainnet'`);
@@ -126,10 +69,10 @@ if (!currentConfig) {
}
const RPC_URL = currentConfig.rpcUrl;
-const PRIVATE_KEY = currentConfig.privateKey;
-const RECEIVER_ADDRESS = currentConfig.receiverAddress || process.env.RECEIVER_ADDRESS;
+const PRIVATE_KEY = process.env.PRIVATE_KEY;
+const RECEIVER_PRIVATE_KEY = process.env.RECEIVER_PRIVATE_KEY;
+const RECEIVER_ADDRESS = 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');
@@ -137,26 +80,23 @@ if (NETWORK === 'mainnet' && (!RPC_URL || !PRIVATE_KEY)) {
}
if (!RECEIVER_ADDRESS) {
- console.error('[!] Missing RECEIVER_ADDRESS in .env-secret file');
+ console.error('[!] Missing RECEIVER_ADDRESS in .env file');
process.exit(1);
}
-// Use network-specific tokens
-const TEST_TOKENS = currentConfig.tokens;
-
const DEFAULT_POOL_PARAMS = {
- name: POOL_NAME,
- symbol: POOL_SYMBOL,
- kappa: KAPPA, //0.01 * 2^64
+ name: poolConfig.name,
+ symbol: poolConfig.symbol,
+ kappa: KAPPA,
swapFeesPpm: Object.values(TEST_TOKENS).map(t => t.feePpm),
- flashFeePpm: FLASH_FEE_PPM, // 0.0005%
- stable: false,
+ flashFeePpm: FLASH_FEE_PPM,
+ stable: poolConfig.stable,
initialLpAmount: ethers.utils.parseUnits(INPUT_USD_AMOUNT.toString(), 18)
};
// ============================================================================
-// LOAD ABIs AND CONFIG
+// LOAD ABIs AND CONTRACT ADDRESSES
// ============================================================================
const chainInfoData = JSON.parse(await readFile(new URL('../src/contracts/liqp-deployments.json', import.meta.url), 'utf-8'));
@@ -165,6 +105,7 @@ const PARTY_PLANNER_ADDRESS = chainInfoData[currentConfig.chainId].v1.PartyPlann
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": "allowance", "stateMutability": "view", "inputs": [{ "name": "owner", "type": "address" }, { "name": "spender", "type": "address" }], "outputs": [{ "name": "", "type": "uint256" }] },
{ "type": "function", "name": "approve", "stateMutability": "nonpayable", "inputs": [{ "name": "spender", "type": "address" }, { "name": "amount", "type": "uint256" }], "outputs": [{ "name": "", "type": "bool" }] }
];
@@ -173,9 +114,6 @@ const ERC20ABI = [
// HELPER FUNCTIONS
// ============================================================================
-/**
- * Fetch real-time prices from CoinGecko API
- */
async function fetchCoinGeckoPrices() {
try {
const ids = Object.values(TEST_TOKENS).map(t => t.coingeckoId).join(',');
@@ -211,24 +149,17 @@ async function fetchCoinGeckoPrices() {
}
}
-/**
- * Calculate token amounts based on equal USD distribution
- */
function calculateTokenAmounts(prices, usdAmount) {
const tokenCount = Object.keys(TEST_TOKENS).length;
- const usdPerToken = usdAmount / tokenCount; // Equally distribute among all tokens
+ const usdPerToken = usdAmount / tokenCount;
const tokenAmounts = {};
console.log(`\n[~] Calculated token amounts for $${usdAmount} ($${usdPerToken.toFixed(2)} per token):`);
for (const [symbol, tokenInfo] of Object.entries(TEST_TOKENS)) {
- // Calculate raw amount
const rawAmount = usdPerToken / prices[symbol];
-
- // Convert to BigNumber with proper decimals
const amountBN = ethers.utils.parseUnits(rawAmount.toFixed(tokenInfo.decimals), tokenInfo.decimals);
-
tokenAmounts[symbol] = amountBN;
console.log(` ${symbol.padEnd(6)}: ${rawAmount.toFixed(tokenInfo.decimals)} (${amountBN.toString()} wei)`);
}
@@ -236,9 +167,6 @@ function calculateTokenAmounts(prices, usdAmount) {
return tokenAmounts;
}
-/**
- * Check token balances
- */
async function checkBalances(provider, wallet, tokenAmounts, receiverAddress) {
console.log(`\n[~] Checking token balances for receiver wallet: ${receiverAddress}`);
@@ -272,29 +200,32 @@ async function checkBalances(provider, wallet, tokenAmounts, receiverAddress) {
return balances;
}
-/**
- * Approve tokens for the PartyPlanner contract
- */
-async function approveTokens(provider, tokenAmounts, receiverPrivateKey) {
+async function approveTokens(provider, tokenAmounts, signerPrivateKey) {
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}`);
+ const signerWallet = new ethers.Wallet(signerPrivateKey, provider);
+ console.log(`[~] Using wallet for approvals: ${signerWallet.address}`);
for (const [symbol, tokenInfo] of Object.entries(TEST_TOKENS)) {
- const tokenContract = new ethers.Contract(tokenInfo.address, ERC20ABI, receiverWallet);
+ const tokenContract = new ethers.Contract(tokenInfo.address, ERC20ABI, signerWallet);
- // 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} ${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 == 'USDT') {
+ const currentAllowance = await tokenContract.allowance(signerWallet.address, PARTY_PLANNER_ADDRESS);
+ const currentFormatted = ethers.utils.formatUnits(currentAllowance, tokenInfo.decimals);
+ const requiredFormatted = ethers.utils.formatUnits(approvalAmount, tokenInfo.decimals);
+
+ if (currentAllowance.gte(approvalAmount)) {
+ console.log(` [=] ${symbol} allowance already sufficient: ${currentFormatted} >= ${requiredFormatted} (skipping)`);
+ continue;
+ }
+
+ console.log(` [~] Approving ${symbol} ${tokenInfo.address} ${approvalAmount} (current: ${currentFormatted}, need: ${requiredFormatted})...`);
+
+ // USDT and some tokens require setting allowance to 0 before raising it
+ if (symbol == 'USDT' && !currentAllowance.isZero()) {
const resetTx = await tokenContract.approve(PARTY_PLANNER_ADDRESS, 0);
await resetTx.wait();
console.log(` [+] ${symbol} allowance reset to 0`);
@@ -312,29 +243,24 @@ async function approveTokens(provider, tokenAmounts, receiverPrivateKey) {
console.log(`[+] All tokens approved`);
}
-/**
- * Create a new pool using cast send
- */
async function createPool(wallet, tokenAmounts) {
console.log(`\n[~] Creating new pool...`);
- // Prepare parameters
const tokenAddresses = Object.values(TEST_TOKENS).map(t => t.address);
const initialDeposits = Object.keys(TEST_TOKENS).map(symbol => tokenAmounts[symbol].toString());
- // 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(` Kappa: ${poolConfig.kappa} (${KAPPA.toString()})`);
console.log(` Tokens: ${tokenAddresses.join(', ')}`);
console.log(` Swap Fees PPM: [${DEFAULT_POOL_PARAMS.swapFeesPpm.join(', ')}]`);
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
const castCommand = `cast send ${PARTY_PLANNER_ADDRESS} \
"newPool(string,string,address[],int128,uint256[],uint256,bool,address,address,uint256[],uint256,uint256)" \
"${DEFAULT_POOL_PARAMS.name}" \
@@ -356,10 +282,14 @@ async function createPool(wallet, tokenAmounts) {
console.log(`\n[~] Cast command:\n${castCommand}\n`);
try {
- // Execute cast command
const { execSync } = await import('child_process');
- const output = execSync(castCommand, { encoding: 'utf-8' });
-
+ const foundryBin = `${process.env.HOME}/.foundry/bin`;
+ const env = {
+ ...process.env,
+ PATH: `${foundryBin}:${process.env.PATH || ''}`,
+ };
+ const output = execSync(castCommand, { encoding: 'utf-8', env });
+
console.log(`[+] Pool created successfully!`);
console.log(output);
@@ -373,22 +303,21 @@ async function createPool(wallet, tokenAmounts) {
}
}
-/**
- * Print help message
- */
function printHelp() {
console.log(`
Usage: node create_pool_from_prices.js [OPTIONS]
Options:
- --amount USD amount to distribute (default: ${INPUT_USD_AMOUNT})
- --name Pool name (default: "${DEFAULT_POOL_PARAMS.name}")
- --symbol Pool symbol (default: "${DEFAULT_POOL_PARAMS.symbol}")
+ --config Pool config JSON file (default: pool-og.json)
+ --amount USD amount to distribute (overrides config, default: ${INPUT_USD_AMOUNT})
+ --name Pool name (overrides config)
+ --symbol Pool symbol (overrides config)
--help, -h Show this help message
Example:
node create_pool_from_prices.js
- node create_pool_from_prices.js --amount 200 --name "My Pool" --symbol "MP"
+ node create_pool_from_prices.js --config pool-test.json
+ node create_pool_from_prices.js --config pool-test.json --amount 200
`);
}
@@ -399,12 +328,9 @@ Example:
async function main() {
console.log(`${'='.repeat(70)}`);
console.log(`Create Pool from Real-Time Prices`);
- console.log(`Network: ${NETWORK}`);
+ console.log(`Network: ${NETWORK} Config: ${configFile}`);
console.log(`${'='.repeat(70)}\n`);
- // Parse command line arguments
- const args = process.argv.slice(2);
-
if (args.includes('--help') || args.includes('-h')) {
printHelp();
process.exit(0);
@@ -427,37 +353,27 @@ async function main() {
}
}
- // Update pool params with parsed values
DEFAULT_POOL_PARAMS.name = poolName;
DEFAULT_POOL_PARAMS.symbol = poolSymbol;
try {
- // Step 1: Fetch prices
const prices = await fetchCoinGeckoPrices();
-
- // Step 2: Calculate token amounts
const tokenAmounts = calculateTokenAmounts(prices, usdAmount);
- //
- // // Step 3: Connect to wallet
+
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 sender wallet: ${wallet.address}`);
console.log(`[+] Receiver wallet: ${RECEIVER_ADDRESS}`);
- //
- // Step 4: Check balances
+
await checkBalances(provider, wallet, tokenAmounts, RECEIVER_ADDRESS);
- // // Step 5: Approve tokens
- if (NETWORK === 'mockchain' && currentConfig.receiverPrivateKey) {
- // On mockchain, use receiver wallet for approvals
- await approveTokens(provider, tokenAmounts, currentConfig.receiverPrivateKey);
+ if (NETWORK === 'mockchain' && RECEIVER_PRIVATE_KEY) {
+ await approveTokens(provider, tokenAmounts, RECEIVER_PRIVATE_KEY);
} 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);
console.log(`\n${'='.repeat(70)}`);
@@ -472,8 +388,7 @@ async function main() {
}
}
-// Run the main function
main().catch(error => {
console.error('[!] Unexpected error:', error);
process.exit(1);
-});
\ No newline at end of file
+});
diff --git a/scripts/package-lock.json b/scripts/package-lock.json
new file mode 100644
index 0000000..96ad3a3
--- /dev/null
+++ b/scripts/package-lock.json
@@ -0,0 +1,894 @@
+{
+ "name": "liquidity-party-scripts",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "liquidity-party-scripts",
+ "version": "1.0.0",
+ "dependencies": {
+ "dotenv": "^17.2.3",
+ "ethers": "^5.7.2"
+ }
+ },
+ "node_modules/@ethersproject/abi": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.8.0.tgz",
+ "integrity": "sha512-b9YS/43ObplgyV6SlyQsG53/vkSal0MNA1fskSC4mbnCMi8R+NkcH8K9FPYNESf6jUefBUniE4SOKms0E/KK1Q==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/address": "^5.8.0",
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/constants": "^5.8.0",
+ "@ethersproject/hash": "^5.8.0",
+ "@ethersproject/keccak256": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "@ethersproject/strings": "^5.8.0"
+ }
+ },
+ "node_modules/@ethersproject/abstract-provider": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.8.0.tgz",
+ "integrity": "sha512-wC9SFcmh4UK0oKuLJQItoQdzS/qZ51EJegK6EmAWlh+OptpQ/npECOR3QqECd8iGHC0RJb4WKbVdSfif4ammrg==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/networks": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "@ethersproject/transactions": "^5.8.0",
+ "@ethersproject/web": "^5.8.0"
+ }
+ },
+ "node_modules/@ethersproject/abstract-signer": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.8.0.tgz",
+ "integrity": "sha512-N0XhZTswXcmIZQdYtUnd79VJzvEwXQw6PK0dTl9VoYrEBxxCPXqS0Eod7q5TNKRxe1/5WUMuR0u0nqTF/avdCA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/abstract-provider": "^5.8.0",
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0"
+ }
+ },
+ "node_modules/@ethersproject/address": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.8.0.tgz",
+ "integrity": "sha512-GhH/abcC46LJwshoN+uBNoKVFPxUuZm6dA257z0vZkKmU1+t8xTn8oK7B9qrj8W2rFRMch4gbJl6PmVxjxBEBA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/keccak256": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/rlp": "^5.8.0"
+ }
+ },
+ "node_modules/@ethersproject/base64": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.8.0.tgz",
+ "integrity": "sha512-lN0oIwfkYj9LbPx4xEkie6rAMJtySbpOAFXSDVQaBnAzYfB4X2Qr+FXJGxMoc3Bxp2Sm8OwvzMrywxyw0gLjIQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bytes": "^5.8.0"
+ }
+ },
+ "node_modules/@ethersproject/basex": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.8.0.tgz",
+ "integrity": "sha512-PIgTszMlDRmNwW9nhS6iqtVfdTAKosA7llYXNmGPw4YAI1PUyMv28988wAb41/gHF/WqGdoLv0erHaRcHRKW2Q==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0"
+ }
+ },
+ "node_modules/@ethersproject/bignumber": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.8.0.tgz",
+ "integrity": "sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "bn.js": "^5.2.1"
+ }
+ },
+ "node_modules/@ethersproject/bytes": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.8.0.tgz",
+ "integrity": "sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/logger": "^5.8.0"
+ }
+ },
+ "node_modules/@ethersproject/constants": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.8.0.tgz",
+ "integrity": "sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bignumber": "^5.8.0"
+ }
+ },
+ "node_modules/@ethersproject/contracts": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.8.0.tgz",
+ "integrity": "sha512-0eFjGz9GtuAi6MZwhb4uvUM216F38xiuR0yYCjKJpNfSEy4HUM8hvqqBj9Jmm0IUz8l0xKEhWwLIhPgxNY0yvQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/abi": "^5.8.0",
+ "@ethersproject/abstract-provider": "^5.8.0",
+ "@ethersproject/abstract-signer": "^5.8.0",
+ "@ethersproject/address": "^5.8.0",
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/constants": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "@ethersproject/transactions": "^5.8.0"
+ }
+ },
+ "node_modules/@ethersproject/hash": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.8.0.tgz",
+ "integrity": "sha512-ac/lBcTbEWW/VGJij0CNSw/wPcw9bSRgCB0AIBz8CvED/jfvDoV9hsIIiWfvWmFEi8RcXtlNwp2jv6ozWOsooA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/abstract-signer": "^5.8.0",
+ "@ethersproject/address": "^5.8.0",
+ "@ethersproject/base64": "^5.8.0",
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/keccak256": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "@ethersproject/strings": "^5.8.0"
+ }
+ },
+ "node_modules/@ethersproject/hdnode": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.8.0.tgz",
+ "integrity": "sha512-4bK1VF6E83/3/Im0ERnnUeWOY3P1BZml4ZD3wcH8Ys0/d1h1xaFt6Zc+Dh9zXf9TapGro0T4wvO71UTCp3/uoA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/abstract-signer": "^5.8.0",
+ "@ethersproject/basex": "^5.8.0",
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/pbkdf2": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "@ethersproject/sha2": "^5.8.0",
+ "@ethersproject/signing-key": "^5.8.0",
+ "@ethersproject/strings": "^5.8.0",
+ "@ethersproject/transactions": "^5.8.0",
+ "@ethersproject/wordlists": "^5.8.0"
+ }
+ },
+ "node_modules/@ethersproject/json-wallets": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.8.0.tgz",
+ "integrity": "sha512-HxblNck8FVUtNxS3VTEYJAcwiKYsBIF77W15HufqlBF9gGfhmYOJtYZp8fSDZtn9y5EaXTE87zDwzxRoTFk11w==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/abstract-signer": "^5.8.0",
+ "@ethersproject/address": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/hdnode": "^5.8.0",
+ "@ethersproject/keccak256": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/pbkdf2": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "@ethersproject/random": "^5.8.0",
+ "@ethersproject/strings": "^5.8.0",
+ "@ethersproject/transactions": "^5.8.0",
+ "aes-js": "3.0.0",
+ "scrypt-js": "3.0.1"
+ }
+ },
+ "node_modules/@ethersproject/keccak256": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.8.0.tgz",
+ "integrity": "sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bytes": "^5.8.0",
+ "js-sha3": "0.8.0"
+ }
+ },
+ "node_modules/@ethersproject/logger": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.8.0.tgz",
+ "integrity": "sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/@ethersproject/networks": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.8.0.tgz",
+ "integrity": "sha512-egPJh3aPVAzbHwq8DD7Po53J4OUSsA1MjQp8Vf/OZPav5rlmWUaFLiq8cvQiGK0Z5K6LYzm29+VA/p4RL1FzNg==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/logger": "^5.8.0"
+ }
+ },
+ "node_modules/@ethersproject/pbkdf2": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.8.0.tgz",
+ "integrity": "sha512-wuHiv97BrzCmfEaPbUFpMjlVg/IDkZThp9Ri88BpjRleg4iePJaj2SW8AIyE8cXn5V1tuAaMj6lzvsGJkGWskg==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/sha2": "^5.8.0"
+ }
+ },
+ "node_modules/@ethersproject/properties": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.8.0.tgz",
+ "integrity": "sha512-PYuiEoQ+FMaZZNGrStmN7+lWjlsoufGIHdww7454FIaGdbe/p5rnaCXTr5MtBYl3NkeoVhHZuyzChPeGeKIpQw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/logger": "^5.8.0"
+ }
+ },
+ "node_modules/@ethersproject/providers": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.8.0.tgz",
+ "integrity": "sha512-3Il3oTzEx3o6kzcg9ZzbE+oCZYyY+3Zh83sKkn4s1DZfTUjIegHnN2Cm0kbn9YFy45FDVcuCLLONhU7ny0SsCw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/abstract-provider": "^5.8.0",
+ "@ethersproject/abstract-signer": "^5.8.0",
+ "@ethersproject/address": "^5.8.0",
+ "@ethersproject/base64": "^5.8.0",
+ "@ethersproject/basex": "^5.8.0",
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/constants": "^5.8.0",
+ "@ethersproject/hash": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/networks": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "@ethersproject/random": "^5.8.0",
+ "@ethersproject/rlp": "^5.8.0",
+ "@ethersproject/sha2": "^5.8.0",
+ "@ethersproject/strings": "^5.8.0",
+ "@ethersproject/transactions": "^5.8.0",
+ "@ethersproject/web": "^5.8.0",
+ "bech32": "1.1.4",
+ "ws": "8.18.0"
+ }
+ },
+ "node_modules/@ethersproject/random": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.8.0.tgz",
+ "integrity": "sha512-E4I5TDl7SVqyg4/kkA/qTfuLWAQGXmSOgYyO01So8hLfwgKvYK5snIlzxJMk72IFdG/7oh8yuSqY2KX7MMwg+A==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0"
+ }
+ },
+ "node_modules/@ethersproject/rlp": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.8.0.tgz",
+ "integrity": "sha512-LqZgAznqDbiEunaUvykH2JAoXTT9NV0Atqk8rQN9nx9SEgThA/WMx5DnW8a9FOufo//6FZOCHZ+XiClzgbqV9Q==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0"
+ }
+ },
+ "node_modules/@ethersproject/sha2": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.8.0.tgz",
+ "integrity": "sha512-dDOUrXr9wF/YFltgTBYS0tKslPEKr6AekjqDW2dbn1L1xmjGR+9GiKu4ajxovnrDbwxAKdHjW8jNcwfz8PAz4A==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "hash.js": "1.1.7"
+ }
+ },
+ "node_modules/@ethersproject/signing-key": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.8.0.tgz",
+ "integrity": "sha512-LrPW2ZxoigFi6U6aVkFN/fa9Yx/+4AtIUe4/HACTvKJdhm0eeb107EVCIQcrLZkxaSIgc/eCrX8Q1GtbH+9n3w==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "bn.js": "^5.2.1",
+ "elliptic": "6.6.1",
+ "hash.js": "1.1.7"
+ }
+ },
+ "node_modules/@ethersproject/solidity": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.8.0.tgz",
+ "integrity": "sha512-4CxFeCgmIWamOHwYN9d+QWGxye9qQLilpgTU0XhYs1OahkclF+ewO+3V1U0mvpiuQxm5EHHmv8f7ClVII8EHsA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/keccak256": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/sha2": "^5.8.0",
+ "@ethersproject/strings": "^5.8.0"
+ }
+ },
+ "node_modules/@ethersproject/strings": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.8.0.tgz",
+ "integrity": "sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/constants": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0"
+ }
+ },
+ "node_modules/@ethersproject/transactions": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.8.0.tgz",
+ "integrity": "sha512-UglxSDjByHG0TuU17bDfCemZ3AnKO2vYrL5/2n2oXvKzvb7Cz+W9gOWXKARjp2URVwcWlQlPOEQyAviKwT4AHg==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/address": "^5.8.0",
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/constants": "^5.8.0",
+ "@ethersproject/keccak256": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "@ethersproject/rlp": "^5.8.0",
+ "@ethersproject/signing-key": "^5.8.0"
+ }
+ },
+ "node_modules/@ethersproject/units": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.8.0.tgz",
+ "integrity": "sha512-lxq0CAnc5kMGIiWW4Mr041VT8IhNM+Pn5T3haO74XZWFulk7wH1Gv64HqE96hT4a7iiNMdOCFEBgaxWuk8ETKQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/constants": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0"
+ }
+ },
+ "node_modules/@ethersproject/wallet": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.8.0.tgz",
+ "integrity": "sha512-G+jnzmgg6UxurVKRKvw27h0kvG75YKXZKdlLYmAHeF32TGUzHkOFd7Zn6QHOTYRFWnfjtSSFjBowKo7vfrXzPA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/abstract-provider": "^5.8.0",
+ "@ethersproject/abstract-signer": "^5.8.0",
+ "@ethersproject/address": "^5.8.0",
+ "@ethersproject/bignumber": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/hash": "^5.8.0",
+ "@ethersproject/hdnode": "^5.8.0",
+ "@ethersproject/json-wallets": "^5.8.0",
+ "@ethersproject/keccak256": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "@ethersproject/random": "^5.8.0",
+ "@ethersproject/signing-key": "^5.8.0",
+ "@ethersproject/transactions": "^5.8.0",
+ "@ethersproject/wordlists": "^5.8.0"
+ }
+ },
+ "node_modules/@ethersproject/web": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.8.0.tgz",
+ "integrity": "sha512-j7+Ksi/9KfGviws6Qtf9Q7KCqRhpwrYKQPs+JBA/rKVFF/yaWLHJEH3zfVP2plVu+eys0d2DlFmhoQJayFewcw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/base64": "^5.8.0",
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "@ethersproject/strings": "^5.8.0"
+ }
+ },
+ "node_modules/@ethersproject/wordlists": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.8.0.tgz",
+ "integrity": "sha512-2df9bbXicZws2Sb5S6ET493uJ0Z84Fjr3pC4tu/qlnZERibZCeUVuqdtt+7Tv9xxhUxHoIekIA7avrKUWHrezg==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/bytes": "^5.8.0",
+ "@ethersproject/hash": "^5.8.0",
+ "@ethersproject/logger": "^5.8.0",
+ "@ethersproject/properties": "^5.8.0",
+ "@ethersproject/strings": "^5.8.0"
+ }
+ },
+ "node_modules/aes-js": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz",
+ "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==",
+ "license": "MIT"
+ },
+ "node_modules/bech32": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz",
+ "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==",
+ "license": "MIT"
+ },
+ "node_modules/bn.js": {
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz",
+ "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==",
+ "license": "MIT"
+ },
+ "node_modules/brorand": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz",
+ "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==",
+ "license": "MIT"
+ },
+ "node_modules/dotenv": {
+ "version": "17.2.3",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz",
+ "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/elliptic": {
+ "version": "6.6.1",
+ "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz",
+ "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==",
+ "license": "MIT",
+ "dependencies": {
+ "bn.js": "^4.11.9",
+ "brorand": "^1.1.0",
+ "hash.js": "^1.0.0",
+ "hmac-drbg": "^1.0.1",
+ "inherits": "^2.0.4",
+ "minimalistic-assert": "^1.0.1",
+ "minimalistic-crypto-utils": "^1.0.1"
+ }
+ },
+ "node_modules/elliptic/node_modules/bn.js": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz",
+ "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==",
+ "license": "MIT"
+ },
+ "node_modules/ethers": {
+ "version": "5.8.0",
+ "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.8.0.tgz",
+ "integrity": "sha512-DUq+7fHrCg1aPDFCHx6UIPb3nmt2XMpM7Y/g2gLhsl3lIBqeAfOJIl1qEvRf2uq3BiKxmh6Fh5pfp2ieyek7Kg==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://gitcoin.co/grants/13/ethersjs-complete-simple-and-tiny-2"
+ },
+ {
+ "type": "individual",
+ "url": "https://www.buymeacoffee.com/ricmoo"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@ethersproject/abi": "5.8.0",
+ "@ethersproject/abstract-provider": "5.8.0",
+ "@ethersproject/abstract-signer": "5.8.0",
+ "@ethersproject/address": "5.8.0",
+ "@ethersproject/base64": "5.8.0",
+ "@ethersproject/basex": "5.8.0",
+ "@ethersproject/bignumber": "5.8.0",
+ "@ethersproject/bytes": "5.8.0",
+ "@ethersproject/constants": "5.8.0",
+ "@ethersproject/contracts": "5.8.0",
+ "@ethersproject/hash": "5.8.0",
+ "@ethersproject/hdnode": "5.8.0",
+ "@ethersproject/json-wallets": "5.8.0",
+ "@ethersproject/keccak256": "5.8.0",
+ "@ethersproject/logger": "5.8.0",
+ "@ethersproject/networks": "5.8.0",
+ "@ethersproject/pbkdf2": "5.8.0",
+ "@ethersproject/properties": "5.8.0",
+ "@ethersproject/providers": "5.8.0",
+ "@ethersproject/random": "5.8.0",
+ "@ethersproject/rlp": "5.8.0",
+ "@ethersproject/sha2": "5.8.0",
+ "@ethersproject/signing-key": "5.8.0",
+ "@ethersproject/solidity": "5.8.0",
+ "@ethersproject/strings": "5.8.0",
+ "@ethersproject/transactions": "5.8.0",
+ "@ethersproject/units": "5.8.0",
+ "@ethersproject/wallet": "5.8.0",
+ "@ethersproject/web": "5.8.0",
+ "@ethersproject/wordlists": "5.8.0"
+ }
+ },
+ "node_modules/hash.js": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz",
+ "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==",
+ "license": "MIT",
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "minimalistic-assert": "^1.0.1"
+ }
+ },
+ "node_modules/hmac-drbg": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
+ "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==",
+ "license": "MIT",
+ "dependencies": {
+ "hash.js": "^1.0.3",
+ "minimalistic-assert": "^1.0.0",
+ "minimalistic-crypto-utils": "^1.0.1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "license": "ISC"
+ },
+ "node_modules/js-sha3": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
+ "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==",
+ "license": "MIT"
+ },
+ "node_modules/minimalistic-assert": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
+ "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==",
+ "license": "ISC"
+ },
+ "node_modules/minimalistic-crypto-utils": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz",
+ "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==",
+ "license": "MIT"
+ },
+ "node_modules/scrypt-js": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz",
+ "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==",
+ "license": "MIT"
+ },
+ "node_modules/ws": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+ "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/scripts/package.json b/scripts/package.json
index c46b248..928c7bc 100644
--- a/scripts/package.json
+++ b/scripts/package.json
@@ -5,7 +5,8 @@
"type": "module",
"private": true,
"scripts": {
- "create-pool": "node create_pool_from_prices.js"
+ "create-pool": "node create_pool_from_prices.js",
+ "adjust-pool": "node adjust_pool_prices_and_stake.js"
},
"dependencies": {
"dotenv": "^17.2.3",
diff --git a/scripts/pool-og.json b/scripts/pool-og.json
new file mode 100644
index 0000000..d465205
--- /dev/null
+++ b/scripts/pool-og.json
@@ -0,0 +1,70 @@
+{
+ "name": "Original Genesis of Liquidity Party",
+ "symbol": "OG.LP",
+ "kappa": 0.01,
+ "flashFeePpm": 5,
+ "inputUsdAmount": 75,
+ "stable": false,
+ "tokens": {
+ "USDT": {
+ "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
+ "coingeckoId": "tether",
+ "decimals": 6,
+ "feePpm": 40
+ },
+ "USDC": {
+ "address": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
+ "coingeckoId": "usd-coin",
+ "decimals": 6,
+ "feePpm": 40
+ },
+ "WBTC": {
+ "address": "0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599",
+ "coingeckoId": "wrapped-bitcoin",
+ "decimals": 8,
+ "feePpm": 200
+ },
+ "WETH": {
+ "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
+ "coingeckoId": "weth",
+ "decimals": 18,
+ "feePpm": 250
+ },
+ "UNI": {
+ "address": "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984",
+ "coingeckoId": "uniswap",
+ "decimals": 18,
+ "feePpm": 950
+ },
+ "WSOL": {
+ "address": "0xD31a59c85aE9D8edEFeC411D448f90841571b89c",
+ "coingeckoId": "solana",
+ "decimals": 9,
+ "feePpm": 950
+ },
+ "TRX": {
+ "address": "0x50327c6c5a14DCaDE707ABad2E27eB517df87AB5",
+ "coingeckoId": "tron",
+ "decimals": 6,
+ "feePpm": 950
+ },
+ "AAVE": {
+ "address": "0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9",
+ "coingeckoId": "aave",
+ "decimals": 18,
+ "feePpm": 1250
+ },
+ "PEPE": {
+ "address": "0x6982508145454Ce325dDbE47a25d4ec3d2311933",
+ "coingeckoId": "pepe",
+ "decimals": 18,
+ "feePpm": 1850
+ },
+ "SHIB": {
+ "address": "0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE",
+ "coingeckoId": "shiba-inu",
+ "decimals": 18,
+ "feePpm": 1850
+ }
+ }
+}
diff --git a/scripts/pool-test.json b/scripts/pool-test.json
new file mode 100644
index 0000000..d420739
--- /dev/null
+++ b/scripts/pool-test.json
@@ -0,0 +1,28 @@
+{
+ "name": "Test Pool",
+ "symbol": "TEST.LP",
+ "kappa": 0.1,
+ "flashFeePpm": 5,
+ "inputUsdAmount": 3,
+ "stable": false,
+ "tokens": {
+ "USDT": {
+ "address": "0xdAC17F958D2ee523a2206206994597C13D831ec7",
+ "coingeckoId": "tether",
+ "decimals": 6,
+ "feePpm": 40
+ },
+ "WETH": {
+ "address": "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
+ "coingeckoId": "weth",
+ "decimals": 18,
+ "feePpm": 250
+ },
+ "AAVE": {
+ "address": "0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9",
+ "coingeckoId": "aave",
+ "decimals": 18,
+ "feePpm": 1250
+ }
+ }
+}
diff --git a/src/components/stake-form.tsx b/src/components/stake-form.tsx
index 258c9b8..b5a50ff 100644
--- a/src/components/stake-form.tsx
+++ b/src/components/stake-form.tsx
@@ -426,7 +426,7 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
{pool.symbol}
{pool.name}
- {(pool.price || pool.tvl) && (
+ {(pool.price || pool.tvl || pool.apy) && (
{pool.price && (
{pool.price}
@@ -434,6 +434,9 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
{pool.tvl && (
TVL: {pool.tvl}
)}
+ {pool.apy && (
+ APY: {pool.apy}
+ )}
)}
@@ -502,7 +505,7 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
{pool.name}
- {!pool.isKilled && (pool.price || pool.tvl) && (
+ {!pool.isKilled && (pool.price || pool.tvl || pool.apy) && (
{pool.price && (
{pool.price}
@@ -510,6 +513,9 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
{pool.tvl && (
TVL: {pool.tvl}
)}
+ {pool.apy && (
+ APY: {pool.apy}
+ )}
)}
diff --git a/src/contracts/IPartyInfoABI.ts b/src/contracts/IPartyInfoABI.ts
index 4b30395..423da97 100644
--- a/src/contracts/IPartyInfoABI.ts
+++ b/src/contracts/IPartyInfoABI.ts
@@ -170,12 +170,12 @@ const IPartyInfoABI = [
"internalType": "contract IPartyPool"
},
{
- "name": "baseTokenIndex",
+ "name": "inputTokenIndex",
"type": "uint256",
"internalType": "uint256"
},
{
- "name": "quoteTokenIndex",
+ "name": "outputTokenIndex",
"type": "uint256",
"internalType": "uint256"
}
@@ -189,6 +189,94 @@ const IPartyInfoABI = [
],
"stateMutability": "view"
},
+ {
+ "type": "function",
+ "name": "swapAmounts",
+ "inputs": [
+ {
+ "name": "pool",
+ "type": "address",
+ "internalType": "contract IPartyPool"
+ },
+ {
+ "name": "inputTokenIndex",
+ "type": "uint256",
+ "internalType": "uint256"
+ },
+ {
+ "name": "outputTokenIndex",
+ "type": "uint256",
+ "internalType": "uint256"
+ },
+ {
+ "name": "maxAmountIn",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "amountIn",
+ "type": "uint256",
+ "internalType": "uint256"
+ },
+ {
+ "name": "amountOut",
+ "type": "uint256",
+ "internalType": "uint256"
+ },
+ {
+ "name": "inFee",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
+ {
+ "type": "function",
+ "name": "swapAmountsForExactPrice",
+ "inputs": [
+ {
+ "name": "pool",
+ "type": "address",
+ "internalType": "contract IPartyPool"
+ },
+ {
+ "name": "inputTokenIndex",
+ "type": "uint256",
+ "internalType": "uint256"
+ },
+ {
+ "name": "outputTokenIndex",
+ "type": "uint256",
+ "internalType": "uint256"
+ },
+ {
+ "name": "minPrice",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "outputs": [
+ {
+ "name": "amountIn",
+ "type": "uint256",
+ "internalType": "uint256"
+ },
+ {
+ "name": "amountOut",
+ "type": "uint256",
+ "internalType": "uint256"
+ },
+ {
+ "name": "inFee",
+ "type": "uint256",
+ "internalType": "uint256"
+ }
+ ],
+ "stateMutability": "view"
+ },
{
"type": "function",
"name": "swapMintAmounts",
@@ -228,50 +316,6 @@ const IPartyInfoABI = [
],
"stateMutability": "view"
},
- {
- "type": "function",
- "name": "swapToLimitAmounts",
- "inputs": [
- {
- "name": "pool",
- "type": "address",
- "internalType": "contract IPartyPool"
- },
- {
- "name": "inputTokenIndex",
- "type": "uint256",
- "internalType": "uint256"
- },
- {
- "name": "outputTokenIndex",
- "type": "uint256",
- "internalType": "uint256"
- },
- {
- "name": "limitPrice",
- "type": "int128",
- "internalType": "int128"
- }
- ],
- "outputs": [
- {
- "name": "amountIn",
- "type": "uint256",
- "internalType": "uint256"
- },
- {
- "name": "amountOut",
- "type": "uint256",
- "internalType": "uint256"
- },
- {
- "name": "inFee",
- "type": "uint256",
- "internalType": "uint256"
- }
- ],
- "stateMutability": "view"
- },
{
"type": "function",
"name": "working",
diff --git a/src/contracts/IPartyPlannerABI.ts b/src/contracts/IPartyPlannerABI.ts
index c151b5d..0b7401b 100644
--- a/src/contracts/IPartyPlannerABI.ts
+++ b/src/contracts/IPartyPlannerABI.ts
@@ -1,6 +1,13 @@
/* GENERATED FILE: DO NOT EDIT! */
const IPartyPlannerABI = [
+ {
+ "type": "function",
+ "name": "acceptOwnership",
+ "inputs": [],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
{
"type": "function",
"name": "getAllPools",
@@ -97,19 +104,6 @@ const IPartyPlannerABI = [
],
"stateMutability": "view"
},
- {
- "type": "function",
- "name": "mintImpl",
- "inputs": [],
- "outputs": [
- {
- "name": "",
- "type": "address",
- "internalType": "contract PartyPoolMintImpl"
- }
- ],
- "stateMutability": "view"
- },
{
"type": "function",
"name": "newPool",
@@ -365,6 +359,19 @@ const IPartyPlannerABI = [
],
"stateMutability": "view"
},
+ {
+ "type": "function",
+ "name": "pendingOwner",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "address",
+ "internalType": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
{
"type": "function",
"name": "poolCount",
@@ -399,28 +406,32 @@ const IPartyPlannerABI = [
},
{
"type": "function",
- "name": "renounceOwnership",
- "inputs": [],
- "outputs": [],
- "stateMutability": "nonpayable"
- },
- {
- "type": "function",
- "name": "swapImpl",
+ "name": "tokenCount",
"inputs": [],
"outputs": [
{
"name": "",
- "type": "address",
- "internalType": "contract PartyPoolSwapImpl"
+ "type": "uint256",
+ "internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
- "name": "tokenCount",
- "inputs": [],
+ "name": "tokenIndex",
+ "inputs": [
+ {
+ "name": "pool",
+ "type": "address",
+ "internalType": "contract IPartyPool"
+ },
+ {
+ "name": "token",
+ "type": "address",
+ "internalType": "contract IERC20"
+ }
+ ],
"outputs": [
{
"name": "",
@@ -443,6 +454,25 @@ const IPartyPlannerABI = [
"outputs": [],
"stateMutability": "nonpayable"
},
+ {
+ "type": "event",
+ "name": "OwnershipTransferStarted",
+ "inputs": [
+ {
+ "name": "previousOwner",
+ "type": "address",
+ "indexed": true,
+ "internalType": "address"
+ },
+ {
+ "name": "newOwner",
+ "type": "address",
+ "indexed": true,
+ "internalType": "address"
+ }
+ ],
+ "anonymous": false
+ },
{
"type": "event",
"name": "OwnershipTransferred",
diff --git a/src/contracts/IPartyPoolABI.ts b/src/contracts/IPartyPoolABI.ts
index d7c9ac5..1c8f0b0 100644
--- a/src/contracts/IPartyPoolABI.ts
+++ b/src/contracts/IPartyPoolABI.ts
@@ -26,6 +26,13 @@ const IPartyPoolABI = [
],
"stateMutability": "view"
},
+ {
+ "type": "function",
+ "name": "acceptOwnership",
+ "inputs": [],
+ "outputs": [],
+ "stateMutability": "nonpayable"
+ },
{
"type": "function",
"name": "allProtocolFeesOwed",
@@ -100,25 +107,6 @@ const IPartyPoolABI = [
],
"stateMutability": "nonpayable"
},
- {
- "type": "function",
- "name": "balance",
- "inputs": [
- {
- "name": "index",
- "type": "uint256",
- "internalType": "uint256"
- }
- ],
- "outputs": [
- {
- "name": "",
- "type": "uint256",
- "internalType": "uint256"
- }
- ],
- "stateMutability": "view"
- },
{
"type": "function",
"name": "balanceOf",
@@ -214,6 +202,11 @@ const IPartyPoolABI = [
"type": "uint256",
"internalType": "uint256"
},
+ {
+ "name": "minAmountOut",
+ "type": "uint256",
+ "internalType": "uint256"
+ },
{
"name": "deadline",
"type": "uint256",
@@ -272,30 +265,6 @@ const IPartyPoolABI = [
],
"stateMutability": "view"
},
- {
- "type": "function",
- "name": "fee",
- "inputs": [
- {
- "name": "i",
- "type": "uint256",
- "internalType": "uint256"
- },
- {
- "name": "j",
- "type": "uint256",
- "internalType": "uint256"
- }
- ],
- "outputs": [
- {
- "name": "",
- "type": "uint256",
- "internalType": "uint256"
- }
- ],
- "stateMutability": "view"
- },
{
"type": "function",
"name": "fees",
@@ -380,19 +349,6 @@ const IPartyPoolABI = [
],
"stateMutability": "payable"
},
- {
- "type": "function",
- "name": "kappa",
- "inputs": [],
- "outputs": [
- {
- "name": "",
- "type": "int128",
- "internalType": "int128"
- }
- ],
- "stateMutability": "view"
- },
{
"type": "function",
"name": "kill",
@@ -422,6 +378,11 @@ const IPartyPoolABI = [
"type": "address",
"internalType": "address"
},
+ {
+ "name": "fundingSelector",
+ "type": "bytes4",
+ "internalType": "bytes4"
+ },
{
"name": "receiver",
"type": "address",
@@ -436,6 +397,11 @@ const IPartyPoolABI = [
"name": "deadline",
"type": "uint256",
"internalType": "uint256"
+ },
+ {
+ "name": "cbData",
+ "type": "bytes",
+ "internalType": "bytes"
}
],
"outputs": [
@@ -447,19 +413,6 @@ const IPartyPoolABI = [
],
"stateMutability": "payable"
},
- {
- "type": "function",
- "name": "mintImpl",
- "inputs": [],
- "outputs": [
- {
- "name": "",
- "type": "address",
- "internalType": "address"
- }
- ],
- "stateMutability": "view"
- },
{
"type": "function",
"name": "name",
@@ -499,6 +452,19 @@ const IPartyPoolABI = [
],
"stateMutability": "view"
},
+ {
+ "type": "function",
+ "name": "pendingOwner",
+ "inputs": [],
+ "outputs": [
+ {
+ "name": "",
+ "type": "address",
+ "internalType": "address"
+ }
+ ],
+ "stateMutability": "view"
+ },
{
"type": "function",
"name": "protocolFeeAddress",
@@ -525,13 +491,6 @@ const IPartyPoolABI = [
],
"stateMutability": "view"
},
- {
- "type": "function",
- "name": "renounceOwnership",
- "inputs": [],
- "outputs": [],
- "stateMutability": "nonpayable"
- },
{
"type": "function",
"name": "swap",
@@ -567,9 +526,9 @@ const IPartyPoolABI = [
"internalType": "uint256"
},
{
- "name": "limitPrice",
- "type": "int128",
- "internalType": "int128"
+ "name": "minAmountOut",
+ "type": "uint256",
+ "internalType": "uint256"
},
{
"name": "deadline",
@@ -606,115 +565,9 @@ const IPartyPoolABI = [
],
"stateMutability": "payable"
},
- {
- "type": "function",
- "name": "swapAmounts",
- "inputs": [
- {
- "name": "inputTokenIndex",
- "type": "uint256",
- "internalType": "uint256"
- },
- {
- "name": "outputTokenIndex",
- "type": "uint256",
- "internalType": "uint256"
- },
- {
- "name": "maxAmountIn",
- "type": "uint256",
- "internalType": "uint256"
- },
- {
- "name": "limitPrice",
- "type": "int128",
- "internalType": "int128"
- }
- ],
- "outputs": [
- {
- "name": "amountIn",
- "type": "uint256",
- "internalType": "uint256"
- },
- {
- "name": "amountOut",
- "type": "uint256",
- "internalType": "uint256"
- },
- {
- "name": "inFee",
- "type": "uint256",
- "internalType": "uint256"
- }
- ],
- "stateMutability": "view"
- },
- {
- "type": "function",
- "name": "swapImpl",
- "inputs": [],
- "outputs": [
- {
- "name": "",
- "type": "address",
- "internalType": "address"
- }
- ],
- "stateMutability": "view"
- },
{
"type": "function",
"name": "swapMint",
- "inputs": [
- {
- "name": "payer",
- "type": "address",
- "internalType": "address"
- },
- {
- "name": "receiver",
- "type": "address",
- "internalType": "address"
- },
- {
- "name": "inputTokenIndex",
- "type": "uint256",
- "internalType": "uint256"
- },
- {
- "name": "maxAmountIn",
- "type": "uint256",
- "internalType": "uint256"
- },
- {
- "name": "deadline",
- "type": "uint256",
- "internalType": "uint256"
- }
- ],
- "outputs": [
- {
- "name": "amountInUsed",
- "type": "uint256",
- "internalType": "uint256"
- },
- {
- "name": "lpMinted",
- "type": "uint256",
- "internalType": "uint256"
- },
- {
- "name": "inFee",
- "type": "uint256",
- "internalType": "uint256"
- }
- ],
- "stateMutability": "payable"
- },
- {
- "type": "function",
- "name": "swapToLimit",
"inputs": [
{
"name": "payer",
@@ -737,25 +590,20 @@ const IPartyPoolABI = [
"internalType": "uint256"
},
{
- "name": "outputTokenIndex",
+ "name": "maxAmountIn",
"type": "uint256",
"internalType": "uint256"
},
{
- "name": "limitPrice",
- "type": "int128",
- "internalType": "int128"
+ "name": "minLpOut",
+ "type": "uint256",
+ "internalType": "uint256"
},
{
"name": "deadline",
"type": "uint256",
"internalType": "uint256"
},
- {
- "name": "unwrap",
- "type": "bool",
- "internalType": "bool"
- },
{
"name": "cbData",
"type": "bytes",
@@ -769,7 +617,7 @@ const IPartyPoolABI = [
"internalType": "uint256"
},
{
- "name": "amountOut",
+ "name": "lpMinted",
"type": "uint256",
"internalType": "uint256"
},
@@ -1090,6 +938,25 @@ const IPartyPoolABI = [
],
"anonymous": false
},
+ {
+ "type": "event",
+ "name": "OwnershipTransferStarted",
+ "inputs": [
+ {
+ "name": "previousOwner",
+ "type": "address",
+ "indexed": true,
+ "internalType": "address"
+ },
+ {
+ "name": "newOwner",
+ "type": "address",
+ "indexed": true,
+ "internalType": "address"
+ }
+ ],
+ "anonymous": false
+ },
{
"type": "event",
"name": "OwnershipTransferred",
diff --git a/src/hooks/usePartyPlanner.ts b/src/hooks/usePartyPlanner.ts
index 8ff79ff..5338147 100644
--- a/src/hooks/usePartyPlanner.ts
+++ b/src/hooks/usePartyPlanner.ts
@@ -8,6 +8,7 @@ import IPartyPoolABI from '@/contracts/IPartyPoolABI';
import IPartyPoolViewerABI from '@/contracts/IPartyPoolViewerABI';
import IPartyInfoABI from '@/contracts/IPartyInfoABI';
import { ERC20ABI } from '@/contracts/ERC20ABI';
+import { fetchPoolMetrics } from '@/lib/poolMetricsCache';
// Helper function to format large numbers with K, M, B suffixes
function formatTVL(value: number): string {
@@ -444,6 +445,7 @@ export interface PoolDetails {
tokens: readonly `0x${string}`[];
price?: string; // Formatted price string
tvl?: string; // Formatted TVL string (e.g., "$1.2M")
+ apy?: string; // Formatted APY string (e.g., "3.25%")
isKilled: boolean; // Whether the pool has been killed
}
@@ -495,6 +497,9 @@ export function useGetAllPools(offset: number = 0, limit: number = 100) {
setPools(result);
+ // Fetch Substreams pool metrics (cached for 1 hour)
+ const poolMetricsData = await fetchPoolMetrics();
+
// Fetch details for each pool and check if it's working
const details: PoolDetails[] = [];
for (const poolAddress of result) {
@@ -526,6 +531,7 @@ export function useGetAllPools(offset: number = 0, limit: number = 100) {
// Fetch pool price and TVL (only for working pools)
let priceStr: string | undefined;
let tvlStr: string | undefined;
+ let apyStr: string | undefined;
if (isWorking) {
// Fetch token decimals and balance first (needed for both price and TVL)
@@ -567,10 +573,21 @@ export function useGetAllPools(offset: number = 0, limit: number = 100) {
priceStr = `$${finalPrice.toFixed(4)}`;
}
- // Calculate TVL (approximate by getting first token balance and multiplying by number of tokens)
- const tokenBalance = Number(balance) / Math.pow(10, decimals);
- const approximateTVL = tokenBalance * tokens.length;
- tvlStr = formatTVL(approximateTVL);
+ // Use Substreams TVL + APY if available; fall back to on-chain estimate
+ const metricKey = poolAddress.toLowerCase().replace('0x', '');
+ const metric = poolMetricsData[metricKey];
+ if (metric) {
+ const tvlAdjusted = Number(BigInt(metric.quoteTvl)) / Math.pow(10, decimals);
+ tvlStr = formatTVL(tvlAdjusted);
+ if (metric.quoteApyBps > 0) {
+ apyStr = `${(metric.quoteApyBps / 100).toFixed(2)}%`;
+ }
+ } else {
+ // Fall back to on-chain estimate
+ const tokenBalance = Number(balance) / Math.pow(10, decimals);
+ const approximateTVL = tokenBalance * tokens.length;
+ tvlStr = formatTVL(approximateTVL);
+ }
}
} catch (err) {
console.error(`Error fetching pool price/TVL for ${poolAddress}:`, err);
@@ -587,6 +604,7 @@ export function useGetAllPools(offset: number = 0, limit: number = 100) {
tokens: tokens as readonly `0x${string}`[],
price: priceStr,
tvl: tvlStr,
+ apy: apyStr,
isKilled: !isWorking,
});
} catch (err) {
diff --git a/src/lib/poolMetricsCache.ts b/src/lib/poolMetricsCache.ts
new file mode 100644
index 0000000..5287009
--- /dev/null
+++ b/src/lib/poolMetricsCache.ts
@@ -0,0 +1,123 @@
+// Client-side cache for Substreams pool metrics (TVL + APY).
+// Fetches from the configured GraphQL subgraph endpoint and caches results
+// in memory and localStorage for up to 1 hour to minimize subgraph query costs.
+
+export interface PoolMetricEntry {
+ quoteTvl: string; // Raw bigint string — needs division by 10^token0Decimals
+ quoteApyBps: number; // Compound APY in basis points (divide by 100 for %)
+}
+
+interface CacheData {
+ timestamp: number;
+ metrics: Record; // keyed by lowercase pool address (no 0x prefix)
+}
+
+const CACHE_KEY = 'lp_pool_metrics_v1';
+const CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
+
+let memCache: CacheData | null = null;
+
+function loadFromStorage(): CacheData | null {
+ if (typeof localStorage === 'undefined') return null;
+ try {
+ const raw = localStorage.getItem(CACHE_KEY);
+ if (!raw) return null;
+ return JSON.parse(raw) as CacheData;
+ } catch {
+ return null;
+ }
+}
+
+function saveToStorage(data: CacheData): void {
+ if (typeof localStorage === 'undefined') return;
+ try {
+ localStorage.setItem(CACHE_KEY, JSON.stringify(data));
+ } catch {
+ // Ignore storage quota errors
+ }
+}
+
+function isFresh(data: CacheData): boolean {
+ return Date.now() - data.timestamp < CACHE_TTL_MS;
+}
+
+// In-flight promise to avoid duplicate simultaneous fetches
+let inFlight: Promise> | null = null;
+
+export async function fetchPoolMetrics(): Promise> {
+ // Memory cache hit
+ if (memCache && isFresh(memCache)) {
+ return memCache.metrics;
+ }
+
+ // localStorage cache hit (skip if empty — means a previous fetch returned nothing)
+ const stored = loadFromStorage();
+ if (stored && isFresh(stored) && Object.keys(stored.metrics).length > 0) {
+ memCache = stored;
+ return stored.metrics;
+ }
+
+ // Deduplicate concurrent fetches
+ if (inFlight) return inFlight;
+
+ inFlight = (async () => {
+ const endpoint = process.env.NEXT_PUBLIC_SUBSTREAMS_ENDPOINT;
+ if (!endpoint) return {};
+
+ try {
+ const response = await fetch(endpoint, {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ query: `{
+ poolMetrics(first: 100) {
+ id
+ quoteTvl
+ quoteApyBps
+ }
+ }`,
+ }),
+ });
+
+ if (!response.ok) {
+ console.warn('[poolMetrics] fetch failed:', response.status, response.statusText, await response.text().catch(() => ''));
+ return {};
+ }
+
+ const json = await response.json() as {
+ data?: { poolMetrics?: Array<{ id: string; quoteTvl: string; quoteApyBps: string | number }> };
+ errors?: unknown;
+ };
+
+ if (json.errors) {
+ console.warn('[poolMetrics] GraphQL errors:', json.errors);
+ }
+
+ const entries = json?.data?.poolMetrics ?? [];
+ console.log(`[poolMetrics] received ${entries.length} pool entries`);
+
+ const metrics: Record = {};
+ for (const entry of entries) {
+ metrics[entry.id.toLowerCase()] = {
+ quoteTvl: entry.quoteTvl,
+ quoteApyBps: Number(entry.quoteApyBps),
+ };
+ }
+
+ // Only cache non-empty results so a failed/empty response doesn't block retries
+ if (Object.keys(metrics).length > 0) {
+ const cache: CacheData = { timestamp: Date.now(), metrics };
+ memCache = cache;
+ saveToStorage(cache);
+ }
+ return metrics;
+ } catch (err) {
+ console.warn('[poolMetrics] fetch error:', err);
+ return {};
+ } finally {
+ inFlight = null;
+ }
+ })();
+
+ return inFlight;
+}