'use client'; import { useState, useEffect } from 'react'; import { usePublicClient } from 'wagmi'; import chainInfo from '@/contracts/liqp-deployments.json'; import IPartyPlannerABI from '@/contracts/IPartyPlannerABI'; import IPartyPoolABI from '@/contracts/IPartyPoolABI'; import IPartyPoolViewerABI from '@/contracts/IPartyPoolViewerABI'; import IPartyInfoABI from '@/contracts/IPartyInfoABI'; import { ERC20ABI } from '@/contracts/ERC20ABI'; import { fetchPoolMetrics } from '@/lib/poolMetricsCache'; // Helper function to format large numbers with K, M, B suffixes function formatTVL(value: number): string { if (value >= 1_000_000_000) { return `$${(value / 1_000_000_000).toFixed(2)}B`; } else if (value >= 1_000_000) { return `$${(value / 1_000_000).toFixed(2)}M`; } else if (value >= 1_000) { return `$${(value / 1_000).toFixed(2)}K`; } else { return `$${value.toFixed(2)}`; } } export function useGetAllTokens(offset: number = 0, limit: number = 100) { const publicClient = usePublicClient(); const [mounted, setMounted] = useState(false); const [tokens, setTokens] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // Handle hydration for Next.js static export useEffect(() => { setMounted(true); }, []); useEffect(() => { if (!mounted) return; const fetchTokens = async () => { if (!publicClient) { setLoading(false); return; } try { setLoading(true); setError(null); // Get chain ID and contract address const chainId = await publicClient.getChainId(); // @ts-ignore const address = (chainInfo as Record)[chainId.toString()]?.v1?.PartyPlanner; if (!address) { setError('IPartyPlanner contract not found for current chain'); setTokens([]); return; } // Call getAllTokens function const result = await publicClient.readContract({ address: address as `0x${string}`, abi: IPartyPlannerABI, functionName: 'getAllTokens', args: [BigInt(offset), BigInt(limit)], }); setTokens(result); } catch (err) { console.error('Error calling getAllTokens:', err); setError(err instanceof Error ? err.message : 'Failed to fetch tokens'); } finally { setLoading(false); } }; fetchTokens(); }, [publicClient, mounted, offset, limit]); return { tokens, loading, error, isReady: mounted, }; } export interface TokenDetails { address: `0x${string}`; name: string; symbol: string; decimals: number; balance: bigint; index: number; } export interface SwapRoute { poolAddress: `0x${string}`; inputTokenIndex: number; outputTokenIndex: number; inputTokenDecimal: number; outputTokenDecimal: number; } export interface AvailableToken { address: `0x${string}`; symbol: string; swapRoutes: SwapRoute[]; } export function useGetPoolsByToken(tokenAddress: `0x${string}` | undefined, offset: number = 0, limit: number = 100) { const publicClient = usePublicClient(); const [mounted, setMounted] = useState(false); const [availableTokens, setAvailableTokens] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // Handle hydration for Next.js static export useEffect(() => { setMounted(true); }, []); useEffect(() => { if (!mounted || !tokenAddress) { setLoading(false); return; } const fetchPoolsFromTokens = async () => { if (!publicClient) { setLoading(false); return; } try { setLoading(true); setError(null); // Get chain ID and contract addresses const chainId = await publicClient.getChainId(); const plannerAddress = (chainInfo as Record)[chainId.toString()]?.v1?.PartyPlanner; const partyInfoAddress = (chainInfo as Record)[chainId.toString()]?.v1?.PartyInfo; if (!plannerAddress || !partyInfoAddress) { setError('IPartyPlanner or PartyInfo contract not found for current chain'); setAvailableTokens([]); return; } // Call getPoolsByToken function const poolsResult = await publicClient.readContract({ address: plannerAddress as `0x${string}`, abi: IPartyPlannerABI, functionName: 'getPoolsByToken', args: [tokenAddress, BigInt(offset), BigInt(limit)], }); // Get the symbol of the originally selected token const selectedTokenSymbol = await publicClient.readContract({ address: tokenAddress, abi: ERC20ABI, functionName: 'symbol', }).catch(() => null); if (!selectedTokenSymbol) { setAvailableTokens([]); return; } // Filter pools to only working ones const workingPools: `0x${string}`[] = []; for (const poolAddress of poolsResult) { try { const isWorking = await publicClient.readContract({ address: partyInfoAddress as `0x${string}`, abi: IPartyInfoABI, functionName: 'working', args: [poolAddress], }) as boolean; if (isWorking) { workingPools.push(poolAddress); } } catch (err) { console.error(`Error checking if pool ${poolAddress} is working:`, err); } } // If no working pools found, set error message if (workingPools.length === 0 && poolsResult.length > 0) { setError('This token is no longer supported. All pools containing this token are currently inactive.'); setAvailableTokens([]); return; } else if (workingPools.length === 0) { setError('No pools found for this token.'); setAvailableTokens([]); 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(); 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(); // 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}`[]; // Find the input token index in this pool const inputTokenIndex = tokensInPool.findIndex( (token) => token.toLowerCase() === tokenAddress.toLowerCase() ); if (inputTokenIndex === -1) { console.error('Input token not found in pool', poolAddress); 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]; // Skip if it's the same as the input token if (outputTokenIndex === inputTokenIndex) { continue; } const outputTokenData = tokenDataMap.get(outputTokenAddress.toLowerCase()); const outputTokenSymbol = outputTokenData?.symbol ?? null; const outputTokenDecimal = outputTokenData?.decimals ?? 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)) { tokenRoutesMap.set(tokenKey, { address: outputTokenAddress, symbol: outputTokenSymbol, swapRoutes: [], }); } // Add this swap route tokenRoutesMap.get(tokenKey)!.swapRoutes.push({ poolAddress, inputTokenIndex, outputTokenIndex, inputTokenDecimal, outputTokenDecimal, }); } } const availableTokensList = Array.from(tokenRoutesMap.values()); setAvailableTokens(availableTokensList); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to fetch pools and tokens'); } finally { setLoading(false); } }; fetchPoolsFromTokens(); }, [publicClient, mounted, tokenAddress, offset, limit]); return { availableTokens, loading, error, isReady: mounted, }; } export function useTokenDetails(userAddress: `0x${string}` | undefined) { const publicClient = usePublicClient(); const { tokens, loading: tokensLoading, isReady } = useGetAllTokens(); const [tokenDetails, setTokenDetails] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { if (!isReady || tokensLoading || !tokens || !publicClient || !userAddress) { setLoading(tokensLoading || !isReady); return; } const fetchTokenDetails = async () => { try { setLoading(true); setError(null); if (tokens.length === 0) { setTokenDetails([]); return; } // Build multicall contracts array - 4 calls per token (name, symbol, decimals, balanceOf) const contracts = tokens.flatMap((tokenAddress) => [ { address: tokenAddress, abi: ERC20ABI, functionName: 'name', }, { address: tokenAddress, abi: ERC20ABI, functionName: 'symbol', }, { address: tokenAddress, abi: ERC20ABI, functionName: 'decimals', }, { address: tokenAddress, abi: ERC20ABI, functionName: 'balanceOf', args: [userAddress], }, ]); // 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), index: i, }); } setTokenDetails(details); } catch (err) { setError(err instanceof Error ? err.message : 'Failed to fetch token details'); } finally { setLoading(false); } }; fetchTokenDetails(); }, [tokens, tokensLoading, publicClient, isReady, userAddress]); return { tokenDetails, loading, error, }; } export interface PoolDetails { address: `0x${string}`; name: string; symbol: string; 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 } export function useGetAllPools(offset: number = 0, limit: number = 100) { const publicClient = usePublicClient(); const [mounted, setMounted] = useState(false); const [pools, setPools] = useState(null); const [poolDetails, setPoolDetails] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); // Handle hydration for Next.js static export useEffect(() => { setMounted(true); }, []); useEffect(() => { if (!mounted) return; const fetchPools = async () => { if (!publicClient) { setLoading(false); return; } try { setLoading(true); setError(null); // Get chain ID and contract addresses const chainId = await publicClient.getChainId(); const plannerAddress = (chainInfo as Record)[chainId.toString()]?.v1?.PartyPlanner; const partyInfoAddress = (chainInfo as Record)[chainId.toString()]?.v1?.PartyInfo; if (!plannerAddress || !partyInfoAddress) { setError('IPartyPlanner or PartyInfo contract not found for current chain'); setPools([]); setPoolDetails([]); return; } // Call getAllPools function const result = await publicClient.readContract({ address: plannerAddress as `0x${string}`, abi: IPartyPlannerABI, functionName: 'getAllPools', args: [BigInt(offset), BigInt(limit)], }); 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) { try { const [name, symbol, tokens, isWorking] = await Promise.all([ publicClient.readContract({ address: poolAddress, abi: ERC20ABI, functionName: 'name', }).catch(() => 'Unknown Pool'), publicClient.readContract({ address: poolAddress, abi: ERC20ABI, functionName: 'symbol', }).catch(() => 'POOL'), publicClient.readContract({ address: poolAddress, abi: IPartyPoolABI, functionName: 'allTokens', }).catch(() => [] as readonly `0x${string}`[]), publicClient.readContract({ address: partyInfoAddress as `0x${string}`, abi: IPartyInfoABI, functionName: 'working', args: [poolAddress], }).catch(() => false), ]); // 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) 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([ publicClient.readContract({ address: firstTokenAddress as `0x${string}`, abi: ERC20ABI, functionName: 'decimals', }) as Promise, publicClient.readContract({ address: firstTokenAddress as `0x${string}`, abi: ERC20ABI, functionName: 'balanceOf', args: [poolAddress], }) as Promise, 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 const tokenBalance = Number(balance) / Math.pow(10, decimals); const approximateTVL = tokenBalance * tokens.length; tvlStr = formatTVL(approximateTVL); } } } catch (err) { console.error(`Error fetching pool price/TVL for ${poolAddress}:`, err); priceStr = undefined; tvlStr = undefined; } } // Add all pools (both working and killed) details.push({ address: poolAddress, name: name as string, symbol: symbol as string, tokens: tokens as readonly `0x${string}`[], price: priceStr, tvl: tvlStr, apy: apyStr, isKilled: !isWorking, }); } catch (err) { console.error('Error fetching pool details for', poolAddress, err); // Skip pools that fail to load } } setPoolDetails(details); } catch (err) { console.error('Error calling getAllPools:', err); setError(err instanceof Error ? err.message : 'Failed to fetch pools'); } finally { setLoading(false); } }; fetchPools(); }, [publicClient, mounted, offset, limit]); return { pools, poolDetails, loading, error, isReady: mounted, }; } export interface SwapMintAmounts { amountInUsed: bigint; fee: bigint; lpMinted: bigint; calculatedSlippage?: number; // Percentage, e.g. 5.5 means 5.5% } export interface BurnSwapAmounts { amountOut: bigint; outFee: bigint; calculatedSlippage?: number; // Percentage, e.g. 5.5 means 5.5% } export function useSwapMintAmounts( poolAddress: `0x${string}` | undefined, inputTokenIndex: number | undefined, maxAmountIn: bigint | undefined, inputTokenDecimals: number | undefined // Decimals of the input token ) { const publicClient = usePublicClient(); const [mounted, setMounted] = useState(false); const [swapMintAmounts, setSwapMintAmounts] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); // Handle hydration for Next.js static export useEffect(() => { setMounted(true); }, []); useEffect(() => { if (!mounted || !poolAddress || inputTokenIndex === undefined || !maxAmountIn || maxAmountIn === BigInt(0)) { setLoading(false); setSwapMintAmounts(null); return; } const fetchSwapMintAmounts = async () => { if (!publicClient) return; // Early validation if (inputTokenDecimals === undefined) { setError('inputTokenDecimals is required but was undefined'); return; } try { setLoading(true); setError(null); const chainId = await publicClient.getChainId(); const partyInfoAddress = (chainInfo as Record)[chainId.toString()]?.v1?.PartyInfo; if (!partyInfoAddress) { setError('PartyInfo contract not found for current chain'); setSwapMintAmounts(null); return; } const result = await publicClient.readContract({ address: partyInfoAddress as `0x${string}`, abi: IPartyPoolViewerABI, functionName: 'swapMintAmounts', args: [poolAddress, BigInt(inputTokenIndex), maxAmountIn], }) as readonly [bigint, bigint, bigint]; // Fetch and calculate pool price let poolPrice: number | undefined; let calculatedSlippage: number | 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); } setSwapMintAmounts({ amountInUsed: result[0], fee: result[2], lpMinted: result[1], calculatedSlippage, }); } catch (err) { console.error('Error calling swapMintAmounts:', err); const errorMessage = err instanceof Error ? err.message : 'Failed to fetch swap mint amounts'; setError(errorMessage); setSwapMintAmounts(null); } finally { setLoading(false); } }; fetchSwapMintAmounts(); }, [publicClient, mounted, poolAddress, inputTokenIndex, maxAmountIn, inputTokenDecimals]); return { swapMintAmounts, loading, error, isReady: mounted, }; } export function useBurnSwapAmounts( poolAddress: `0x${string}` | undefined, lpAmount: bigint | undefined, inputTokenIndex: number | undefined, tokenDecimals: number | undefined // Decimals of the output token ) { const publicClient = usePublicClient(); const [mounted, setMounted] = useState(false); const [burnSwapAmounts, setBurnSwapAmounts] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); // Handle hydration for Next.js static export useEffect(() => { setMounted(true); }, []); useEffect(() => { if (!mounted || !poolAddress || !lpAmount || lpAmount === BigInt(0) || inputTokenIndex === undefined) { setLoading(false); setBurnSwapAmounts(null); return; } const fetchBurnSwapAmounts = async () => { if (!publicClient) { setLoading(false); return; } try { setLoading(true); setError(null); // Get chain ID and contract address const chainId = await publicClient.getChainId(); // @ts-ignore const partyInfoAddress = (chainInfo as Record)[chainId.toString()]?.v1?.PartyInfo; if (!partyInfoAddress) { setError('PartyInfo contract not found for current chain'); setBurnSwapAmounts(null); return; } // Call burnSwapAmounts function - returns [amountOut, outFee] const result = await publicClient.readContract({ address: partyInfoAddress as `0x${string}`, abi: IPartyPoolViewerABI, functionName: 'burnSwapAmounts', args: [poolAddress, lpAmount, BigInt(inputTokenIndex)], }) as readonly [bigint, bigint]; // Calculate slippage for burnSwap using poolPrice let calculatedSlippage: number | undefined; if (tokenDecimals !== undefined) { try { // Get the market price from poolPrice (quoteTokenIndex = 0) const poolPriceInt128 = await publicClient.readContract({ address: partyInfoAddress as `0x${string}`, abi: IPartyInfoABI, functionName: 'poolPrice', args: [poolAddress, BigInt(inputTokenIndex)], }) as bigint; // Convert Q64 format to decimal (price = priceValue / 2^64) let poolPrice = Number(poolPriceInt128) / 2 ** 64; poolPrice = (poolPrice * Math.pow(10, 18 - tokenDecimals)); // For burnSwap: swapPrice = (outAmount + fee) / lpAmount // Need to apply decimal corrections: outAmount and fee are in token decimals, lpAmount is in 18 decimals const outAmountDecimal = Number(result[0]) / Math.pow(10, tokenDecimals); const feeDecimal = Number(result[1]) / Math.pow(10, tokenDecimals); const lpAmountDecimal = Number(lpAmount) / Math.pow(10, 18); // LP tokens have 18 decimals const swapPrice = (outAmountDecimal + feeDecimal) / lpAmountDecimal; calculatedSlippage = ((poolPrice - swapPrice) / poolPrice) * 100; } catch (slippageErr) { console.error(`Error calculating slippage for burnSwap:`, slippageErr); } } const parsedAmounts = { amountOut: result[0], outFee: result[1], 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); setBurnSwapAmounts(null); } finally { setLoading(false); } }; fetchBurnSwapAmounts(); }, [publicClient, mounted, poolAddress, lpAmount, inputTokenIndex, tokenDecimals]); return { burnSwapAmounts, loading, error, isReady: mounted, }; } export function useLPTokenBalance( poolAddress: `0x${string}` | undefined, userAddress: `0x${string}` | undefined ) { const publicClient = usePublicClient(); const [mounted, setMounted] = useState(false); const [lpBalance, setLpBalance] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); // Handle hydration for Next.js static export useEffect(() => { setMounted(true); }, []); useEffect(() => { if (!mounted || !poolAddress || !userAddress) { setLoading(false); setLpBalance(null); return; } const fetchLPBalance = async () => { if (!publicClient) { setLoading(false); return; } try { setLoading(true); setError(null); // Call balanceOf on the pool (which is an ERC20 LP token) const balance = await publicClient.readContract({ address: poolAddress, abi: ERC20ABI, functionName: 'balanceOf', args: [userAddress], }) as bigint; setLpBalance(balance); } catch (err) { console.error('Error fetching LP token balance:', err); setError(err instanceof Error ? err.message : 'Failed to fetch LP balance'); setLpBalance(null); } finally { setLoading(false); } }; fetchLPBalance(); }, [publicClient, mounted, poolAddress, userAddress]); return { lpBalance, loading, error, isReady: mounted, }; } export function useBurnAmounts( poolAddress: `0x${string}` | undefined, lpTokenAmount: bigint | undefined ) { const publicClient = usePublicClient(); const [mounted, setMounted] = useState(false); const [burnAmounts, setBurnAmounts] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { setMounted(true); }, []); useEffect(() => { if (!mounted || !poolAddress || !lpTokenAmount || lpTokenAmount === 0n) { setBurnAmounts(null); setLoading(false); return; } const fetchBurnAmounts = async () => { if (!publicClient) { setLoading(false); return; } try { setLoading(true); setError(null); // Get chain ID and PartyInfo contract address const chainId = await publicClient.getChainId(); const partyInfoAddress = (chainInfo as Record)[chainId.toString()]?.v1?.PartyInfo; if (!partyInfoAddress) { setError('PartyInfo contract not found for current chain'); setBurnAmounts(null); return; } // Call burnAmounts function const amounts = await publicClient.readContract({ address: partyInfoAddress as `0x${string}`, abi: IPartyPoolViewerABI, functionName: 'burnAmounts', args: [poolAddress, lpTokenAmount], }) as bigint[]; setBurnAmounts(amounts); } catch (err) { console.error('Error fetching burn amounts:', err); setError(err instanceof Error ? err.message : 'Failed to fetch burn amounts'); setBurnAmounts(null); } finally { setLoading(false); } }; fetchBurnAmounts(); }, [publicClient, mounted, poolAddress, lpTokenAmount]); return { burnAmounts, loading, error, isReady: mounted, }; }