Compare commits

..

3 Commits

26 changed files with 1581 additions and 5451 deletions

11
.env-secret Normal file
View File

@@ -0,0 +1,11 @@
# Secret environment variables - DO NOT COMMIT TO GIT
# Add this file to .gitignore
# RPC Node Connection
MAINNET_RPC_URL=https://eth-1.dxod.org/joEnzz51UH6Bc2yU
ALCHEMY_RPC_URL=https://eth-mainnet.g.alchemy.com/v2/o_eQWfo1Rb7qZKpl_vBRL
# Receiver Address
RECEIVER_ADDRESS=0xd3b310bd32d782f89eea49cb79656bcaccde7213
PRIVATE_KEY=89c8f2542b5ff7f3cf0b73255e0a8d79d89c2be598e7f272a275a380ff56a212

3
.gitignore vendored
View File

@@ -5,9 +5,6 @@
/.pnp
.pnp.js
*secret*
.env
# testing
/coverage

View File

@@ -1,4 +0,0 @@
cast call 0x8f98B899F4135408Fe03228cE942Ad6BF8E40f22 \
"working(address)" \
0x3EDE1eE859A72aEc85ff04d305B6Ffe89f2Cb4eb \
--rpc-url https://eth-sepolia.g.alchemy.com/v2/demo

View File

@@ -26,14 +26,6 @@ else
echo Building
fi
# Load secret env vars (including NEXT_PUBLIC_* vars baked into the static bundle)
if [ -f .env-secret ]; then
set -a
# shellcheck disable=SC1091
source .env-secret
set +a
fi
npm run build || exit 1
if [ "$BUILD" != "1" ]; then

View File

@@ -1,6 +1,6 @@
#!/bin/sh
CHAIN_ID=${1:-1}
CHAIN_ID=${1:-31337}
case "$CHAIN_ID" in
"sepolia")

3196
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,7 @@
"clsx": "^2.1.1",
"i18next": "^23.15.0",
"lucide-react": "^0.460.0",
"next": "15.5.7",
"next": "^15.1.3",
"next-themes": "^0.4.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
@@ -33,7 +33,6 @@
"@types/react": "^19",
"@types/react-dom": "^19",
"autoprefixer": "^10.4.20",
"baseline-browser-mapping": "^2.10.0",
"postcss": "^8.4.49",
"tailwindcss": "^3.4.17",
"typescript": "^5"

View File

