Files
web/src/hooks/usePartyPlanner.ts

995 lines
33 KiB
TypeScript

'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<readonly `0x${string}`[] | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(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<string, { v1: { PartyPlanner: string } }>)[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<AvailableToken[] | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(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<string, { v1: { PartyPlanner: string; PartyInfo: string } }>)[chainId.toString()]?.v1?.PartyPlanner;
const partyInfoAddress = (chainInfo as Record<string, { v1: { PartyPlanner: string; PartyInfo: string } }>)[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<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}`[];
// 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<TokenDetails[] | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(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<readonly `0x${string}`[] | null>(null);
const [poolDetails, setPoolDetails] = useState<PoolDetails[] | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(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<string, { v1: { PartyPlanner: string; PartyInfo: string } }>)[chainId.toString()]?.v1?.PartyPlanner;
const partyInfoAddress = (chainInfo as Record<string, { v1: { PartyPlanner: string; PartyInfo: string } }>)[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<number>,
publicClient.readContract({
address: firstTokenAddress as `0x${string}`,
abi: ERC20ABI,
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
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<SwapMintAmounts | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(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<string, { v1: { PartyInfo: string } }>)[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<BurnSwapAmounts | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(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<string, { v1: { PartyInfo: string } }>)[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<bigint | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(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<bigint[] | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(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<string, { v1: { PartyInfo: string } }>)[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,
};
}