Compare commits
3 Commits
main
...
f8120ed045
| Author | SHA1 | Date | |
|---|---|---|---|
| f8120ed045 | |||
| c49b4a6814 | |||
| 2638bcc02f |
11
.env-secret
Normal file
11
.env-secret
Normal 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
3
.gitignore
vendored
@@ -5,9 +5,6 @@
|
||||
/.pnp
|
||||
.pnp.js
|
||||
|
||||
*secret*
|
||||
.env
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
cast call 0x8f98B899F4135408Fe03228cE942Ad6BF8E40f22 \
|
||||
"working(address)" \
|
||||
0x3EDE1eE859A72aEc85ff04d305B6Ffe89f2Cb4eb \
|
||||
--rpc-url https://eth-sepolia.g.alchemy.com/v2/demo
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/sh
|
||||
|
||||
CHAIN_ID=${1:-1}
|
||||
CHAIN_ID=${1:-31337}
|
||||
|
||||
case "$CHAIN_ID" in
|
||||
"sepolia")
|
||||
|
||||
3196
package-lock.json
generated
3196
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
894
scripts/package-lock.json
generated
894
scripts/package-lock.json
generated
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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())
|
||||
);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user