937 lines
30 KiB
TypeScript
937 lines
30 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';
|
|
|
|
// 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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
// Map to store available tokens with their swap routes
|
|
const tokenRoutesMap = new Map<string, AvailableToken>();
|
|
|
|
// 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(
|
|
(token) => token.toLowerCase() === tokenAddress.toLowerCase()
|
|
);
|
|
|
|
if (inputTokenIndex === -1) {
|
|
console.error('Input token not found in pool', poolAddress);
|
|
continue;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
// 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,
|
|
});
|
|
}
|
|
} catch (err) {
|
|
console.error('Error fetching tokens from pool', poolAddress, err);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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)),
|
|
]);
|
|
|
|
details.push({
|
|
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);
|
|
} 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")
|
|
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 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;
|
|
|
|
if (isWorking) {
|
|
// Fetch pool price (use first token as quote, index 0)
|
|
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 and balance
|
|
const [decimals, balance] = 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>,
|
|
]);
|
|
|
|
// Convert balance to float and double it for total TVL approximation
|
|
const tokenBalance = Number(balance) / Math.pow(10, decimals);
|
|
const approximateTVL = tokenBalance * 3;
|
|
|
|
tvlStr = formatTVL(approximateTVL);
|
|
}
|
|
} catch (err) {
|
|
console.error(`Error fetching TVL for ${poolAddress}:`, err);
|
|
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,
|
|
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 {
|
|
console.log('input token index', inputTokenIndex);
|
|
|
|
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);
|
|
setError(err instanceof Error ? err.message : 'Failed to fetch swap mint amounts');
|
|
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);
|
|
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;
|
|
}
|
|
|
|
// 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;
|
|
|
|
console.log('burnSwap slippage calculation:', {
|
|
poolPrice,
|
|
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);
|
|
}
|
|
}
|
|
|
|
const parsedAmounts = {
|
|
amountOut: result[0],
|
|
outFee: result[1],
|
|
calculatedSlippage,
|
|
};
|
|
|
|
setBurnSwapAmounts(parsedAmounts);
|
|
} catch (err) {
|
|
console.error('Error calling burnSwapAmounts:', err);
|
|
setError(err instanceof Error ? err.message : 'Failed to fetch burn swap amounts');
|
|
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,
|
|
};
|
|
} |