create_pool refactor; test pool deployed; etc

This commit is contained in:
2026-05-10 17:57:45 -04:00
parent d31f99d330
commit e1d2acea0d
13 changed files with 2359 additions and 444 deletions

View File

@@ -26,6 +26,14 @@ else
echo Building echo Building
fi 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 npm run build || exit 1
if [ "$BUILD" != "1" ]; then if [ "$BUILD" != "1" ]; then

View File

@@ -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 <address> (--private-key <key> | --trezor <address>) [OPTIONS]
Signer (exactly one required):
--private-key <key> Sign all transactions with this private key
--trezor <address> Sign all transactions via Trezor hardware wallet at given address
Options:
--pool <address> Target LiqP pool address (required)
--quote <symbol> Quote token symbol (default: auto-detect USDT/USDC)
--iterations <n> Max rebalancing iterations, best-first (default: 10)
--stake <usd> 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 <address> is required.');
process.exit(1);
}
if (!opts.privateKey && !opts.trezorAddress) {
console.error('[!] Signer required: provide --private-key <key> or --trezor <address>.');
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 <usd>.');
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 <symbol>');
}
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);
});

View File

@@ -8,117 +8,60 @@ import { ethers } from 'ethers';
import { readFile } from 'fs/promises'; import { readFile } from 'fs/promises';
import { config } from 'dotenv'; import { config } from 'dotenv';
// Load environment variables from .env-secret // Load environment variables from .env
config({ path: new URL('../.env-secret', import.meta.url).pathname }); config({ path: new URL('../.env', import.meta.url).pathname });
// ============================================================================ // ============================================================================
// CONFIGURATION // LOAD POOL CONFIG
// ============================================================================ // ============================================================================
// Default pool parameters const args = process.argv.slice(2);
const POOL_NAME = 'Original Genesis of Liquidity Party';
const POOL_SYMBOL = 'OG.LP'; function getArg(flag) {
const KAPPA = ethers.BigNumber.from('184467440737095520'); const idx = args.indexOf(flag);
const FLASH_FEE_PPM = 5; return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;
const INPUT_USD_AMOUNT = 75; // Size of initial mint in USD }
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 = { const NETWORK_CONFIG = {
mockchain: { mockchain: {
rpcUrl: 'http://localhost:8545', rpcUrl: process.env.MOCKCHAIN_RPC_URL || 'http://localhost:8545',
privateKey: '0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a', // Dev wallet #4 (sender)
receiverPrivateKey: '0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356', // Receiver wallet
receiverAddress: '0x14dC79964da2C08b23698B3D3cc7Ca32193d9955', // Receiver public key
chainId: '31337', 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: { mainnet: {
rpcUrl: process.env.MAINNET_RPC_URL, rpcUrl: process.env.MAINNET_RPC_URL,
privateKey: process.env.PRIVATE_KEY,
receiverAddress: '0xd3b310bd32d782f89eea49cb79656bcaccde7213', // Same as payer for mainnet
chainId: '1', 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'; const NETWORK = process.env.NETWORK || 'mainnet';
// Get current network config
const currentConfig = NETWORK_CONFIG[NETWORK]; const currentConfig = NETWORK_CONFIG[NETWORK];
if (!currentConfig) { if (!currentConfig) {
console.error(`[!] Invalid NETWORK: ${NETWORK}. Must be 'mockchain' or 'mainnet'`); console.error(`[!] Invalid NETWORK: ${NETWORK}. Must be 'mockchain' or 'mainnet'`);
@@ -126,10 +69,10 @@ if (!currentConfig) {
} }
const RPC_URL = currentConfig.rpcUrl; const RPC_URL = currentConfig.rpcUrl;
const PRIVATE_KEY = currentConfig.privateKey; const PRIVATE_KEY = process.env.PRIVATE_KEY;
const RECEIVER_ADDRESS = currentConfig.receiverAddress || process.env.RECEIVER_ADDRESS; 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)) { if (NETWORK === 'mainnet' && (!RPC_URL || !PRIVATE_KEY)) {
console.error('[!] Missing required environment variables for mainnet'); console.error('[!] Missing required environment variables for mainnet');
console.error(' Required: MAINNET_RPC_URL, PRIVATE_KEY'); console.error(' Required: MAINNET_RPC_URL, PRIVATE_KEY');
@@ -137,26 +80,23 @@ if (NETWORK === 'mainnet' && (!RPC_URL || !PRIVATE_KEY)) {
} }
if (!RECEIVER_ADDRESS) { if (!RECEIVER_ADDRESS) {
console.error('[!] Missing RECEIVER_ADDRESS in .env-secret file'); console.error('[!] Missing RECEIVER_ADDRESS in .env file');
process.exit(1); process.exit(1);
} }
// Use network-specific tokens
const TEST_TOKENS = currentConfig.tokens;
const DEFAULT_POOL_PARAMS = { const DEFAULT_POOL_PARAMS = {
name: POOL_NAME, name: poolConfig.name,
symbol: POOL_SYMBOL, symbol: poolConfig.symbol,
kappa: KAPPA, //0.01 * 2^64 kappa: KAPPA,
swapFeesPpm: Object.values(TEST_TOKENS).map(t => t.feePpm), swapFeesPpm: Object.values(TEST_TOKENS).map(t => t.feePpm),
flashFeePpm: FLASH_FEE_PPM, // 0.0005% flashFeePpm: FLASH_FEE_PPM,
stable: false, stable: poolConfig.stable,
initialLpAmount: ethers.utils.parseUnits(INPUT_USD_AMOUNT.toString(), 18) 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')); 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 = [ const ERC20ABI = [
{ "type": "function", "name": "balanceOf", "stateMutability": "view", "inputs": [{ "name": "account", "type": "address" }], "outputs": [{ "name": "", "type": "uint256" }] }, { "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": "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" }] } { "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 // HELPER FUNCTIONS
// ============================================================================ // ============================================================================
/**
* Fetch real-time prices from CoinGecko API
*/
async function fetchCoinGeckoPrices() { async function fetchCoinGeckoPrices() {
try { try {
const ids = Object.values(TEST_TOKENS).map(t => t.coingeckoId).join(','); 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) { function calculateTokenAmounts(prices, usdAmount) {
const tokenCount = Object.keys(TEST_TOKENS).length; const tokenCount = Object.keys(TEST_TOKENS).length;
const usdPerToken = usdAmount / tokenCount; // Equally distribute among all tokens const usdPerToken = usdAmount / tokenCount;
const tokenAmounts = {}; const tokenAmounts = {};
console.log(`\n[~] Calculated token amounts for $${usdAmount} ($${usdPerToken.toFixed(2)} per token):`); console.log(`\n[~] Calculated token amounts for $${usdAmount} ($${usdPerToken.toFixed(2)} per token):`);
for (const [symbol, tokenInfo] of Object.entries(TEST_TOKENS)) { for (const [symbol, tokenInfo] of Object.entries(TEST_TOKENS)) {
// Calculate raw amount
const rawAmount = usdPerToken / prices[symbol]; const rawAmount = usdPerToken / prices[symbol];
// Convert to BigNumber with proper decimals
const amountBN = ethers.utils.parseUnits(rawAmount.toFixed(tokenInfo.decimals), tokenInfo.decimals); const amountBN = ethers.utils.parseUnits(rawAmount.toFixed(tokenInfo.decimals), tokenInfo.decimals);
tokenAmounts[symbol] = amountBN; tokenAmounts[symbol] = amountBN;
console.log(` ${symbol.padEnd(6)}: ${rawAmount.toFixed(tokenInfo.decimals)} (${amountBN.toString()} wei)`); console.log(` ${symbol.padEnd(6)}: ${rawAmount.toFixed(tokenInfo.decimals)} (${amountBN.toString()} wei)`);
} }
@@ -236,9 +167,6 @@ function calculateTokenAmounts(prices, usdAmount) {
return tokenAmounts; return tokenAmounts;
} }
/**
* Check token balances
*/
async function checkBalances(provider, wallet, tokenAmounts, receiverAddress) { async function checkBalances(provider, wallet, tokenAmounts, receiverAddress) {
console.log(`\n[~] Checking token balances for receiver wallet: ${receiverAddress}`); console.log(`\n[~] Checking token balances for receiver wallet: ${receiverAddress}`);
@@ -272,29 +200,32 @@ async function checkBalances(provider, wallet, tokenAmounts, receiverAddress) {
return balances; return balances;
} }
/** async function approveTokens(provider, tokenAmounts, signerPrivateKey) {
* Approve tokens for the PartyPlanner contract
*/
async function approveTokens(provider, tokenAmounts, receiverPrivateKey) {
console.log(`\n[~] Approving tokens for PartyPlanner contract...`); console.log(`\n[~] Approving tokens for PartyPlanner contract...`);
// Connect with receiver wallet for approvals const signerWallet = new ethers.Wallet(signerPrivateKey, provider);
const receiverWallet = new ethers.Wallet(receiverPrivateKey, provider); console.log(`[~] Using wallet for approvals: ${signerWallet.address}`);
console.log(`[~] Using receiver wallet for approvals: ${receiverWallet.address}`);
for (const [symbol, tokenInfo] of Object.entries(TEST_TOKENS)) { 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 requiredAmount = tokenAmounts[symbol];
const approvalAmount = requiredAmount.mul(101).div(100); // 1% buffer const approvalAmount = requiredAmount.mul(101).div(100); // 1% buffer
console.log(` [~] Approving ${symbol} ${tokenInfo.address} ${approvalAmount} (1% buffer)...`);
try { try {
// USDT and some tokens require setting allowance to 0 before setting a new value const currentAllowance = await tokenContract.allowance(signerWallet.address, PARTY_PLANNER_ADDRESS);
// Skip for BNB as it has a broken approve function const currentFormatted = ethers.utils.formatUnits(currentAllowance, tokenInfo.decimals);
if (symbol == 'USDT') { 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); const resetTx = await tokenContract.approve(PARTY_PLANNER_ADDRESS, 0);
await resetTx.wait(); await resetTx.wait();
console.log(` [+] ${symbol} allowance reset to 0`); console.log(` [+] ${symbol} allowance reset to 0`);
@@ -312,29 +243,24 @@ async function approveTokens(provider, tokenAmounts, receiverPrivateKey) {
console.log(`[+] All tokens approved`); console.log(`[+] All tokens approved`);
} }
/**
* Create a new pool using cast send
*/
async function createPool(wallet, tokenAmounts) { async function createPool(wallet, tokenAmounts) {
console.log(`\n[~] Creating new pool...`); console.log(`\n[~] Creating new pool...`);
// Prepare parameters
const tokenAddresses = Object.values(TEST_TOKENS).map(t => t.address); const tokenAddresses = Object.values(TEST_TOKENS).map(t => t.address);
const initialDeposits = Object.keys(TEST_TOKENS).map(symbol => tokenAmounts[symbol].toString()); 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; const deadline = Math.floor(Date.now() / 1000) + 300;
console.log(`[~] Pool parameters:`); console.log(`[~] Pool parameters:`);
console.log(` Name: ${DEFAULT_POOL_PARAMS.name}`); console.log(` Name: ${DEFAULT_POOL_PARAMS.name}`);
console.log(` Symbol: ${DEFAULT_POOL_PARAMS.symbol}`); console.log(` Symbol: ${DEFAULT_POOL_PARAMS.symbol}`);
console.log(` Kappa: ${poolConfig.kappa} (${KAPPA.toString()})`);
console.log(` Tokens: ${tokenAddresses.join(', ')}`); console.log(` Tokens: ${tokenAddresses.join(', ')}`);
console.log(` Swap Fees PPM: [${DEFAULT_POOL_PARAMS.swapFeesPpm.join(', ')}]`); console.log(` Swap Fees PPM: [${DEFAULT_POOL_PARAMS.swapFeesPpm.join(', ')}]`);
console.log(` Payer (provides tokens): ${RECEIVER_ADDRESS}`); console.log(` Payer (provides tokens): ${RECEIVER_ADDRESS}`);
console.log(` Receiver (gets LP tokens): ${wallet.address}`); console.log(` Receiver (gets LP tokens): ${wallet.address}`);
console.log(` Deadline: ${new Date(deadline * 1000).toISOString()}`); console.log(` Deadline: ${new Date(deadline * 1000).toISOString()}`);
// Build cast send command
const castCommand = `cast send ${PARTY_PLANNER_ADDRESS} \ const castCommand = `cast send ${PARTY_PLANNER_ADDRESS} \
"newPool(string,string,address[],int128,uint256[],uint256,bool,address,address,uint256[],uint256,uint256)" \ "newPool(string,string,address[],int128,uint256[],uint256,bool,address,address,uint256[],uint256,uint256)" \
"${DEFAULT_POOL_PARAMS.name}" \ "${DEFAULT_POOL_PARAMS.name}" \
@@ -356,10 +282,14 @@ async function createPool(wallet, tokenAmounts) {
console.log(`\n[~] Cast command:\n${castCommand}\n`); console.log(`\n[~] Cast command:\n${castCommand}\n`);
try { try {
// Execute cast command
const { execSync } = await import('child_process'); 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(`[+] Pool created successfully!`);
console.log(output); console.log(output);
@@ -373,22 +303,21 @@ async function createPool(wallet, tokenAmounts) {
} }
} }
/**
* Print help message
*/
function printHelp() { function printHelp() {
console.log(` console.log(`
Usage: node create_pool_from_prices.js [OPTIONS] Usage: node create_pool_from_prices.js [OPTIONS]
Options: Options:
--amount <usd> USD amount to distribute (default: ${INPUT_USD_AMOUNT}) --config <file> Pool config JSON file (default: pool-og.json)
--name <name> Pool name (default: "${DEFAULT_POOL_PARAMS.name}") --amount <usd> USD amount to distribute (overrides config, default: ${INPUT_USD_AMOUNT})
--symbol <symbol> Pool symbol (default: "${DEFAULT_POOL_PARAMS.symbol}") --name <name> Pool name (overrides config)
--symbol <symbol> Pool symbol (overrides config)
--help, -h Show this help message --help, -h Show this help message
Example: Example:
node create_pool_from_prices.js 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() { async function main() {
console.log(`${'='.repeat(70)}`); console.log(`${'='.repeat(70)}`);
console.log(`Create Pool from Real-Time Prices`); console.log(`Create Pool from Real-Time Prices`);
console.log(`Network: ${NETWORK}`); console.log(`Network: ${NETWORK} Config: ${configFile}`);
console.log(`${'='.repeat(70)}\n`); console.log(`${'='.repeat(70)}\n`);
// Parse command line arguments
const args = process.argv.slice(2);
if (args.includes('--help') || args.includes('-h')) { if (args.includes('--help') || args.includes('-h')) {
printHelp(); printHelp();
process.exit(0); process.exit(0);
@@ -427,37 +353,27 @@ async function main() {
} }
} }
// Update pool params with parsed values
DEFAULT_POOL_PARAMS.name = poolName; DEFAULT_POOL_PARAMS.name = poolName;
DEFAULT_POOL_PARAMS.symbol = poolSymbol; DEFAULT_POOL_PARAMS.symbol = poolSymbol;
try { try {
// Step 1: Fetch prices
const prices = await fetchCoinGeckoPrices(); const prices = await fetchCoinGeckoPrices();
// Step 2: Calculate token amounts
const tokenAmounts = calculateTokenAmounts(prices, usdAmount); const tokenAmounts = calculateTokenAmounts(prices, usdAmount);
//
// // Step 3: Connect to wallet
console.log(`\n[~] Connecting to sender wallet at ${RPC_URL}...`); console.log(`\n[~] Connecting to sender wallet at ${RPC_URL}...`);
const provider = new ethers.providers.JsonRpcProvider(RPC_URL); const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const wallet = new ethers.Wallet(PRIVATE_KEY, provider); const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
console.log(`[+] Connected. Using sender wallet: ${wallet.address}`); console.log(`[+] Connected. Using sender wallet: ${wallet.address}`);
console.log(`[+] Receiver wallet: ${RECEIVER_ADDRESS}`); console.log(`[+] Receiver wallet: ${RECEIVER_ADDRESS}`);
//
// Step 4: Check balances
await checkBalances(provider, wallet, tokenAmounts, RECEIVER_ADDRESS); await checkBalances(provider, wallet, tokenAmounts, RECEIVER_ADDRESS);
// // Step 5: Approve tokens if (NETWORK === 'mockchain' && RECEIVER_PRIVATE_KEY) {
if (NETWORK === 'mockchain' && currentConfig.receiverPrivateKey) { await approveTokens(provider, tokenAmounts, RECEIVER_PRIVATE_KEY);
// On mockchain, use receiver wallet for approvals
await approveTokens(provider, tokenAmounts, currentConfig.receiverPrivateKey);
} else if (NETWORK === 'mainnet') { } else if (NETWORK === 'mainnet') {
// On mainnet, use the main wallet (payer and receiver are the same)
await approveTokens(provider, tokenAmounts, PRIVATE_KEY); await approveTokens(provider, tokenAmounts, PRIVATE_KEY);
} }
// Step 6: Create pool
await createPool(wallet, tokenAmounts); await createPool(wallet, tokenAmounts);
console.log(`\n${'='.repeat(70)}`); console.log(`\n${'='.repeat(70)}`);
@@ -472,8 +388,7 @@ async function main() {
} }
} }
// Run the main function
main().catch(error => { main().catch(error => {
console.error('[!] Unexpected error:', error); console.error('[!] Unexpected error:', error);
process.exit(1); process.exit(1);
}); });

894
scripts/package-lock.json generated Normal file
View File

@@ -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
}
}
}
}
}

View File

@@ -5,7 +5,8 @@
"type": "module", "type": "module",
"private": true, "private": true,
"scripts": { "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": { "dependencies": {
"dotenv": "^17.2.3", "dotenv": "^17.2.3",

70
scripts/pool-og.json Normal file
View File

@@ -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
}
}
}

28
scripts/pool-test.json Normal file
View File

@@ -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
}
}
}

View File

@@ -426,7 +426,7 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
<span className="font-medium">{pool.symbol}</span> <span className="font-medium">{pool.symbol}</span>
<span className="text-xs text-muted-foreground">{pool.name}</span> <span className="text-xs text-muted-foreground">{pool.name}</span>
</div> </div>
{(pool.price || pool.tvl) && ( {(pool.price || pool.tvl || pool.apy) && (
<div className="flex flex-col items-end"> <div className="flex flex-col items-end">
{pool.price && ( {pool.price && (
<span className="text-sm font-medium text-muted-foreground">{pool.price}</span> <span className="text-sm font-medium text-muted-foreground">{pool.price}</span>
@@ -434,6 +434,9 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
{pool.tvl && ( {pool.tvl && (
<span className="text-xs text-muted-foreground">TVL: {pool.tvl}</span> <span className="text-xs text-muted-foreground">TVL: {pool.tvl}</span>
)} )}
{pool.apy && (
<span className="text-xs text-muted-foreground">APY: {pool.apy}</span>
)}
</div> </div>
)} )}
</div> </div>
@@ -502,7 +505,7 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
</div> </div>
<span className="text-xs text-muted-foreground">{pool.name}</span> <span className="text-xs text-muted-foreground">{pool.name}</span>
</div> </div>
{!pool.isKilled && (pool.price || pool.tvl) && ( {!pool.isKilled && (pool.price || pool.tvl || pool.apy) && (
<div className="flex flex-col items-end"> <div className="flex flex-col items-end">
{pool.price && ( {pool.price && (
<span className="text-sm font-medium text-muted-foreground">{pool.price}</span> <span className="text-sm font-medium text-muted-foreground">{pool.price}</span>
@@ -510,6 +513,9 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
{pool.tvl && ( {pool.tvl && (
<span className="text-xs text-muted-foreground">TVL: {pool.tvl}</span> <span className="text-xs text-muted-foreground">TVL: {pool.tvl}</span>
)} )}
{pool.apy && (
<span className="text-xs text-muted-foreground">APY: {pool.apy}</span>
)}
</div> </div>
)} )}
</div> </div>

View File

@@ -170,12 +170,12 @@ const IPartyInfoABI = [
"internalType": "contract IPartyPool" "internalType": "contract IPartyPool"
}, },
{ {
"name": "baseTokenIndex", "name": "inputTokenIndex",
"type": "uint256", "type": "uint256",
"internalType": "uint256" "internalType": "uint256"
}, },
{ {
"name": "quoteTokenIndex", "name": "outputTokenIndex",
"type": "uint256", "type": "uint256",
"internalType": "uint256" "internalType": "uint256"
} }
@@ -189,6 +189,94 @@ const IPartyInfoABI = [
], ],
"stateMutability": "view" "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", "type": "function",
"name": "swapMintAmounts", "name": "swapMintAmounts",
@@ -228,50 +316,6 @@ const IPartyInfoABI = [
], ],
"stateMutability": "view" "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", "type": "function",
"name": "working", "name": "working",

View File

@@ -1,6 +1,13 @@
/* GENERATED FILE: DO NOT EDIT! */ /* GENERATED FILE: DO NOT EDIT! */
const IPartyPlannerABI = [ const IPartyPlannerABI = [
{
"type": "function",
"name": "acceptOwnership",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{ {
"type": "function", "type": "function",
"name": "getAllPools", "name": "getAllPools",
@@ -97,19 +104,6 @@ const IPartyPlannerABI = [
], ],
"stateMutability": "view" "stateMutability": "view"
}, },
{
"type": "function",
"name": "mintImpl",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "contract PartyPoolMintImpl"
}
],
"stateMutability": "view"
},
{ {
"type": "function", "type": "function",
"name": "newPool", "name": "newPool",
@@ -365,6 +359,19 @@ const IPartyPlannerABI = [
], ],
"stateMutability": "view" "stateMutability": "view"
}, },
{
"type": "function",
"name": "pendingOwner",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{ {
"type": "function", "type": "function",
"name": "poolCount", "name": "poolCount",
@@ -399,28 +406,32 @@ const IPartyPlannerABI = [
}, },
{ {
"type": "function", "type": "function",
"name": "renounceOwnership", "name": "tokenCount",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "swapImpl",
"inputs": [], "inputs": [],
"outputs": [ "outputs": [
{ {
"name": "", "name": "",
"type": "address", "type": "uint256",
"internalType": "contract PartyPoolSwapImpl" "internalType": "uint256"
} }
], ],
"stateMutability": "view" "stateMutability": "view"
}, },
{ {
"type": "function", "type": "function",
"name": "tokenCount", "name": "tokenIndex",
"inputs": [], "inputs": [
{
"name": "pool",
"type": "address",
"internalType": "contract IPartyPool"
},
{
"name": "token",
"type": "address",
"internalType": "contract IERC20"
}
],
"outputs": [ "outputs": [
{ {
"name": "", "name": "",
@@ -443,6 +454,25 @@ const IPartyPlannerABI = [
"outputs": [], "outputs": [],
"stateMutability": "nonpayable" "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", "type": "event",
"name": "OwnershipTransferred", "name": "OwnershipTransferred",

View File

@@ -26,6 +26,13 @@ const IPartyPoolABI = [
], ],
"stateMutability": "view" "stateMutability": "view"
}, },
{
"type": "function",
"name": "acceptOwnership",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{ {
"type": "function", "type": "function",
"name": "allProtocolFeesOwed", "name": "allProtocolFeesOwed",
@@ -100,25 +107,6 @@ const IPartyPoolABI = [
], ],
"stateMutability": "nonpayable" "stateMutability": "nonpayable"
}, },
{
"type": "function",
"name": "balance",
"inputs": [
{
"name": "index",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{ {
"type": "function", "type": "function",
"name": "balanceOf", "name": "balanceOf",
@@ -214,6 +202,11 @@ const IPartyPoolABI = [
"type": "uint256", "type": "uint256",
"internalType": "uint256" "internalType": "uint256"
}, },
{
"name": "minAmountOut",
"type": "uint256",
"internalType": "uint256"
},
{ {
"name": "deadline", "name": "deadline",
"type": "uint256", "type": "uint256",
@@ -272,30 +265,6 @@ const IPartyPoolABI = [
], ],
"stateMutability": "view" "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", "type": "function",
"name": "fees", "name": "fees",
@@ -380,19 +349,6 @@ const IPartyPoolABI = [
], ],
"stateMutability": "payable" "stateMutability": "payable"
}, },
{
"type": "function",
"name": "kappa",
"inputs": [],
"outputs": [
{
"name": "",
"type": "int128",
"internalType": "int128"
}
],
"stateMutability": "view"
},
{ {
"type": "function", "type": "function",
"name": "kill", "name": "kill",
@@ -422,6 +378,11 @@ const IPartyPoolABI = [
"type": "address", "type": "address",
"internalType": "address" "internalType": "address"
}, },
{
"name": "fundingSelector",
"type": "bytes4",
"internalType": "bytes4"
},
{ {
"name": "receiver", "name": "receiver",
"type": "address", "type": "address",
@@ -436,6 +397,11 @@ const IPartyPoolABI = [
"name": "deadline", "name": "deadline",
"type": "uint256", "type": "uint256",
"internalType": "uint256" "internalType": "uint256"
},
{
"name": "cbData",
"type": "bytes",
"internalType": "bytes"
} }
], ],
"outputs": [ "outputs": [
@@ -447,19 +413,6 @@ const IPartyPoolABI = [
], ],
"stateMutability": "payable" "stateMutability": "payable"
}, },
{
"type": "function",
"name": "mintImpl",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{ {
"type": "function", "type": "function",
"name": "name", "name": "name",
@@ -499,6 +452,19 @@ const IPartyPoolABI = [
], ],
"stateMutability": "view" "stateMutability": "view"
}, },
{
"type": "function",
"name": "pendingOwner",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{ {
"type": "function", "type": "function",
"name": "protocolFeeAddress", "name": "protocolFeeAddress",
@@ -525,13 +491,6 @@ const IPartyPoolABI = [
], ],
"stateMutability": "view" "stateMutability": "view"
}, },
{
"type": "function",
"name": "renounceOwnership",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{ {
"type": "function", "type": "function",
"name": "swap", "name": "swap",
@@ -567,9 +526,9 @@ const IPartyPoolABI = [
"internalType": "uint256" "internalType": "uint256"
}, },
{ {
"name": "limitPrice", "name": "minAmountOut",
"type": "int128", "type": "uint256",
"internalType": "int128" "internalType": "uint256"
}, },
{ {
"name": "deadline", "name": "deadline",
@@ -606,115 +565,9 @@ const IPartyPoolABI = [
], ],
"stateMutability": "payable" "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", "type": "function",
"name": "swapMint", "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": [ "inputs": [
{ {
"name": "payer", "name": "payer",
@@ -737,25 +590,20 @@ const IPartyPoolABI = [
"internalType": "uint256" "internalType": "uint256"
}, },
{ {
"name": "outputTokenIndex", "name": "maxAmountIn",
"type": "uint256", "type": "uint256",
"internalType": "uint256" "internalType": "uint256"
}, },
{ {
"name": "limitPrice", "name": "minLpOut",
"type": "int128", "type": "uint256",
"internalType": "int128" "internalType": "uint256"
}, },
{ {
"name": "deadline", "name": "deadline",
"type": "uint256", "type": "uint256",
"internalType": "uint256" "internalType": "uint256"
}, },
{
"name": "unwrap",
"type": "bool",
"internalType": "bool"
},
{ {
"name": "cbData", "name": "cbData",
"type": "bytes", "type": "bytes",
@@ -769,7 +617,7 @@ const IPartyPoolABI = [
"internalType": "uint256" "internalType": "uint256"
}, },
{ {
"name": "amountOut", "name": "lpMinted",
"type": "uint256", "type": "uint256",
"internalType": "uint256" "internalType": "uint256"
}, },
@@ -1090,6 +938,25 @@ const IPartyPoolABI = [
], ],
"anonymous": false "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", "type": "event",
"name": "OwnershipTransferred", "name": "OwnershipTransferred",

View File

@@ -8,6 +8,7 @@ import IPartyPoolABI from '@/contracts/IPartyPoolABI';
import IPartyPoolViewerABI from '@/contracts/IPartyPoolViewerABI'; import IPartyPoolViewerABI from '@/contracts/IPartyPoolViewerABI';
import IPartyInfoABI from '@/contracts/IPartyInfoABI'; import IPartyInfoABI from '@/contracts/IPartyInfoABI';
import { ERC20ABI } from '@/contracts/ERC20ABI'; import { ERC20ABI } from '@/contracts/ERC20ABI';
import { fetchPoolMetrics } from '@/lib/poolMetricsCache';
// Helper function to format large numbers with K, M, B suffixes // Helper function to format large numbers with K, M, B suffixes
function formatTVL(value: number): string { function formatTVL(value: number): string {
@@ -444,6 +445,7 @@ export interface PoolDetails {
tokens: readonly `0x${string}`[]; tokens: readonly `0x${string}`[];
price?: string; // Formatted price string price?: string; // Formatted price string
tvl?: string; // Formatted TVL string (e.g., "$1.2M") 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 isKilled: boolean; // Whether the pool has been killed
} }
@@ -495,6 +497,9 @@ export function useGetAllPools(offset: number = 0, limit: number = 100) {
setPools(result); 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 // Fetch details for each pool and check if it's working
const details: PoolDetails[] = []; const details: PoolDetails[] = [];
for (const poolAddress of result) { 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) // Fetch pool price and TVL (only for working pools)
let priceStr: string | undefined; let priceStr: string | undefined;
let tvlStr: string | undefined; let tvlStr: string | undefined;
let apyStr: string | undefined;
if (isWorking) { if (isWorking) {
// Fetch token decimals and balance first (needed for both price and TVL) // 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)}`; priceStr = `$${finalPrice.toFixed(4)}`;
} }
// Calculate TVL (approximate by getting first token balance and multiplying by number of tokens) // Use Substreams TVL + APY if available; fall back to on-chain estimate
const tokenBalance = Number(balance) / Math.pow(10, decimals); const metricKey = poolAddress.toLowerCase().replace('0x', '');
const approximateTVL = tokenBalance * tokens.length; const metric = poolMetricsData[metricKey];
tvlStr = formatTVL(approximateTVL); 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) { } catch (err) {
console.error(`Error fetching pool price/TVL for ${poolAddress}:`, 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}`[], tokens: tokens as readonly `0x${string}`[],
price: priceStr, price: priceStr,
tvl: tvlStr, tvl: tvlStr,
apy: apyStr,
isKilled: !isWorking, isKilled: !isWorking,
}); });
} catch (err) { } catch (err) {

123
src/lib/poolMetricsCache.ts Normal file
View File

@@ -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<string, PoolMetricEntry>; // 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<Record<string, PoolMetricEntry>> | null = null;
export async function fetchPoolMetrics(): Promise<Record<string, PoolMetricEntry>> {
// 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<string, PoolMetricEntry> = {};
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;
}