@@ -1,911 +0,0 @@
#!/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,60 +8,110 @@ import { ethers } from 'ethers';
import { readFile } from 'fs/promises';
import { config } from 'dotenv';
// Load environment variables from .env
config({ path: new URL('../.env', import.meta.url).pathname });
// Load environment variables from .env-secret
config({ path: new URL('../.env-secret', import.meta.url).pathname });
// ============================================================================
// LOAD POOL CONFIG
// CONFIGURATION
// ============================================================================
const args = process.argv.slice(2);
function getArg(flag) {
const idx = args.indexOf(flag);
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : null;
}
const configFile = getArg('--config') || 'pool-test.json';
const poolConfig = JSON.parse(
await readFile(new URL(configFile, import.meta.url), 'utf-8')
);
const TEST_TOKENS = poolConfig.tokens;
// Convert kappa from float to Q64.64 fixed-point BigInt
// e.g. 0.01 -> round(0.01 * 2^64)
function kappaToFixedPoint(kappaFloat) {
const str = kappaFloat.toString();
const [whole, frac = ''] = str.split('.');
const precision = frac.length;
const numerator = BigInt(whole + frac);
const denominator = BigInt(10 ** precision);
const scale = 2n ** 64n;
return (numerator * scale + denominator / 2n) / denominator;
}
const KAPPA = ethers.BigNumber.from(kappaToFixedPoint(poolConfig.kappa).toString());
const FLASH_FEE_PPM = poolConfig.flashFeePpm;
const INPUT_USD_AMOUNT = poolConfig.inputUsdAmount;
// ============================================================================
// NETWORK CONFIGURATION
// ============================================================================
// Network flag: 'mockchain' or 'mainnet'
const NETWORK = process.env.NETWORK || 'mainnet';
// Network-specific configuration
const NETWORK_CONFIG = {
mockchain: {
rpcUrl: process.env.MOCKCHAIN_RPC_URL || 'http://localhost:8545',
rpcUrl: 'http://localhost:8545',
privateKey: '0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a', // Dev wallet #4 (sender)
receiverPrivateKey: '0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356', // Receiver wallet
receiverAddress: '0x14dC79964da2C08b23698B3D3cc7Ca32193d9955', // Receiver public key
chainId: '31337',
tokens: {
USDT: {
address: '0xbdEd0D2bf404bdcBa897a74E6657f1f12e5C6fb6', // USXD on mockchain
coingeckoId: 'tether',
decimals: 6,
feePpm: 400
},
USDC: {
address: '0x2910E325cf29dd912E3476B61ef12F49cb931096', // FUSD on mockchain
coingeckoId: 'usd-coin',
decimals: 6,
feePpm: 400
}
}
},
mainnet: {
rpcUrl: process.env.MAINNET_RPC_URL,
privateKey: process.env.PRIVATE_KEY,
receiverAddress: '0xd3b310bd32d782f89eea49cb79656bcaccde7213', // Same as payer for mainnet
chainId: '1',
tokens: {
USDT: {
address: '0xdAC17F958D2ee523a2206206994597C13D831ec7',
coingeckoId: 'tether',
decimals: 6,
feePpm: 40 // 0.0004%
},
USDC: {
address: '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
coingeckoId: 'usd-coin',
decimals: 6,
feePpm: 40 // 0.0004%
},
WBTC: {
address: '0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599',
coingeckoId: 'wrapped-bitcoin',
decimals: 8,
feePpm: 300 // 0.00030%
},
WETH: {
address: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2',
coingeckoId: 'weth',
decimals: 18,
feePpm: 350 // 0.0035%
},
UNI: {
address: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
coingeckoId: 'uniswap',
decimals: 18,
feePpm: 1450 // 0.00145%
},
WSOL: {
address: '0xD31a59c85aE9D8edEFeC411D448f90841571b89c', // Wormhole Wrapped SOL
coingeckoId: 'solana',
decimals: 9,
feePpm: 950 // 0.00095%
},
TRX: {
address: '0x50327c6c5a14DCaDE707ABad2E27eB517df87AB5',
coingeckoId: 'tron',
decimals: 6,
feePpm: 950 // 0.00095%
},
AAVE: {
address: '0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9',
coingeckoId: 'aave',
decimals: 18,
feePpm: 1450 // 0.00145%
},
PEPE: {
address: '0x6982508145454Ce325dDbE47a25d4ec3d2311933',
coingeckoId: 'pepe',
decimals: 18,
feePpm: 2150 // 0.00215%
},
SHIB: {
address: '0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE',
coingeckoId: 'shiba-inu',
decimals: 18,
feePpm: 2150 // 0.00215%
}
}
}
};
const NETWORK = process.env.NETWORK || 'mainnet';
// Get current network config
const currentConfig = NETWORK_CONFIG[NETWORK];
if (!currentConfig) {
console.error(`[!] Invalid NETWORK: ${NETWORK}. Must be 'mockchain' or 'mainnet'`);
@@ -69,10 +119,10 @@ if (!currentConfig) {
}
const RPC_URL = currentConfig.rpcUrl;
const PRIVATE_KEY = process.env.PRIVATE_KEY;
const RECEIVER_PRIVATE_KEY = process.env.RECEIVER_PRIVATE_KEY;
const RECEIVER_ADDRESS = process.env.RECEIVER_ADDRESS;
const PRIVATE_KEY = currentConfig.privateKey;
const RECEIVER_ADDRESS = currentConfig.receiverAddress || process.env.RECEIVER_ADDRESS;
// Validate required config for mainnet
if (NETWORK === 'mainnet' && (!RPC_URL || !PRIVATE_KEY)) {
console.error('[!] Missing required environment variables for mainnet');
console.error(' Required: MAINNET_RPC_URL, PRIVATE_KEY');
@@ -80,23 +130,30 @@ if (NETWORK === 'mainnet' && (!RPC_URL || !PRIVATE_KEY)) {
}
if (!RECEIVER_ADDRESS) {
console.error('[!] Missing RECEIVER_ADDRESS in .env file');
console.error('[!] Missing RECEIVER_ADDRESS in .env-secret file');
process.exit(1);
}
// Use network-specific tokens
const TEST_TOKENS = currentConfig.tokens;
// Default pool parameters
const DEFAULT_POOL_PARAMS = {
name: poolConfig.name,
symbol: poolConfig.symbol,
kappa: KAPPA,
name: 'Original Genesis of Liquidity Party',
symbol: 'OG.LP',
kappa: ethers.BigNumber.from('184467440737095520'), //0.01 * 2^64
swapFeesPpm: Object.values(TEST_TOKENS).map(t => t.feePpm),
flashFeePpm: FLASH_FEE_PPM,
stable: poolConfig.stable,
initialLpAmount: ethers.utils.parseUnits(INPUT_USD_AMOUNT.toString(), 18)
flashFeePpm: 5, // 0.0005%
stable: false,
initialLpAmount: ethers.utils.parseUnits('1', 18) // 100 USD in 18 decimals
};
// Input amount in USD
const INPUT_USD_AMOUNT = 1;
// ============================================================================
// LOAD ABIs AND CONTRACT ADDRESSES
// LOAD ABIs AND CONFIG
// ============================================================================
const chainInfoData = JSON.parse(await readFile(new URL('../src/contracts/liqp-deployments.json', import.meta.url), 'utf-8'));
@@ -105,7 +162,6 @@ const PARTY_PLANNER_ADDRESS = chainInfoData[currentConfig.chainId].v1.PartyPlann
const ERC20ABI = [
{ "type": "function", "name": "balanceOf", "stateMutability": "view", "inputs": [{ "name": "account", "type": "address" }], "outputs": [{ "name": "", "type": "uint256" }] },
{ "type": "function", "name": "decimals", "stateMutability": "view", "inputs": [], "outputs": [{ "name": "", "type": "uint8" }] },
{ "type": "function", "name": "allowance", "stateMutability": "view", "inputs": [{ "name": "owner", "type": "address" }, { "name": "spender", "type": "address" }], "outputs": [{ "name": "", "type": "uint256" }] },
{ "type": "function", "name": "approve", "stateMutability": "nonpayable", "inputs": [{ "name": "spender", "type": "address" }, { "name": "amount", "type": "uint256" }], "outputs": [{ "name": "", "type": "bool" }] }
];
@@ -114,6 +170,9 @@ const ERC20ABI = [
// HELPER FUNCTIONS
// ============================================================================
/**
* Fetch real-time prices from CoinGecko API
*/
async function fetchCoinGeckoPrices() {
try {
const ids = Object.values(TEST_TOKENS).map(t => t.coingeckoId).join(',');
@@ -149,17 +208,24 @@ async function fetchCoinGeckoPrices() {
}
}
/**
* Calculate token amounts based on equal USD distribution
*/
function calculateTokenAmounts(prices, usdAmount) {
const tokenCount = Object.keys(TEST_TOKENS).length;
const usdPerToken = usdAmount / tokenCount;
const usdPerToken = usdAmount / tokenCount; // Equally distribute among all tokens
const tokenAmounts = {};
console.log(`\n[~] Calculated token amounts for $${usdAmount} ($${usdPerToken.toFixed(2)} per token):`);
for (const [symbol, tokenInfo] of Object.entries(TEST_TOKENS)) {
// Calculate raw amount
const rawAmount = usdPerToken / prices[symbol];
// Convert to BigNumber with proper decimals
const amountBN = ethers.utils.parseUnits(rawAmount.toFixed(tokenInfo.decimals), tokenInfo.decimals);
tokenAmounts[symbol] = amountBN;
console.log(` ${symbol.padEnd(6)}: ${rawAmount.toFixed(tokenInfo.decimals)} (${amountBN.toString()} wei)`);
}
@@ -167,6 +233,9 @@ function calculateTokenAmounts(prices, usdAmount) {
return tokenAmounts;
}
/**
* Check token balances
*/
async function checkBalances(provider, wallet, tokenAmounts, receiverAddress) {
console.log(`\n[~] Checking token balances for receiver wallet: ${receiverAddress}`);
@@ -200,32 +269,29 @@ async function checkBalances(provider, wallet, tokenAmounts, receiverAddress) {
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...`);
const signerWallet = new ethers.Wallet(signerPrivateKey, provider);
console.log(`[~] Using wallet for approvals: ${signerWallet.address}`);
// Connect with receiver wallet for approvals
const receiverWallet = new ethers.Wallet(receiverPrivateKey, provider);
console.log(`[~] Using receiver wallet for approvals: ${receiverWallet.address}`);
for (const [symbol, tokenInfo] of Object.entries(TEST_TOKENS)) {
const tokenContract = new ethers.Contract(tokenInfo.address, ERC20ABI, signerWallet);
const tokenContract = new ethers.Contract(tokenInfo.address, ERC20ABI, receiverWallet);
// Approve 1% more than needed to account for fees/slippage
const requiredAmount = tokenAmounts[symbol];
const approvalAmount = requiredAmount.mul(101).div(100); // 1% buffer
console.log(` [~] Approving ${symbol} ${tokenInfo.address} ${approvalAmount} (1% buffer)...`);
try {
const currentAllowance = await tokenContract.allowance(signerWallet.address, PARTY_PLANNER_ADDRESS);
const currentFormatted = ethers.utils.formatUnits(currentAllowance, tokenInfo.decimals);
const requiredFormatted = ethers.utils.formatUnits(approvalAmount, tokenInfo.decimals);
if (currentAllowance.gte(approvalAmount)) {
console.log(` [=] ${symbol} allowance already sufficient: ${currentFormatted} >= ${requiredFormatted} (skipping)`);
continue;
}
console.log(` [~] Approving ${symbol} ${tokenInfo.address} ${approvalAmount} (current: ${currentFormatted}, need: ${requiredFormatted})...`);
// USDT and some tokens require setting allowance to 0 before raising it
if (symbol == 'USDT' && !currentAllowance.isZero()) {
// USDT and some tokens require setting allowance to 0 before setting a new value
// Skip for BNB as it has a broken approve function
if (symbol == 'USDT') {
const resetTx = await tokenContract.approve(PARTY_PLANNER_ADDRESS, 0);
await resetTx.wait();
console.log(` [+] ${symbol} allowance reset to 0`);
@@ -243,24 +309,29 @@ async function approveTokens(provider, tokenAmounts, signerPrivateKey) {
console.log(`[+] All tokens approved`);
}
/**
* Create a new pool using cast send
*/
async function createPool(wallet, tokenAmounts) {
console.log(`\n[~] Creating new pool...`);
// Prepare parameters
const tokenAddresses = Object.values(TEST_TOKENS).map(t => t.address);
const initialDeposits = Object.keys(TEST_TOKENS).map(symbol => tokenAmounts[symbol].toString());
// Set deadline to 5 minutes from now
const deadline = Math.floor(Date.now() / 1000) + 300;
console.log(`[~] Pool parameters:`);
console.log(` Name: ${DEFAULT_POOL_PARAMS.name}`);
console.log(` Symbol: ${DEFAULT_POOL_PARAMS.symbol}`);
console.log(` Kappa: ${poolConfig.kappa} (${KAPPA.toString()})`);
console.log(` Tokens: ${tokenAddresses.join(', ')}`);
console.log(` Swap Fees PPM: [${DEFAULT_POOL_PARAMS.swapFeesPpm.join(', ')}]`);
console.log(` Payer (provides tokens): ${RECEIVER_ADDRESS}`);
console.log(` Receiver (gets LP tokens): ${wallet.address}`);
console.log(` Deadline: ${new Date(deadline * 1000).toISOString()}`);
// Build cast send command
const castCommand = `cast send ${PARTY_PLANNER_ADDRESS} \
"newPool(string,string,address[],int128,uint256[],uint256,bool,address,address,uint256[],uint256,uint256)" \
"${DEFAULT_POOL_PARAMS.name}" \
@@ -282,13 +353,9 @@ async function createPool(wallet, tokenAmounts) {
console.log(`\n[~] Cast command:\n${castCommand}\n`);
try {
// Execute cast command
const { execSync } = await import('child_process');
const foundryBin = `${process.env.HOME}/.foundry/bin`;
const env = {
...process.env,
PATH: `${foundryBin}:${process.env.PATH || ''}`,
};
const output = execSync(castCommand, { encoding: 'utf-8', env });
const output = execSync(castCommand, { encoding: 'utf-8' });
console.log(`[+] Pool created successfully!`);
console.log(output);
@@ -303,21 +370,22 @@ async function createPool(wallet, tokenAmounts) {
}
}
/**
* Print help message
*/
function printHelp() {
console.log(`
Usage: node create_pool_from_prices.js [OPTIONS]
Options:
--config <file> Pool config JSON file (default: pool-og.json)
--amount <usd> USD amount to distribute (overrides config, default: ${INPUT_USD_AMOUNT})
--name <name> Pool name (overrides config)
--symbol <symbol> Pool symbol (overrides config)
--amount <usd> USD amount to distribute (default: ${INPUT_USD_AMOUNT})
--name <name> Pool name (default: "${DEFAULT_POOL_PARAMS.name}")
--symbol <symbol> Pool symbol (default: "${DEFAULT_POOL_PARAMS.symbol}")
--help, -h Show this help message
Example:
node create_pool_from_prices.js
node create_pool_from_prices.js --config pool-test.json
node create_pool_from_prices.js --config pool-test.json --amount 200
node create_pool_from_prices.js --amount 200 --name "My Pool" --symbol "MP"
`);
}
@@ -328,9 +396,12 @@ Example:
async function main() {
console.log(`${'='.repeat(70)}`);
console.log(`Create Pool from Real-Time Prices`);
console.log(`Network: ${NETWORK} Config: ${configFile}`);
console.log(`Network: ${NETWORK}`);
console.log(`${'='.repeat(70)}\n`);
// Parse command line arguments
const args = process.argv.slice(2);
if (args.includes('--help') || args.includes('-h')) {
printHelp();
process.exit(0);
@@ -353,27 +424,37 @@ async function main() {
}
}
// Update pool params with parsed values
DEFAULT_POOL_PARAMS.name = poolName;
DEFAULT_POOL_PARAMS.symbol = poolSymbol;
try {
// Step 1: Fetch prices
const prices = await fetchCoinGeckoPrices();
const tokenAmounts = calculateTokenAmounts(prices, usdAmount);
// Step 2: Calculate token amounts
const tokenAmounts = calculateTokenAmounts(prices, usdAmount);
//
// // Step 3: Connect to wallet
console.log(`\n[~] Connecting to sender wallet at ${RPC_URL}...`);
const provider = new ethers.providers.JsonRpcProvider(RPC_URL);
const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
console.log(`[+] Connected. Using sender wallet: ${wallet.address}`);
console.log(`[+] Receiver wallet: ${RECEIVER_ADDRESS}`);
//
// Step 4: Check balances
await checkBalances(provider, wallet, tokenAmounts, RECEIVER_ADDRESS);
if (NETWORK === 'mockchain' && RECEIVER_PRIVATE_KEY) {
await approveTokens(provider, tokenAmounts, RECEIVER_PRIVATE_KEY);
// // Step 5: Approve tokens
if (NETWORK === 'mockchain' && currentConfig.receiverPrivateKey) {
// On mockchain, use receiver wallet for approvals
await approveTokens(provider, tokenAmounts, currentConfig.receiverPrivateKey);
} else if (NETWORK === 'mainnet') {
// On mainnet, use the main wallet (payer and receiver are the same)
await approveTokens(provider, tokenAmounts, PRIVATE_KEY);
}
// Step 6: Create pool
await createPool(wallet, tokenAmounts);
console.log(`\n${'='.repeat(70)}`);
@@ -388,6 +469,7 @@ async function main() {
}
}
// Run the main function
main().catch(error => {
console.error('[!] Unexpected error:', error);
process.exit(1);

View File

@@ -1,894 +0,0 @@
{
"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,11 +5,9 @@
"type": "module",
"private": true,
"scripts": {
"create-pool": "node create_pool_from_prices.js",
"adjust-pool": "node adjust_pool_prices_and_stake.js"
"create-pool": "node create_pool_from_prices.js"
},
"dependencies": {
"dotenv": "^17.2.3",
"ethers": "^5.7.2"
}
}

View File

@@ -1,70 +0,0 @@
{
"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
}
}
}

View File

@@ -1,28 +0,0 @@
{
"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

@@ -224,7 +224,7 @@ export default function AboutPage() {
<p className="text-muted-foreground leading-relaxed">
Verify our contracts on{' '}
<a
href="https://etherscan.io/address/0x42977f565971F6D288a05ddEbC87A17276F71A29#code"
href="https://sepolia.etherscan.io/address/0x081aA8AB1984680087c01a5Cd50fC9f49742434D#code"
target="liqp_etherscan"
rel="noopener noreferrer"
className="text-primary hover:underline"

View File

@@ -2,7 +2,6 @@ import '@rainbow-me/rainbowkit/styles.css';
import '@/app/globals.css';
import { Providers } from '@/components/providers';
import { Metadata } from 'next';
import Script from "next/script";
export const metadata: Metadata = {
metadataBase: new URL('https://liquidity.party'),
@@ -38,19 +37,6 @@ export default function RootLayout({
return (
<html lang="en" suppressHydrationWarning>
<body className="min-h-screen">
{/* Google tag (gtag.js) */}
<Script
src="https://www.googletagmanager.com/gtag/js?id=G-GH2R6NTLC3"
strategy="afterInteractive"
/>
<Script id="gtag-init" strategy="afterInteractive">
{`
window.dataLayer = window.dataLayer || [];
function gtag(){window.dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-GH2R6NTLC3');
`}
</Script>
<Providers>{children}</Providers>
</body>
</html>

View File

@@ -28,16 +28,7 @@ const mockchain = defineChain({
testnet: true,
});
// Create a deep copy of mainnet with custom RPC URL
const ethereum = {
...mainnet,
rpcUrls: {
...mainnet.rpcUrls,
default: {http: ['https://mainnet.chainnodes.org/f88f1b21-91dd-4ba2-bbb2-6a48cf57b987']},
},
};
const allChains = [ethereum, sepolia, mockchain];
const allChains = [mainnet, polygon, optimism, arbitrum, base, sepolia, mockchain];
const chains = allChains.filter(chain =>
Object.keys(deployments || {}).includes(chain.id.toString())
);

View File

@@ -20,38 +20,6 @@ interface StakeFormProps {
defaultMode?: Mode;
}
// Helper component for slippage warnings
function SlippageWarning({
slippage,
action,
isError
}: {
slippage: number;
action: string;
isError: boolean;
}) {
if (isError) {
return (
<div className="px-4 py-3 bg-destructive/10 border border-destructive/20 rounded-lg">
<p className="text-sm text-destructive font-medium"> Slippage Exceeds 5%</p>
<p className="text-xs text-destructive/80 mt-1">
The estimated slippage for this {action} is {Math.abs(slippage).toFixed(2)}%.
We cannot process this {action} as you may lose too much money due to the high slippage.
</p>
</div>
);
}
return (
<div className="px-4 py-3 bg-yellow-500/10 border border-yellow-500/20 rounded-lg">
<p className="text-sm text-yellow-600 dark:text-yellow-500 font-medium"> High Slippage Warning</p>
<p className="text-xs text-yellow-600/80 dark:text-yellow-500/80 mt-1">
The estimated slippage for this {action} is {Math.abs(slippage).toFixed(2)}%. You may lose money due to low liquidity in this pool.
</p>
</div>
);
}
interface TokenInfo {
address: `0x${string}`;
symbol: string;
@@ -173,48 +141,23 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
}, [stakeAmount, selectedToken, mode]);
// Fetch swap mint amounts (for stake mode)
const { swapMintAmounts, loading: swapMintLoading, error: swapMintError } = useSwapMintAmounts(
const { swapMintAmounts, loading: swapMintLoading } = useSwapMintAmounts(
mode === 'stake' ? selectedPool?.address : undefined,
mode === 'stake' ? inputTokenIndex : undefined,
mode === 'stake' ? maxAmountIn : undefined,
mode === 'stake' && selectedToken ? selectedToken.decimals : undefined
mode === 'stake' ? maxAmountIn : undefined
);
// Fetch burn swap amounts (for unstake mode, only when not redeeming all)
const { burnSwapAmounts, loading: burnSwapLoading, error: burnSwapError } = useBurnSwapAmounts(
const { burnSwapAmounts, loading: burnSwapLoading } = useBurnSwapAmounts(
mode === 'unstake' && !redeemAll ? selectedPool?.address : undefined,
mode === 'unstake' && !redeemAll ? maxAmountIn : undefined,
mode === 'unstake' && !redeemAll ? inputTokenIndex : undefined,
undefined, // lpTokenPrice - not needed for burnSwap custom calculation
mode === 'unstake' && !redeemAll && selectedToken ? selectedToken.decimals : undefined
);
// Check if calculated slippage exceeds 5%
const slippageExceedsLimit = useMemo(() => {
if (mode === 'stake' && swapMintAmounts?.calculatedSlippage !== undefined) {
return Math.abs(swapMintAmounts.calculatedSlippage) > 5;
}
return false;
}, [mode, swapMintAmounts]);
// Check if slippage is high (> 2%)
const slippageIsHigh = useMemo(() => {
if (mode === 'stake' && swapMintAmounts?.calculatedSlippage !== undefined) {
return Math.abs(swapMintAmounts.calculatedSlippage) > 2;
}
if (mode === 'unstake' && !redeemAll && burnSwapAmounts?.calculatedSlippage !== undefined) {
return Math.abs(burnSwapAmounts.calculatedSlippage) > 2;
}
return false;
}, [mode, swapMintAmounts, burnSwapAmounts, redeemAll]);
// Check if unstake slippage exceeds 5%
const unstakeSlippageExceedsLimit = useMemo(() => {
if (mode === 'unstake' && !redeemAll && burnSwapAmounts?.calculatedSlippage !== undefined) {
return Math.abs(burnSwapAmounts.calculatedSlippage) > 5;
}
return false;
}, [mode, burnSwapAmounts, redeemAll]);
console.log('burn swap maounts', burnSwapAmounts)
// Fetch burn amounts (for unstake mode when redeeming all)
const { burnAmounts, loading: burnAmountsLoading } = useBurnAmounts(
@@ -426,7 +369,7 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
<span className="font-medium">{pool.symbol}</span>
<span className="text-xs text-muted-foreground">{pool.name}</span>
</div>
{(pool.price || pool.tvl || pool.apy) && (
{(pool.price || pool.tvl) && (
<div className="flex flex-col items-end">
{pool.price && (
<span className="text-sm font-medium text-muted-foreground">{pool.price}</span>
@@ -434,9 +377,6 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
{pool.tvl && (
<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>
@@ -505,7 +445,7 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
</div>
<span className="text-xs text-muted-foreground">{pool.name}</span>
</div>
{!pool.isKilled && (pool.price || pool.tvl || pool.apy) && (
{!pool.isKilled && (pool.price || pool.tvl) && (
<div className="flex flex-col items-end">
{pool.price && (
<span className="text-sm font-medium text-muted-foreground">{pool.price}</span>
@@ -513,9 +453,6 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
{pool.tvl && (
<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>
@@ -732,58 +669,6 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
</div>
)}
{/* Error messages for output zero */}
{mode === 'stake' && swapMintError && stakeAmount && (
<div className="px-4 py-3 bg-destructive/10 border border-destructive/20 rounded-lg">
<p className="text-sm text-destructive font-medium"> Cannot Process Stake</p>
<p className="text-xs text-destructive/80 mt-1">
{swapMintError}
</p>
</div>
)}
{mode === 'unstake' && !redeemAll && burnSwapError && stakeAmount && (
<div className="px-4 py-3 bg-destructive/10 border border-destructive/20 rounded-lg">
<p className="text-sm text-destructive font-medium"> Cannot Process Unstake</p>
<p className="text-xs text-destructive/80 mt-1">
{burnSwapError}
</p>
</div>
)}
{/* Slippage warnings - consolidated for both stake and unstake modes */}
{mode === 'stake' && !swapMintError && slippageExceedsLimit && swapMintAmounts?.calculatedSlippage !== undefined && (
<SlippageWarning
slippage={swapMintAmounts.calculatedSlippage}
action="stake"
isError={true}
/>
)}
{mode === 'stake' && !swapMintError && !slippageExceedsLimit && slippageIsHigh && swapMintAmounts?.calculatedSlippage !== undefined && (
<SlippageWarning
slippage={swapMintAmounts.calculatedSlippage}
action="stake"
isError={false}
/>
)}
{mode === 'unstake' && !redeemAll && !burnSwapError && unstakeSlippageExceedsLimit && burnSwapAmounts?.calculatedSlippage !== undefined && (
<SlippageWarning
slippage={burnSwapAmounts.calculatedSlippage}
action="unstake"
isError={true}
/>
)}
{mode === 'unstake' && !redeemAll && !burnSwapError && !unstakeSlippageExceedsLimit && slippageIsHigh && burnSwapAmounts?.calculatedSlippage !== undefined && (
<SlippageWarning
slippage={burnSwapAmounts.calculatedSlippage}
action="unstake"
isError={false}
/>
)}
{/* Burn Swap Amounts Display (Unstake Mode) */}
{mode === 'unstake' && !redeemAll && burnSwapAmounts && selectedToken && !isAmountExceedingBalance && (
<div className="px-4 py-3 bg-muted/30 rounded-lg space-y-2">
@@ -832,10 +717,10 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) {
!selectedPool ||
isAmountExceedingBalance ||
(mode === 'stake'
? (!selectedToken || isSwapMinting || slippageExceedsLimit || !!swapMintError)
? (!selectedToken || isSwapMinting)
: (redeemAll
? isBurning
: (!selectedToken || inputTokenIndex === undefined || isBurnSwapping || unstakeSlippageExceedsLimit || !!burnSwapError)))
: (!selectedToken || inputTokenIndex === undefined || isBurnSwapping)))
}
>
{!isConnected

View File

@@ -6,19 +6,17 @@ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Button } from '@/components/ui/button';
import { ArrowDownUp, ChevronDown, Settings, CheckCircle, XCircle, Loader2 } from 'lucide-react';
import { useAccount, useChainId } from 'wagmi';
import { useAccount } from 'wagmi';
import { useTokenDetails, useGetPoolsByToken, type TokenDetails } from '@/hooks/usePartyPlanner';
import { useSwapAmounts, useSwap, selectBestSwapRoute, type ActualSwapAmounts } from '@/hooks/usePartyPool';
import { formatUnits, parseUnits } from 'viem';
import { SwapReviewModal } from './swap-review-modal';
import UniswapQuote from './uniswap-quote';
type TransactionStatus = 'idle' | 'pending' | 'success' | 'error';
export function SwapForm() {
const { t } = useTranslation();
const { isConnected, address } = useAccount();
const chainId = useChainId();
const [fromAmount, setFromAmount] = useState('');
const [toAmount, setToAmount] = useState('');
const [selectedFromToken, setSelectedFromToken] = useState<TokenDetails | null>(null);
@@ -75,14 +73,6 @@ export function SwapForm() {
}
}, [selectedFromToken, fromAmount]);
// Check if calculated slippage exceeds 5%
const slippageExceedsLimit = useMemo(() => {
if (!swapAmounts || swapAmounts.length === 0 || swapAmounts[0].calculatedSlippage === undefined) {
return false;
}
return Math.abs(swapAmounts[0].calculatedSlippage) > 5;
}, [swapAmounts]);
// Initialize swap hook
const { executeSwap, estimateGas, isSwapping, gasEstimate, isEstimatingGas } = useSwap();
@@ -96,8 +86,6 @@ export function SwapForm() {
const swapResult = swapAmounts[0]; // Get the first (and should be only) result
const formattedAmount = formatUnits(swapResult.amountOut, selectedToToken.decimals);
setToAmount(formattedAmount);
} else {
setToAmount('');
}
}, [swapAmounts, selectedToToken, hasInsufficientBalance]);
@@ -313,7 +301,6 @@ export function SwapForm() {
onChange={(e) => setToAmount(e.target.value)}
className="text-2xl h-16"
disabled={!selectedFromToken}
readOnly
/>
<div className="relative min-w-[160px] space-y-1" ref={toDropdownRef}>
<Button
@@ -383,42 +370,18 @@ export function SwapForm() {
</div>
)}
{/* Error message for slippage exceeding 5% */}
{slippageExceedsLimit && (
<div className="px-4 py-3 bg-destructive/10 border border-destructive/20 rounded-lg">
<p className="text-sm text-destructive font-medium"> Slippage Exceeds 5%</p>
<p className="text-xs text-destructive/80 mt-1">
The estimated slippage for this swap is {Math.abs(swapAmounts![0].calculatedSlippage!).toFixed(2)}%.
We cannot process this swap as you may lose too much money due to the high slippage.
</p>
</div>
)}
{/* High slippage warning - show if calculated slippage exceeds max slippage but is under 5% */}
{!slippageExceedsLimit && swapAmounts && swapAmounts.length > 0 && swapAmounts[0].calculatedSlippage !== undefined && (
Math.abs(swapAmounts[0].calculatedSlippage) > currentSlippage
{/* High slippage warning - show if calculated slippage exceeds max slippage OR 5% */}
{swapAmounts && swapAmounts.length > 0 && swapAmounts[0].calculatedSlippage !== undefined && (
Math.abs(swapAmounts[0].calculatedSlippage) > Math.max(currentSlippage, 5)
) && (
<div className="px-4 py-3 bg-yellow-500/10 border border-yellow-500/20 rounded-lg">
<p className="text-sm text-yellow-600 dark:text-yellow-500 font-medium"> Slippage Exceeds Your Tolerance</p>
<p className="text-sm text-yellow-600 dark:text-yellow-500 font-medium"> High Slippage Warning</p>
<p className="text-xs text-yellow-600/80 dark:text-yellow-500/80 mt-1">
The estimated slippage for this swap is {Math.abs(swapAmounts[0].calculatedSlippage).toFixed(2)}%, which exceeds your maximum slippage setting of {currentSlippage}%. This swap may result in less favorable pricing than expected due to low liquidity.
The estimated slippage for this swap is {Math.abs(swapAmounts[0].calculatedSlippage).toFixed(2)}%. You may lose money due to low liquidity in this pool.
</p>
</div>
)}
{/*/!* Uniswap Quote - Hidden (under construction) *!/*/}
{/*{false && fromAmount && selectedFromToken && selectedToToken && (*/}
{/* <UniswapQuote*/}
{/* amountIn={fromAmount}*/}
{/* tokenInAddress={selectedFromToken.address}*/}
{/* tokenOutAddress={selectedToToken.address}*/}
{/* tokenInDecimals={selectedFromToken.decimals}*/}
{/* tokenOutDecimals={selectedToToken.decimals}*/}
{/* tokenOutSymbol={selectedToToken.symbol}*/}
{/* chainId={chainId || 1}*/}
{/* />*/}
{/*)}*/}
{/* Gas Estimate, Slippage, and Fees */}
{isConnected && fromAmount && toAmount && (
<div className="px-4 py-2 bg-muted/30 rounded-lg space-y-2">
@@ -457,14 +420,12 @@ export function SwapForm() {
<Button
className="w-full h-14 text-lg"
onClick={() => setIsReviewModalOpen(true)}
disabled={!isConnected || !fromAmount || !toAmount || !!poolsError || hasInsufficientBalance || slippageExceedsLimit}
disabled={!isConnected || !fromAmount || !toAmount || !!poolsError || hasInsufficientBalance}
>
{!isConnected
? t('swap.connectWalletToSwap')
: hasInsufficientBalance
? 'Insufficient Balance'
: slippageExceedsLimit
? 'Slippage Too High'
: 'Review'}
</Button>
</CardContent>
@@ -508,7 +469,7 @@ export function SwapForm() {
</span>
</div>
<p className="text-xs text-muted-foreground mt-2">
You will be warned if the slippage exceeds this setting, and you can choose whether to proceed with the trade.
Your transaction will revert if the price changes unfavorably by more than this percentage.
</p>
</div>
</div>

View File

@@ -4,16 +4,18 @@ export default function TosCard() {
return (
<div className="max-w-5xl mx-auto p-5">
<div className="bg-card rounded-lg shadow-md p-6 border">
<h1 className="text-2xl font-semibold text-center mb-4">Terms of Service</h1>
<h1 className="text-2xl font-semibold text-center mb-4">Dexorder Terms of Service</h1>
{/* MAKE SURE TO UPDATE THE VERSION VARIABLE AS WELL */}
<p className="text-center mb-4">Last Updated November 18, 2024</p>
<div className="mb-4 leading-relaxed">
Please read these Terms of Service (the "<b>Terms</b>")
Please read these Terms of Service (the "<b>Terms</b>") and our{' '}
<a href="https://dexorder.com/privacy-policy" target="dexorder">
Privacy Policy
</a>{' '}
carefully because they govern your use of the website (and all subdomains and subpages
thereon) located at liquidity.party, including without limitation the subdomains
app.liquidity.party and www.liquidity.party (collectively, the "<b>Site</b>"), and the
Liquidity Party
thereon) located at dexorder.com, including without limitation the subdomains
app.dexorder.com and www.dexorder.com (collectively, the "<b>Site</b>"), and the Dexorder
web application graphical user interface and any other services accessible via the Site
(together with the Site, web application, and other services, collectively, the "
<b>Dexorder Service</b>") offered by Dexorder Trading Services Ltd. ("<b>Dexorder</b>," "
@@ -43,14 +45,29 @@ export default function TosCard() {
<div className="mb-4 leading-relaxed">
(a) The Dexorder Service allows you to access an online web application graphical user
interface (the "<b>App</b>") which enables you to interact with a protocol consisting of a
set of smart contracts (the "<b>Protocol</b>").
You may use the <b>App</b> to send signals to, interact with, and initiate actions on the
decentralized exchange ("<b>DEX</b>").
set of smart contracts (the "<b>Smart Contracts</b>") and to create and interact with a
user controlled smart contract involving digital assets over which only you have upgrade
authority (a "<b>Vault</b>" and together with the Smart Contracts, the "<b>Protocol</b>").
You may use your Vault to send signals to, interact with, and initiate actions on third
party smart contract blockchain protocols ("<b>Interactions</b>") operating on
decentralized exchanges (e.g., Uniswap) ("<b>DEXs</b>"). Certain Interactions may require
threshold parameters to be met and that a third party transmit an oracle related activation
signal (the "<b>Activation Signal</b>") to the Vault in order to effectuate your commands
(such party being an "<b>Oracle</b>"). Dexorder may in the ordinary course of events be an
Oracle ("<b>Dexorder Oracle</b>"), but bears no obligation nor promise to do so on an
ongoing basis, and you may send such Activation Signal yourself or utilize a third-party
Oracle to do so. Further, Dexorder is not and does not offer a digital wallet, and has no
custody or control over your digital wallet, which is never accessible by Dexorder, and only
users, and not Dexorder, may provide or withdraw tokens to be held by Vault. From time to
time, the Services may include making recommendations with respect to technical changes that
only you may accept and implement.
</div>
<div className="mb-4 leading-relaxed">
(b) <b>Interface</b>. The Dexorder Service provides you with access to the Protocol.
All information provided in
(b) <b>Interface</b>. The Dexorder Service provides you with access to the Protocol, which
is a user controlled, non-custodial protocol, upgradeable only by your actions and consent,
deployed on the blockchains indicated on our Site, and provides information and interaction
capabilities with other blockchain related service providers. All information provided in
connection with your access and use of the Dexorder Service is for informational purposes
only. You should not take, or refrain from taking, any action based on any information
contained on the Dexorder Service or any other information that we make available at any
@@ -191,8 +208,12 @@ export default function TosCard() {
<div className="mb-4 leading-relaxed ml-8">
(i) Subject to your compliance with these Terms, Dexorder will use its commercially
reasonable efforts to provide you with access to the Dexorder Service and to cause your
Interactions to be executed on the Protocol, however from time to time the Site and
the Dexorder Service may
Interactions to be executed on the applicable DEX in accordance with Dexorder's Execution
Policy located at{' '}
<a href="https://dexorder.com/execution-policy">
https://dexorder.com/execution-policy
</a>{' '}
("<b>Execution Policy</b>"), however from time to time the Site and the Dexorder Service may
be inaccessible or inoperable for any reason, including, without limitation: (a) if an
Interaction repeatedly fails to be executed (such as due to an error in Interaction
execution or a malfunction in the Dexorder Service); (b) equipment malfunctions; (c)
@@ -200,7 +221,8 @@ export default function TosCard() {
contractors may undertake from time to time; (d) causes beyond Dexorder's control or that
Dexorder could not reasonably foresee; (e) disruptions and temporary or permanent
unavailability of underlying blockchain infrastructure; (f) unavailability of third-party
service providers or external partners for any reason.
service providers or external partners for any reason; or (g) an Activation Signal not being
sent.
</div>
<div className="mb-4 leading-relaxed ml-8">
(ii) the Site and the Dexorder Service may evolve, which means Dexorder may apply changes,
@@ -223,9 +245,11 @@ export default function TosCard() {
<h2 className="text-xl font-semibold mt-6 mb-4">6. Interactions; Fees</h2>
<div className="mb-4 leading-relaxed">
(a) <u>Interactions</u>. In order to effectuate Interactions, you may need to transfer
digital assets (e.g., tokens). You acknowledge that you may use the Dexorder
Services to process and cause Interactions to operate on the Protocol.
Dexorder is an interface to that smart contract, and does not offer a digital
digital assets (e.g., tokens) to the Vault. You acknowledge that you may use the Dexorder
Services to process and cause Interactions to be operate with an applicable DEX, including
without limitation the transfer of digital assets via the DEX in accordance with the
Interaction. For clarity, the Vault is a smart contract automatically controlled by the
blockchain. Dexorder is an interface to that smart contract, and does not offer a digital
wallet and has no custody or control over your digital wallet or any digital assets or
cryptocurrency, which is never accessible by Dexorder.
</div>
@@ -233,8 +257,8 @@ export default function TosCard() {
(b) <u>Fees</u>.
</div>
<div className="mb-4 leading-relaxed ml-8">
(i) Dexorder charges fees for usage of the Dexorder Services at the time of user
Interactions ("<b>Fees</b>"). You agree to pay all applicable Fees to Dexorder, in
(i) Dexorder charges fees upfront for usage of the Dexorder Services at the time of user
Interactions ("<b>Fees</b>"). You agree to pay all applicable Fees upfront to Dexorder, in
the amounts communicated or presented to you via the Dexorder Service in connection with
usage of the Dexorder Service. Each party shall be responsible for all Taxes imposed on its
income or property.
@@ -392,7 +416,8 @@ export default function TosCard() {
<h2 className="text-xl font-semibold mt-6 mb-4">9. Links to Third Party Websites or Resources</h2>
<div className="mb-4 leading-relaxed">
The Dexorder Service may allow you to access third-party websites, integrations, or other
resources, including the Protocol (collectively, "<b>Third Party Resources</b>"). We
resources, including the DEX, services providing Activation Signals, and any bridge between
the DEX and any third party protocols (collectively, "<b>Third Party Resources</b>"). We
provide access only as a convenience and are not responsible for the content, products or
services on or available from those resources or links displayed on such websites. You
acknowledge sole responsibility for and assume all risk arising from, your use of any
@@ -415,7 +440,7 @@ export default function TosCard() {
<div className="mb-4 leading-relaxed">
THE DEXORDER SERVICE (INCLUDING WITHOUT LIMITATION THE VAULT) AND ANY CONTENT CONTAINED
THEREIN, AS WELL AS THE PROTOCOL, THE DEXORDER ORACLE, AND ANY ASSOCIATED PROTOCOL OR
BLOCKCHAIN MESSAGING FUNCTIONALITY UNDERLYING THE DEXORDER
BLOCKCHAIN MESSAGING FUNCTIONALITY SUCH AS ACTIVATION SIGNALS UNDERLYING THE DEXORDER
SERVICE (TOGETHER, THE "<b>UTILITIES</b>"), ARE PROVIDED "AS IS," WITHOUT WARRANTY OF ANY
KIND. WITHOUT LIMITING THE FOREGOING, WE EXPLICITLY DISCLAIM ANY IMPLIED WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT AND NON-INFRINGEMENT, AND
@@ -451,6 +476,25 @@ export default function TosCard() {
USE OF VIRUSES, PHISHING, BRUTEFORCING OR OTHER MEANS OF ATTACK AGAINST ANY BLOCKCHAIN
NETWORK UNDERLYING THE UTILITIES.
</div>
<div className="mb-4 leading-relaxed">
THE UTILITIES MAY INCLUDE THE PLACEMENT OR EXECUTION OF AN ACTIVATION SIGNAL TO A USER VAULT
WITH RESPECT TO USER DEFINED INTERACTIONS (E.G., VIA TRANSMITTING AN ACTIVATION SIGNAL WHICH
TRIGGERS INTERACTION EXECUTION), HOWEVER, ANYONE, INCLUDING YOU OR ANY THIRD PARTY, MAY
CAUSE AN INTERACTION TO BE EXECUTED (SUCH AS BY TRANSMITTING THE APPLICABLE ACTIVATION
SIGNAL), AND NOTWITHSTANDING ANYTHING TO THE CONTRARY IN THESE TERMS, DEXORDER DOES NOT
GUARANTEE THE PLACEMENT OR EXECUTION OR ANY INTERACTION, INCLUDING WITHOUT LIMITATION THAT
DEXORDER WILL TRANSMIT ANY PARTICULAR ACTIVATION SIGNAL TO TRIGGER EXECUTION OF ANY
INTERACTION, OR THAT AN INTERACTION WILL OTHERWISE BE PROPERLY CARRIED OUT PURSUANT TO A
VAULT. YOU ACKNOWLEDGE AND AGREE THAT YOU WILL NOT RELY ON THE UTILITIES TO CARRY OUT
PLACEMENTS OR EXECUTIONS OF INTERACTIONS. IF ANY PARTICULAR INTERACTION THAT IS PLACED IS
NOT EXECUTED BY DEXORDER IN A TIMELY MANNER, YOU MAY CAUSE SUCH INTERACTION TO BE EXECUTED
YOURSELF OR BY ENGAGING A THIRD PARTY SERVICE PROVIDER TO DO SO (E.G., BY TRANSMITTING THE
APPLICABLE ACTIVATION SIGNAL YOURSELF OR ENGAGING A THIRD PARTY TO DO SO). YOU ACCEPT THE
INHERENT RISK THAT ANY PARTICULAR INTERACTION MAY NOT BE EXECUTED, INCLUDING WITHOUT
LIMITATION DUE TO BAD ACTORS OR THE MALFUNCTION OF THE UTILITIES, AND DEXORDER HEREBY
DISCLAIMS ANY AND ALL LIABILITY AND RESPONSIBILITY IN CONNECTION WITH THE PLACEMENT OR
EXECUTION OF INTERACTIONS AND THE VAULT.
</div>
<div className="mb-4 leading-relaxed">
THE UTILITIES MAY NOT BE AVAILABLE DUE TO ANY NUMBER OF FACTORS INCLUDING, BUT NOT LIMITED
TO, PERIODIC SYSTEM MAINTENANCE, SCHEDULED OR UNSCHEDULED, ACTS OF GOD, UNAUTHORIZED ACCESS,

View File

@@ -1,248 +0,0 @@
'use client';
import { useState, useEffect } from 'react';
import { parseUnits, formatUnits } from 'viem';
interface UniswapQuoteProps {
amountIn: string;
tokenInAddress: string | null;
tokenOutAddress: string | null;
tokenInDecimals: number;
tokenOutDecimals: number;
tokenOutSymbol: string;
chainId: number;
}
interface TokenPrices {
[key: string]: number;
}
export default function UniswapQuote({
amountIn,
tokenInAddress,
tokenOutAddress,
tokenInDecimals,
tokenOutDecimals,
tokenOutSymbol,
chainId
}: UniswapQuoteProps) {
const [quote, setQuote] = useState<any>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
const [prices, setPrices] = useState<TokenPrices | null>(null);
// Only show on mainnet
if (chainId !== 1) {
return null;
}
// Don't fetch quote if tokens aren't selected
if (!tokenInAddress || !tokenOutAddress) {
return null;
}
// Fetch token prices from CoinGecko using contract addresses
useEffect(() => {
if (!tokenInAddress || !tokenOutAddress) return;
const fetchPrices = async () => {
try {
// Fetch both token prices separately using the correct endpoint
const [tokenInResponse, tokenOutResponse] = await Promise.all([
fetch(`https://api.coingecko.com/api/v3/simple/token_price/ethereum?contract_addresses=${tokenInAddress}&vs_currencies=usd`),
fetch(`https://api.coingecko.com/api/v3/simple/token_price/ethereum?contract_addresses=${tokenOutAddress}&vs_currencies=usd`)
]);
const tokenInData = await tokenInResponse.json();
const tokenOutData = await tokenOutResponse.json();
const tokenInPrice = tokenInData[tokenInAddress.toLowerCase()]?.usd || 0;
const tokenOutPrice = tokenOutData[tokenOutAddress.toLowerCase()]?.usd || 0;
setPrices({
tokenIn: tokenInPrice,
tokenOut: tokenOutPrice
});
console.log('Token prices:', { tokenInPrice, tokenOutPrice, tokenInData, tokenOutData });
} catch (err) {
console.error('Failed to fetch prices:', err);
}
};
fetchPrices();
// Refresh prices every 30 seconds
const interval = setInterval(fetchPrices, 30000);
return () => clearInterval(interval);
}, [tokenInAddress, tokenOutAddress]);
const getQuote = async () => {
if (!amountIn || parseFloat(amountIn) <= 0) {
setError('Please enter a valid amount');
return;
}
setLoading(true);
setError('');
try {
// Convert amount to smallest unit based on token decimals using viem
const amountInSmallestUnit = parseUnits(amountIn, tokenInDecimals).toString();
const response = await fetch('https://api.uniswap.org/v2/quote', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Origin': 'https://app.uniswap.org'
},
body: JSON.stringify({
amount: amountInSmallestUnit,
tokenIn: tokenInAddress,
tokenInChainId: chainId,
tokenOut: tokenOutAddress,
tokenOutChainId: chainId,
type: 'EXACT_INPUT',
configs: [
{
protocols: ['V2', 'V3', 'V4'],
enableUniversalRouter: true,
routingType: 'CLASSIC'
}
]
})
});
if (!response.ok) throw new Error('Failed to fetch quote');
const data = await response.json();
console.log('Uniswap Quote:', data);
setQuote(data);
} catch (err: any) {
setError(err.message);
} finally {
setLoading(false);
}
};
const formatTokenAmount = (amount: string) => {
const formatted = formatUnits(BigInt(amount), tokenOutDecimals);
return parseFloat(formatted).toLocaleString('en-US', {
maximumFractionDigits: tokenOutDecimals >= 18 ? 0 : 6
});
};
const getQuoteAmount = () => {
if (!quote) return '0';
// Handle nested quote structure
return quote.quote?.quote || quote.quote || '0';
};
const calculateCostBreakdown = () => {
if (!quote || !prices || !prices.tokenIn) return null;
const tokenAmount = parseFloat(amountIn);
const tradeValueUSD = tokenAmount * prices.tokenIn;
// Access nested quote object
const quoteData = quote.quote || quote;
// 1. Gas Cost
const gasCostUSD = parseFloat(quoteData.gasUseEstimateUSD || '0');
// 2. Uniswap UX Fee (0.25%)
const uniswapFeePercent = 0.25;
const uniswapFeeUSD = (uniswapFeePercent / 100) * tradeValueUSD;
const totalCostUSD = gasCostUSD + uniswapFeeUSD;
console.log('Cost breakdown calc:', {
gasCostUSD,
uniswapFeeUSD,
totalCostUSD,
tradeValueUSD,
quoteData
});
return {
gasCostUSD,
uniswapFeePercent,
uniswapFeeUSD,
totalCostUSD,
tradeValueUSD
};
};
const costBreakdown = calculateCostBreakdown();
return (
<div className="w-full max-w-md p-4">
<button
onClick={getQuote}
disabled={loading}
className="w-full bg-blue-500 hover:bg-blue-600 disabled:bg-gray-400 text-white font-semibold py-2 px-4 rounded"
>
{loading ? 'Getting Quote...' : 'Get Quote'}
</button>
{error && (
<div className="mt-3 p-3 bg-red-50 text-red-700 rounded text-sm">
{error}
</div>
)}
{quote && costBreakdown && (
<div className="mt-4 space-y-3">
<div className="p-3 bg-gray-50 rounded">
<div className="text-sm text-gray-600">You Get ({tokenOutSymbol})</div>
<div className="text-xl font-bold">
{formatTokenAmount(getQuoteAmount())}
</div>
</div>
{/* Total Costs Breakdown */}
<div className="p-4 bg-blue-50 rounded-lg space-y-3">
<h3 className="font-semibold text-lg text-gray-800">Total Costs Breakdown</h3>
{/* 1. Gas Cost */}
<div className="space-y-1">
<div className="flex justify-between items-center">
<span className="text-sm font-medium text-gray-700">1. Gas Cost (Network Fee)</span>
<span className="text-sm font-bold text-gray-900">
${costBreakdown.gasCostUSD.toFixed(2)} USD
</span>
</div>
</div>
{/* 2. Uniswap UX Fee */}
<div className="space-y-1">
<div className="flex justify-between items-center">
<span className="text-sm font-medium text-gray-700">2. Uniswap UX Fee</span>
<span className="text-sm font-bold text-gray-900">
{costBreakdown.uniswapFeePercent}%
</span>
</div>
<div className="text-xs text-gray-600 pl-4">
${costBreakdown.uniswapFeeUSD.toFixed(2)} USD
</div>
</div>
{/* Total */}
<div className="pt-2 border-t border-gray-300">
<div className="flex justify-between items-center">
<span className="text-sm font-bold text-gray-800">Total Estimated Cost</span>
<span className="text-base font-bold text-red-600">
${costBreakdown.totalCostUSD.toFixed(2)} USD
</span>
</div>
</div>
{/* Trade Value Reference */}
<div className="text-xs text-gray-500 text-center pt-1">
Trade Value: ${costBreakdown.tradeValueUSD.toFixed(2)} USD
</div>
</div>
</div>
)}
</div>
);
}

View File

@@ -170,12 +170,12 @@ const IPartyInfoABI = [
"internalType": "contract IPartyPool"
},
{
"name": "inputTokenIndex",
"name": "baseTokenIndex",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "outputTokenIndex",
"name": "quoteTokenIndex",
"type": "uint256",
"internalType": "uint256"
}
@@ -183,96 +183,8 @@ const IPartyInfoABI = [
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"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"
"type": "int128",
"internalType": "int128"
}
],
"stateMutability": "view"
@@ -316,6 +228,50 @@ const IPartyInfoABI = [
],
"stateMutability": "view"
},
{
"type": "function",
"name": "swapToLimitAmounts",
"inputs": [
{
"name": "pool",
"type": "address",
"internalType": "contract IPartyPool"
},
{
"name": "inputTokenIndex",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "outputTokenIndex",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "limitPrice",
"type": "int128",
"internalType": "int128"
}
],
"outputs": [
{
"name": "amountIn",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "amountOut",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "inFee",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "working",

View File

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

View File

@@ -26,13 +26,6 @@ const IPartyPoolABI = [
],
"stateMutability": "view"
},
{
"type": "function",
"name": "acceptOwnership",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "allProtocolFeesOwed",
@@ -126,19 +119,6 @@ const IPartyPoolABI = [
],
"stateMutability": "view"
},
{
"type": "function",
"name": "balances",
"inputs": [],
"outputs": [
{
"name": "",
"type": "uint256[]",
"internalType": "uint256[]"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "burn",
@@ -202,11 +182,6 @@ const IPartyPoolABI = [
"type": "uint256",
"internalType": "uint256"
},
{
"name": "minAmountOut",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "deadline",
"type": "uint256",
@@ -265,6 +240,30 @@ const IPartyPoolABI = [
],
"stateMutability": "view"
},
{
"type": "function",
"name": "fee",
"inputs": [
{
"name": "i",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "j",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "fees",
@@ -349,6 +348,19 @@ const IPartyPoolABI = [
],
"stateMutability": "payable"
},
{
"type": "function",
"name": "kappa",
"inputs": [],
"outputs": [
{
"name": "",
"type": "int128",
"internalType": "int128"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "kill",
@@ -378,11 +390,6 @@ const IPartyPoolABI = [
"type": "address",
"internalType": "address"
},
{
"name": "fundingSelector",
"type": "bytes4",
"internalType": "bytes4"
},
{
"name": "receiver",
"type": "address",
@@ -397,11 +404,6 @@ const IPartyPoolABI = [
"name": "deadline",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "cbData",
"type": "bytes",
"internalType": "bytes"
}
],
"outputs": [
@@ -452,19 +454,6 @@ const IPartyPoolABI = [
],
"stateMutability": "view"
},
{
"type": "function",
"name": "pendingOwner",
"inputs": [],
"outputs": [
{
"name": "",
"type": "address",
"internalType": "address"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "protocolFeeAddress",
@@ -491,6 +480,13 @@ const IPartyPoolABI = [
],
"stateMutability": "view"
},
{
"type": "function",
"name": "renounceOwnership",
"inputs": [],
"outputs": [],
"stateMutability": "nonpayable"
},
{
"type": "function",
"name": "swap",
@@ -526,9 +522,9 @@ const IPartyPoolABI = [
"internalType": "uint256"
},
{
"name": "minAmountOut",
"type": "uint256",
"internalType": "uint256"
"name": "limitPrice",
"type": "int128",
"internalType": "int128"
},
{
"name": "deadline",
@@ -565,9 +561,102 @@ const IPartyPoolABI = [
],
"stateMutability": "payable"
},
{
"type": "function",
"name": "swapAmounts",
"inputs": [
{
"name": "inputTokenIndex",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "outputTokenIndex",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "maxAmountIn",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "limitPrice",
"type": "int128",
"internalType": "int128"
}
],
"outputs": [
{
"name": "amountIn",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "amountOut",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "inFee",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "view"
},
{
"type": "function",
"name": "swapMint",
"inputs": [
{
"name": "payer",
"type": "address",
"internalType": "address"
},
{
"name": "receiver",
"type": "address",
"internalType": "address"
},
{
"name": "inputTokenIndex",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "maxAmountIn",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "deadline",
"type": "uint256",
"internalType": "uint256"
}
],
"outputs": [
{
"name": "amountInUsed",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "lpMinted",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "inFee",
"type": "uint256",
"internalType": "uint256"
}
],
"stateMutability": "payable"
},
{
"type": "function",
"name": "swapToLimit",
"inputs": [
{
"name": "payer",
@@ -590,20 +679,25 @@ const IPartyPoolABI = [
"internalType": "uint256"
},
{
"name": "maxAmountIn",
"name": "outputTokenIndex",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "minLpOut",
"type": "uint256",
"internalType": "uint256"
"name": "limitPrice",
"type": "int128",
"internalType": "int128"
},
{
"name": "deadline",
"type": "uint256",
"internalType": "uint256"
},
{
"name": "unwrap",
"type": "bool",
"internalType": "bool"
},
{
"name": "cbData",
"type": "bytes",
@@ -617,7 +711,7 @@ const IPartyPoolABI = [
"internalType": "uint256"
},
{
"name": "lpMinted",
"name": "amountOut",
"type": "uint256",
"internalType": "uint256"
},
@@ -938,25 +1032,6 @@ const IPartyPoolABI = [
],
"anonymous": false
},
{
"type": "event",
"name": "OwnershipTransferStarted",
"inputs": [
{
"name": "previousOwner",
"type": "address",
"indexed": true,
"internalType": "address"
},
{
"name": "newOwner",
"type": "address",
"indexed": true,
"internalType": "address"
}
],
"anonymous": false
},
{
"type": "event",
"name": "OwnershipTransferred",

View File

@@ -8,7 +8,7 @@ import IPartyPoolABI from '@/contracts/IPartyPoolABI';
import IPartyPoolViewerABI from '@/contracts/IPartyPoolViewerABI';
import IPartyInfoABI from '@/contracts/IPartyInfoABI';
import { ERC20ABI } from '@/contracts/ERC20ABI';
import { fetchPoolMetrics } from '@/lib/poolMetricsCache';
import { calculateSlippage } from './usePartyPool';
// Helper function to format large numbers with K, M, B suffixes
function formatTVL(value: number): string {
@@ -100,8 +100,6 @@ export interface SwapRoute {
poolAddress: `0x${string}`;
inputTokenIndex: number;
outputTokenIndex: number;
inputTokenDecimal: number;
outputTokenDecimal: number;
}
export interface AvailableToken {
@@ -199,75 +197,17 @@ export function useGetPoolsByToken(tokenAddress: `0x${string}` | undefined, offs
return;
}
// First, fetch all tokens from all working pools
const poolTokensContracts = workingPools.map(poolAddress => ({
address: poolAddress,
abi: IPartyPoolABI,
functionName: 'allTokens',
}));
const poolTokensResults = await publicClient.multicall({
contracts: poolTokensContracts as any,
allowFailure: true,
});
// Build a flat list of all unique token addresses we need to query
const uniqueTokenAddresses = new Set<`0x${string}`>();
uniqueTokenAddresses.add(tokenAddress); // Add input token
poolTokensResults.forEach((result) => {
if (result.status === 'success') {
const tokens = result.result as readonly `0x${string}`[];
tokens.forEach(token => uniqueTokenAddresses.add(token));
}
});
const tokenAddressesArray = Array.from(uniqueTokenAddresses);
// Build multicall for all token symbols and decimals
const tokenDataContracts = tokenAddressesArray.flatMap(addr => [
{
address: addr,
abi: ERC20ABI,
functionName: 'symbol',
},
{
address: addr,
abi: ERC20ABI,
functionName: 'decimals',
},
]);
const tokenDataResults = await publicClient.multicall({
contracts: tokenDataContracts as any,
allowFailure: true,
});
// Parse token data into a map
const tokenDataMap = new Map<string, { symbol: string | null; decimals: number | null }>();
for (let i = 0; i < tokenAddressesArray.length; i++) {
const symbolResult = tokenDataResults[i * 2];
const decimalsResult = tokenDataResults[i * 2 + 1];
tokenDataMap.set(tokenAddressesArray[i].toLowerCase(), {
symbol: symbolResult.status === 'success' ? (symbolResult.result as string) : null,
decimals: decimalsResult.status === 'success' ? Number(decimalsResult.result) : null,
});
}
// Map to store available tokens with their swap routes
const tokenRoutesMap = new Map<string, AvailableToken>();
// For each working pool, process tokens
for (let poolIdx = 0; poolIdx < workingPools.length; poolIdx++) {
const poolAddress = workingPools[poolIdx];
const poolTokensResult = poolTokensResults[poolIdx];
if (poolTokensResult.status !== 'success') {
console.error('Failed to fetch tokens for pool', poolAddress);
continue;
}
const tokensInPool = poolTokensResult.result as readonly `0x${string}`[];
// For each working pool, fetch all tokens and track indices
for (const poolAddress of workingPools) {
try {
const tokensInPool = await publicClient.readContract({
address: poolAddress,
abi: IPartyPoolABI,
functionName: 'allTokens',
}) as readonly `0x${string}`[];
// Find the input token index in this pool
const inputTokenIndex = tokensInPool.findIndex(
@@ -279,9 +219,6 @@ export function useGetPoolsByToken(tokenAddress: `0x${string}` | undefined, offs
continue;
}
const inputTokenData = tokenDataMap.get(tokenAddress.toLowerCase());
const inputTokenDecimal = inputTokenData?.decimals ?? null;
// Process each token in the pool
for (let outputTokenIndex = 0; outputTokenIndex < tokensInPool.length; outputTokenIndex++) {
const outputTokenAddress = tokensInPool[outputTokenIndex];
@@ -291,21 +228,18 @@ export function useGetPoolsByToken(tokenAddress: `0x${string}` | undefined, offs
continue;
}
const outputTokenData = tokenDataMap.get(outputTokenAddress.toLowerCase());
const outputTokenSymbol = outputTokenData?.symbol ?? null;
const outputTokenDecimal = outputTokenData?.decimals ?? null;
// Get the symbol of this token
const outputTokenSymbol = await publicClient.readContract({
address: outputTokenAddress,
abi: ERC20ABI,
functionName: 'symbol',
}).catch(() => null);
// Skip tokens with the same symbol as the selected token
if (!outputTokenSymbol || outputTokenSymbol === selectedTokenSymbol) {
continue;
}
// Skip tokens if decimals failed to load
if (inputTokenDecimal === null || outputTokenDecimal === null) {
console.error(`Failed to load decimals for token ${outputTokenAddress} or ${tokenAddress}`);
continue;
}
// Create or update the available token entry
const tokenKey = outputTokenAddress.toLowerCase();
if (!tokenRoutesMap.has(tokenKey)) {
@@ -321,10 +255,11 @@ export function useGetPoolsByToken(tokenAddress: `0x${string}` | undefined, offs
poolAddress,
inputTokenIndex,
outputTokenIndex,
inputTokenDecimal,
outputTokenDecimal,
});
}
} catch (err) {
console.error('Error fetching tokens from pool', poolAddress, err);
}
}
const availableTokensList = Array.from(tokenRoutesMap.values());
@@ -370,54 +305,55 @@ export function useTokenDetails(userAddress: `0x${string}` | undefined) {
return;
}
// Build multicall contracts array - 4 calls per token (name, symbol, decimals, balanceOf)
const contracts = tokens.flatMap((tokenAddress) => [
{
const details: TokenDetails[] = [];
// Make individual calls for each token
for (let i = 0; i < tokens.length; i++) {
const tokenAddress = tokens[i];
try {
const [name, symbol, decimals, balance] = await Promise.all([
publicClient.readContract({
address: tokenAddress,
abi: ERC20ABI,
functionName: 'name',
},
{
}).catch(() => 'Unknown'),
publicClient.readContract({
address: tokenAddress,
abi: ERC20ABI,
functionName: 'symbol',
},
{
}).catch(() => '???'),
publicClient.readContract({
address: tokenAddress,
abi: ERC20ABI,
functionName: 'decimals',
},
{
}).catch(() => 18),
publicClient.readContract({
address: tokenAddress,
abi: ERC20ABI,
functionName: 'balanceOf',
args: [userAddress],
},
}).catch(() => BigInt(0)),
]);
// Execute multicall
const results = await publicClient.multicall({
contracts: contracts as any,
allowFailure: true,
});
// Parse results
const details: TokenDetails[] = [];
for (let i = 0; i < tokens.length; i++) {
const baseIndex = i * 4;
const nameResult = results[baseIndex];
const symbolResult = results[baseIndex + 1];
const decimalsResult = results[baseIndex + 2];
const balanceResult = results[baseIndex + 3];
details.push({
address: tokens[i],
name: nameResult.status === 'success' ? (nameResult.result as string) : 'Unknown',
symbol: symbolResult.status === 'success' ? (symbolResult.result as string) : '???',
decimals: decimalsResult.status === 'success' ? Number(decimalsResult.result) : 18,
balance: balanceResult.status === 'success' ? (balanceResult.result as bigint) : BigInt(0),
address: tokenAddress,
name: name as string,
symbol: symbol as string,
decimals: Number(decimals),
balance: balance as bigint,
index: i,
});
} catch (err) {
// Add token with fallback values if individual call fails
details.push({
address: tokenAddress,
name: 'Unknown',
symbol: '???',
decimals: 18,
balance: BigInt(0),
index: i,
});
}
}
setTokenDetails(details);
@@ -445,7 +381,6 @@ export interface PoolDetails {
tokens: readonly `0x${string}`[];
price?: string; // Formatted price string
tvl?: string; // Formatted TVL string (e.g., "$1.2M")
apy?: string; // Formatted APY string (e.g., "3.25%")
isKilled: boolean; // Whether the pool has been killed
}
@@ -497,9 +432,6 @@ export function useGetAllPools(offset: number = 0, limit: number = 100) {
setPools(result);
// Fetch Substreams pool metrics (cached for 1 hour)
const poolMetricsData = await fetchPoolMetrics();
// Fetch details for each pool and check if it's working
const details: PoolDetails[] = [];
for (const poolAddress of result) {
@@ -531,15 +463,44 @@ export function useGetAllPools(offset: number = 0, limit: number = 100) {
// Fetch pool price and TVL (only for working pools)
let priceStr: string | undefined;
let tvlStr: string | undefined;
let apyStr: string | undefined;
if (isWorking) {
// Fetch token decimals and balance first (needed for both price and TVL)
// Fetch pool price (use first token as quote, index 0)
console.log('fetching pool price')
try {
const priceRaw = await publicClient.readContract({
address: partyInfoAddress as `0x${string}`,
abi: IPartyInfoABI,
functionName: 'poolPrice',
args: [poolAddress, BigInt(0)],
});
const price = BigInt(priceRaw as bigint | number);
if (price === 0n) {
priceStr = undefined;
} else {
// Convert Q64 format to decimal (price = priceValue / 2^64)
const Q64 = 2n ** 64n;
const isNegative = price < 0n;
const absPrice = isNegative ? -price : price;
const priceFloat = Number(absPrice) / Number(Q64);
const finalPrice = isNegative ? -priceFloat : priceFloat;
priceStr = `$${finalPrice.toFixed(4)}`;
}
} catch (err) {
console.error(`Error fetching pool price for ${poolAddress}:`, err);
priceStr = undefined;
}
// Calculate TVL (approximate by getting first token balance and doubling it)
try {
if (tokens && tokens.length > 0) {
const firstTokenAddress = tokens[0];
// Get token decimals, balance, and pool price in parallel
const [decimals, balance, priceRaw] = await Promise.all([
// Get token decimals and balance
const [decimals, balance] = await Promise.all([
publicClient.readContract({
address: firstTokenAddress as `0x${string}`,
abi: ERC20ABI,
@@ -551,47 +512,16 @@ export function useGetAllPools(offset: number = 0, limit: number = 100) {
functionName: 'balanceOf',
args: [poolAddress],
}) as Promise<bigint>,
publicClient.readContract({
address: partyInfoAddress as `0x${string}`,
abi: IPartyInfoABI,
functionName: 'poolPrice',
args: [poolAddress, BigInt(0)],
}),
]);
// Calculate pool price using actual token decimals
const price = BigInt(priceRaw as bigint | number);
if (price === 0n) {
priceStr = undefined;
} else {
// Convert Q64 format to decimal (price = priceValue / 2^64)
const Q64 = 2n ** 64n;
const priceFloat = Number(price) / Number(Q64);
// Adjust for token decimals (poolPrice assumes 18 decimals, adjust based on actual token decimals)
const finalPrice = priceFloat * (Math.pow(10, 18) / Math.pow(10, decimals));
priceStr = `$${finalPrice.toFixed(4)}`;
}
// Use Substreams TVL + APY if available; fall back to on-chain estimate
const metricKey = poolAddress.toLowerCase().replace('0x', '');
const metric = poolMetricsData[metricKey];
if (metric) {
const tvlAdjusted = Number(BigInt(metric.quoteTvl)) / Math.pow(10, decimals);
tvlStr = formatTVL(tvlAdjusted);
if (metric.quoteApyBps > 0) {
apyStr = `${(metric.quoteApyBps / 100).toFixed(2)}%`;
}
} else {
// Fall back to on-chain estimate
// Convert balance to float and double it for total TVL approximation
const tokenBalance = Number(balance) / Math.pow(10, decimals);
const approximateTVL = tokenBalance * tokens.length;
const approximateTVL = tokenBalance * 3;
tvlStr = formatTVL(approximateTVL);
}
}
} catch (err) {
console.error(`Error fetching pool price/TVL for ${poolAddress}:`, err);
priceStr = undefined;
console.error(`Error fetching TVL for ${poolAddress}:`, err);
tvlStr = undefined;
}
}
@@ -604,7 +534,6 @@ export function useGetAllPools(offset: number = 0, limit: number = 100) {
tokens: tokens as readonly `0x${string}`[],
price: priceStr,
tvl: tvlStr,
apy: apyStr,
isKilled: !isWorking,
});
} catch (err) {
@@ -651,7 +580,7 @@ export function useSwapMintAmounts(
poolAddress: `0x${string}` | undefined,
inputTokenIndex: number | undefined,
maxAmountIn: bigint | undefined,
inputTokenDecimals: number | undefined // Decimals of the input token
lpTokenPrice?: number // Market price of the LP token in decimal format
) {
const publicClient = usePublicClient();
const [mounted, setMounted] = useState(false);
@@ -672,11 +601,8 @@ export function useSwapMintAmounts(
}
const fetchSwapMintAmounts = async () => {
if (!publicClient) return;
// Early validation
if (inputTokenDecimals === undefined) {
setError('inputTokenDecimals is required but was undefined');
if (!publicClient) {
setLoading(false);
return;
}
@@ -684,7 +610,9 @@ export function useSwapMintAmounts(
setLoading(true);
setError(null);
// Get chain ID and contract address
const chainId = await publicClient.getChainId();
// @ts-ignore
const partyInfoAddress = (chainInfo as Record<string, { v1: { PartyInfo: string } }>)[chainId.toString()]?.v1?.PartyInfo;
if (!partyInfoAddress) {
@@ -693,6 +621,7 @@ export function useSwapMintAmounts(
return;
}
// Call swapMintAmounts function
const result = await publicClient.readContract({
address: partyInfoAddress as `0x${string}`,
abi: IPartyPoolViewerABI,
@@ -700,42 +629,27 @@ export function useSwapMintAmounts(
args: [poolAddress, BigInt(inputTokenIndex), maxAmountIn],
}) as readonly [bigint, bigint, bigint];
// Fetch and calculate pool price
let poolPrice: number | undefined;
// Calculate slippage if LP token price is provided
let calculatedSlippage: number | undefined;
if (lpTokenPrice !== undefined) {
try {
const poolPriceInt128 = await publicClient.readContract({
address: partyInfoAddress as `0x${string}`,
abi: IPartyInfoABI,
functionName: 'poolPrice',
args: [poolAddress, BigInt(inputTokenIndex)],
}) as bigint;
const basePrice = Number(poolPriceInt128) / (2 ** 64);
poolPrice = 1 / (basePrice * Math.pow(10, 18 - inputTokenDecimals));
// Calculate slippage
const decimalsMultiplier = Math.pow(10, inputTokenDecimals);
const lpMinted = Number(result[1]) / Math.pow(10, 18);
const amountIn = Number(result[0]) / decimalsMultiplier;
const fee = Number(result[2]) / decimalsMultiplier;
const swapPrice = lpMinted / (amountIn - fee);
calculatedSlippage = ((poolPrice - swapPrice) / poolPrice) * 100;
} catch (priceErr) {
console.error('Error fetching poolPrice or calculating slippage:', priceErr);
// For swapMint: output is result[0] (amountInUsed), input is maxAmountIn, fee is result[2]
calculatedSlippage = calculateSlippage(lpTokenPrice, result[0], maxAmountIn, result[2]);
console.log('swapMint calculatedSlippage', calculatedSlippage);
} catch (slippageErr) {
console.error(`Error calculating slippage for swapMint:`, slippageErr);
}
}
setSwapMintAmounts({
amountInUsed: result[0],
fee: result[2],
lpMinted: result[1],
fee: result[1],
lpMinted: result[2],
calculatedSlippage,
});
} catch (err) {
console.error('Error calling swapMintAmounts:', err);
const errorMessage = err instanceof Error ? err.message : 'Failed to fetch swap mint amounts';
setError(errorMessage);
setError(err instanceof Error ? err.message : 'Failed to fetch swap mint amounts');
setSwapMintAmounts(null);
} finally {
setLoading(false);
@@ -743,7 +657,7 @@ export function useSwapMintAmounts(
};
fetchSwapMintAmounts();
}, [publicClient, mounted, poolAddress, inputTokenIndex, maxAmountIn, inputTokenDecimals]);
}, [publicClient, mounted, poolAddress, inputTokenIndex, maxAmountIn, lpTokenPrice]);
return {
swapMintAmounts,
@@ -757,7 +671,8 @@ export function useBurnSwapAmounts(
poolAddress: `0x${string}` | undefined,
lpAmount: bigint | undefined,
inputTokenIndex: number | undefined,
tokenDecimals: number | undefined // Decimals of the output token
lpTokenPrice?: number, // Market price of the LP token in decimal format
tokenDecimals?: number // Decimals of the output token
) {
const publicClient = usePublicClient();
const [mounted, setMounted] = useState(false);
@@ -786,17 +701,31 @@ export function useBurnSwapAmounts(
try {
setLoading(true);
setError(null);
console.log('fetching swap amounts')
// Get chain ID and contract address
const chainId = await publicClient.getChainId();
// @ts-ignore
const partyInfoAddress = (chainInfo as Record<string, { v1: { PartyInfo: string } }>)[chainId.toString()]?.v1?.PartyInfo;
if (!partyInfoAddress) {
console.log('errores here')
setError('PartyInfo contract not found for current chain');
setBurnSwapAmounts(null);
return;
}
console.log('fetching swap amounts 2')
// Log inputs
console.log('🔍 burnSwapAmounts INPUTS:', {
chainId: chainId.toString(),
rpcUrl: publicClient.transport?.url || 'Unknown',
poolAddress,
lpAmount: lpAmount.toString(),
inputTokenIndex,
partyInfoAddress,
});
// Call burnSwapAmounts function - returns [amountOut, outFee]
const result = await publicClient.readContract({
address: partyInfoAddress as `0x${string}`,
@@ -805,6 +734,12 @@ export function useBurnSwapAmounts(
args: [poolAddress, lpAmount, BigInt(inputTokenIndex)],
}) as readonly [bigint, bigint];
// Log raw result
console.log('📊 burnSwapAmounts RAW RESULT:', {
resultArray: result,
amountOut: result[0].toString(),
outFee: result[1].toString(),
});
// Calculate slippage for burnSwap using poolPrice
let calculatedSlippage: number | undefined;
if (tokenDecimals !== undefined) {
@@ -818,8 +753,7 @@ export function useBurnSwapAmounts(
}) as bigint;
// Convert Q64 format to decimal (price = priceValue / 2^64)
let poolPrice = Number(poolPriceInt128) / 2 ** 64;
poolPrice = (poolPrice * Math.pow(10, 18 - tokenDecimals));
const marketPrice = Number(poolPriceInt128) / 2 ** 64;
// For burnSwap: swapPrice = (outAmount + fee) / lpAmount
// Need to apply decimal corrections: outAmount and fee are in token decimals, lpAmount is in 18 decimals
@@ -828,8 +762,21 @@ export function useBurnSwapAmounts(
const lpAmountDecimal = Number(lpAmount) / Math.pow(10, 18); // LP tokens have 18 decimals
const swapPrice = (outAmountDecimal + feeDecimal) / lpAmountDecimal;
calculatedSlippage = ((poolPrice - swapPrice) / poolPrice) * 100;
// Calculate slippage percentage: ((swapPrice - marketPrice) / marketPrice) * 100
calculatedSlippage = ((swapPrice - marketPrice) / marketPrice) * 100;
console.log('burnSwap slippage calculation:', {
marketPrice,
swapPrice,
calculatedSlippage,
outAmountDecimal,
feeDecimal,
lpAmountDecimal,
outAmount: result[0].toString(),
fee: result[1].toString(),
lpAmount: lpAmount.toString(),
});
} catch (slippageErr) {
console.error(`Error calculating slippage for burnSwap:`, slippageErr);
}
@@ -841,11 +788,17 @@ export function useBurnSwapAmounts(
calculatedSlippage,
};
// Log parsed result
console.log('✅ burnSwapAmounts PARSED:', {
amountOut: parsedAmounts.amountOut.toString(),
outFee: parsedAmounts.outFee.toString(),
calculatedSlippage: parsedAmounts.calculatedSlippage,
});
setBurnSwapAmounts(parsedAmounts);
} catch (err) {
console.error('Error calling burnSwapAmounts:', err);
const errorMessage = err instanceof Error ? err.message : 'Failed to fetch burn swap amounts';
setError(errorMessage);
setError(err instanceof Error ? err.message : 'Failed to fetch burn swap amounts');
setBurnSwapAmounts(null);
} finally {
setLoading(false);
@@ -853,7 +806,7 @@ export function useBurnSwapAmounts(
};
fetchBurnSwapAmounts();
}, [publicClient, mounted, poolAddress, lpAmount, inputTokenIndex, tokenDecimals]);
}, [publicClient, mounted, poolAddress, lpAmount, inputTokenIndex, lpTokenPrice, tokenDecimals]);
return {
burnSwapAmounts,

View File

@@ -21,14 +21,15 @@ const Q96 = 1n << 96n;
*/
export function calculateSlippage(
marketPrice: number,
swapOutputAmount: number,
swapInputAmount: number,
swapFee: number
swapOutputAmount: bigint,
swapInputAmount: bigint,
swapFee: bigint
): number {
// Calculate actual swap price with decimal correction
const swapPrice = swapOutputAmount / ((swapInputAmount) - (swapFee));
const swapPrice = Number(swapOutputAmount) / (Number(swapInputAmount) - Number(swapFee));
// Calculate slippage percentage: ((swapPrice - marketPrice) / marketPrice) * 100
const slippage = ((marketPrice - swapPrice) / marketPrice) * 100;
const slippage = ((swapPrice - marketPrice) / marketPrice) * 100;
return slippage;
}
@@ -120,6 +121,18 @@ export function useSwapAmounts(
setLoading(true);
const amountInTokenUnits = parseUnits(fromAmount, fromTokenDecimals);
// Calculate limit price based on slippage tolerance
// limitPrice in Q96 format = Q96 * (100 + slippage%) / 100
// This represents the maximum acceptable price ratio (1 + slippage%)
const slippageBasisPoints = BigInt(Math.floor(slippagePercent * 100)); // Convert to basis points (0.5% = 50)
const limitPrice = (Q96 * (10000n + slippageBasisPoints)) / 10000n;
console.log('Limit Price Calculation:', {
slippagePercent,
slippageBasisPoints: slippageBasisPoints.toString(),
limitPriceQ96: limitPrice.toString(),
Q96: Q96.toString(),
});
const results: SwapAmountResult[] = [];
const chainId = await publicClient.getChainId();
@@ -134,8 +147,8 @@ export function useSwapAmounts(
// Evaluate ALL routes for this token
for (const route of token.swapRoutes) {
try {
// Get swap amounts with NO LIMIT
const [swapInputAmount, swapOutputAmount, inFee] = await publicClient.readContract({
// Get swap amounts
const swapResult = await publicClient.readContract({
address: route.poolAddress,
abi: IPartyPoolABI,
functionName: 'swapAmounts',
@@ -143,10 +156,12 @@ export function useSwapAmounts(
BigInt(route.inputTokenIndex),
BigInt(route.outputTokenIndex),
amountInTokenUnits,
0n, // NO LIMIT
limitPrice,
],
}) as readonly [bigint, bigint, bigint];
const [amountIn, amountOut, fee] = swapResult;
// Get kappa for this pool
const kappa = await publicClient.readContract({
address: route.poolAddress,
@@ -158,6 +173,19 @@ export function useSwapAmounts(
let calculatedSlippage: number | undefined;
if (partyInfoAddress) {
try {
// Get swap amounts with NO LIMIT (0 means no price limit)
const [swapInputAmount, swapOutputAmount, inFee] = await publicClient.readContract({
address: route.poolAddress,
abi: IPartyPoolABI,
functionName: 'swapAmounts',
args: [
BigInt(route.inputTokenIndex),
BigInt(route.outputTokenIndex),
amountInTokenUnits,
0n, // NO LIMIT
],
}) as readonly [bigint, bigint, bigint];
// Get the current market price from PoolInfo
const priceInt128 = await publicClient.readContract({
address: partyInfoAddress,
@@ -166,17 +194,11 @@ export function useSwapAmounts(
args: [route.poolAddress, BigInt(route.inputTokenIndex), BigInt(route.outputTokenIndex)],
}) as bigint;
// Convert Q128 format to decimal (price = priceValue / 2^128)
// Then apply decimal conversion: 10^18 / 10^outputTokenDecimals
const priceQ128 = Number(priceInt128) / 2 ** 128;
const decimalAdjustment = 10 ** (route.outputTokenDecimal - route.inputTokenDecimal);
const marketPrice = priceQ128 / decimalAdjustment;
const swapOutputAmountDecimal = Number(swapOutputAmount) / 10 ** route.outputTokenDecimal;
const swapInputAmountDecimal = Number(swapInputAmount) / 10 ** route.inputTokenDecimal;
const swapFeeDecimal = Number(inFee) / 10 ** route.inputTokenDecimal;
// Calculate slippage using the reusable function
// Convert Q64 format to decimal (price = priceValue / 2^64)
const marketPrice = Number(priceInt128) / 2 ** 64;
calculatedSlippage = calculateSlippage(marketPrice, swapOutputAmountDecimal, swapInputAmountDecimal, swapFeeDecimal);
// Calculate slippage using the reusable function
calculatedSlippage = calculateSlippage(marketPrice, swapOutputAmount, swapInputAmount, inFee);
console.log('calculatedSlippage', calculatedSlippage)
} catch (slippageErr) {
console.error(`Error calculating slippage for ${token.symbol}:`, slippageErr);
@@ -186,9 +208,9 @@ export function useSwapAmounts(
routeResults.push({
tokenAddress: token.address,
tokenSymbol: token.symbol,
amountIn: swapInputAmount,
amountOut: swapOutputAmount,
fee: inFee,
amountIn,
amountOut,
fee,
poolAddress: route.poolAddress,
kappa,
inputTokenIndex: route.inputTokenIndex,
@@ -360,7 +382,9 @@ export function useSwap() {
await publicClient.waitForTransactionReceipt({ hash: approvalHash });
console.log('✅ Approval confirmed');
// STEP 2: Calculate deadline
// STEP 2: Calculate limit price and deadline
const slippageBasisPoints = BigInt(Math.floor(slippagePercent * 100));
const limitPrice = (Q96 * (10000n + slippageBasisPoints)) / 10000n;
const deadline = BigInt(Math.floor(Date.now() / 1000) + 1200);
console.log('🚀 Executing swap with params:', {
@@ -370,12 +394,12 @@ export function useSwap() {
inputTokenIndex,
outputTokenIndex,
maxAmountIn: maxAmountIn.toString(),
limitPrice: '0 (no limit)',
limitPrice: limitPrice.toString(),
deadline: deadline.toString(),
unwrap: false,
});
// STEP 3: Execute the swap transaction with no limit price
// STEP 3: Execute the swap transaction
const hash = await walletClient.writeContract({
address: poolAddress,
abi: IPartyPoolABI,
@@ -387,7 +411,7 @@ export function useSwap() {
BigInt(inputTokenIndex),
BigInt(outputTokenIndex),
maxAmountIn,
0n, // no limit price
limitPrice,
deadline,
false, // unwrap
'0x', // cbData (empty bytes)

View File

@@ -1,123 +0,0 @@
// 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;
}