diff --git a/src/components/stake-form.tsx b/src/components/stake-form.tsx index da24bd1..077aa82 100644 --- a/src/components/stake-form.tsx +++ b/src/components/stake-form.tsx @@ -20,6 +20,38 @@ interface StakeFormProps { defaultMode?: Mode; } +// Helper component for slippage warnings +function SlippageWarning({ + slippage, + action, + isError +}: { + slippage: number; + action: string; + isError: boolean; +}) { + if (isError) { + return ( +
+

⚠️ Slippage Exceeds 5%

+

+ 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. +

+
+ ); + } + + return ( +
+

⚠️ High Slippage Warning

+

+ The estimated slippage for this {action} is {Math.abs(slippage).toFixed(2)}%. You may lose money due to low liquidity in this pool. +

+
+ ); +} + interface TokenInfo { address: `0x${string}`; symbol: string; @@ -144,7 +176,8 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) { const { swapMintAmounts, loading: swapMintLoading } = useSwapMintAmounts( mode === 'stake' ? selectedPool?.address : undefined, mode === 'stake' ? inputTokenIndex : undefined, - mode === 'stake' ? maxAmountIn : undefined + mode === 'stake' ? maxAmountIn : undefined, + mode === 'stake' && selectedToken ? selectedToken.decimals : undefined ); // Fetch burn swap amounts (for unstake mode, only when not redeeming all) @@ -152,12 +185,36 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) { 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]); - console.log('burn swap maounts', burnSwapAmounts) + // 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]); // Fetch burn amounts (for unstake mode when redeeming all) const { burnAmounts, loading: burnAmountsLoading } = useBurnAmounts( @@ -669,6 +726,39 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) { )} + {/* Slippage warnings - consolidated for both stake and unstake modes */} + {mode === 'stake' && slippageExceedsLimit && swapMintAmounts?.calculatedSlippage !== undefined && ( + + )} + + {mode === 'stake' && !slippageExceedsLimit && slippageIsHigh && swapMintAmounts?.calculatedSlippage !== undefined && ( + + )} + + {mode === 'unstake' && !redeemAll && unstakeSlippageExceedsLimit && burnSwapAmounts?.calculatedSlippage !== undefined && ( + + )} + + {mode === 'unstake' && !redeemAll && !unstakeSlippageExceedsLimit && slippageIsHigh && burnSwapAmounts?.calculatedSlippage !== undefined && ( + + )} + {/* Burn Swap Amounts Display (Unstake Mode) */} {mode === 'unstake' && !redeemAll && burnSwapAmounts && selectedToken && !isAmountExceedingBalance && (
@@ -717,10 +807,10 @@ export function StakeForm({ defaultMode = 'stake' }: StakeFormProps) { !selectedPool || isAmountExceedingBalance || (mode === 'stake' - ? (!selectedToken || isSwapMinting) + ? (!selectedToken || isSwapMinting || slippageExceedsLimit) : (redeemAll ? isBurning - : (!selectedToken || inputTokenIndex === undefined || isBurnSwapping))) + : (!selectedToken || inputTokenIndex === undefined || isBurnSwapping || unstakeSlippageExceedsLimit))) } > {!isConnected diff --git a/src/hooks/usePartyPlanner.ts b/src/hooks/usePartyPlanner.ts index 6755ea4..3070677 100644 --- a/src/hooks/usePartyPlanner.ts +++ b/src/hooks/usePartyPlanner.ts @@ -8,7 +8,6 @@ import IPartyPoolABI from '@/contracts/IPartyPoolABI'; import IPartyPoolViewerABI from '@/contracts/IPartyPoolViewerABI'; import IPartyInfoABI from '@/contracts/IPartyInfoABI'; import { ERC20ABI } from '@/contracts/ERC20ABI'; -import { calculateSlippage } from './usePartyPool'; // Helper function to format large numbers with K, M, B suffixes function formatTVL(value: number): string { @@ -466,7 +465,6 @@ export function useGetAllPools(offset: number = 0, limit: number = 100) { if (isWorking) { // 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}`, @@ -580,8 +578,7 @@ export function useSwapMintAmounts( poolAddress: `0x${string}` | undefined, inputTokenIndex: number | undefined, maxAmountIn: bigint | undefined, - lpTokenPrice?: number, // Market price of the LP token in decimal format - inputTokenDecimals?: number // Decimals of the input token + inputTokenDecimals: number | undefined // Decimals of the input token ) { const publicClient = usePublicClient(); const [mounted, setMounted] = useState(false); @@ -602,8 +599,11 @@ export function useSwapMintAmounts( } const fetchSwapMintAmounts = async () => { - if (!publicClient) { - setLoading(false); + if (!publicClient) return; + + // Early validation + if (inputTokenDecimals === undefined) { + setError('inputTokenDecimals is required but was undefined'); return; } @@ -611,9 +611,7 @@ 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)[chainId.toString()]?.v1?.PartyInfo; if (!partyInfoAddress) { @@ -622,7 +620,6 @@ export function useSwapMintAmounts( return; } - // Call swapMintAmounts function const result = await publicClient.readContract({ address: partyInfoAddress as `0x${string}`, abi: IPartyPoolViewerABI, @@ -630,45 +627,34 @@ export function useSwapMintAmounts( args: [poolAddress, BigInt(inputTokenIndex), maxAmountIn], }) as readonly [bigint, bigint, bigint]; - // Fetch the market price if not provided - let marketPrice = lpTokenPrice; - if (marketPrice === undefined) { - try { - console.log('input token index', inputTokenIndex) - // Get the pool price (price of the pool in terms of the input token) - 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) - // poolPrice returns how much 1 LP token is worth in terms of the input token - marketPrice = Number(poolPriceInt128) / 2 ** 64; - console.log('swapMintAmounts fetched marketPrice (poolPrice)', marketPrice); - } catch (priceErr) { - console.error('Error fetching poolPrice:', priceErr); - } - } - - // Calculate slippage if market price is available + // Fetch and calculate pool price + let poolPrice: number | undefined; let calculatedSlippage: number | undefined; - if (marketPrice !== undefined) { - try { - // For swapMint: - // - marketPrice: how much 1 LP token is worth in input tokens (from poolPrice) - // - swapOutputAmount: LP tokens minted (result[2]) - // - swapInputAmount: input token amount used (maxAmountIn) - // - inFee: fee charged (result[1]) - console.log('LP minted', result[1]) - // Convert result[1] to token price using poolPrice: result[1] * poolPrice - const lpMintedInTokenPrice = BigInt(Math.floor(Number(result[1]) * marketPrice)); - calculatedSlippage = calculateSlippage(marketPrice, lpMintedInTokenPrice, result[0], result[2]); - console.log('🎯 swapMint calculatedSlippage: ' + calculatedSlippage.toFixed(4) + '%'); - } catch (slippageErr) { - console.error(`Error calculating slippage for swapMint:`, slippageErr); - } + + 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({ @@ -687,7 +673,7 @@ export function useSwapMintAmounts( }; fetchSwapMintAmounts(); - }, [publicClient, mounted, poolAddress, inputTokenIndex, maxAmountIn, lpTokenPrice]); + }, [publicClient, mounted, poolAddress, inputTokenIndex, maxAmountIn, inputTokenDecimals]); return { swapMintAmounts, @@ -701,8 +687,7 @@ export function useBurnSwapAmounts( poolAddress: `0x${string}` | undefined, lpAmount: bigint | undefined, inputTokenIndex: number | undefined, - lpTokenPrice?: number, // Market price of the LP token in decimal format - tokenDecimals?: number // Decimals of the output token + tokenDecimals: number | undefined // Decimals of the output token ) { const publicClient = usePublicClient(); const [mounted, setMounted] = useState(false); @@ -744,18 +729,6 @@ export function useBurnSwapAmounts( 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}`, @@ -764,12 +737,6 @@ 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) { @@ -783,7 +750,8 @@ export function useBurnSwapAmounts( }) as bigint; // Convert Q64 format to decimal (price = priceValue / 2^64) - const marketPrice = Number(poolPriceInt128) / 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 @@ -792,12 +760,10 @@ export function useBurnSwapAmounts( const lpAmountDecimal = Number(lpAmount) / Math.pow(10, 18); // LP tokens have 18 decimals const swapPrice = (outAmountDecimal + feeDecimal) / lpAmountDecimal; - - // Calculate slippage percentage: ((swapPrice - marketPrice) / marketPrice) * 100 - calculatedSlippage = ((swapPrice - marketPrice) / marketPrice) * 100; + calculatedSlippage = ((poolPrice - swapPrice) / poolPrice) * 100; console.log('burnSwap slippage calculation:', { - marketPrice, + poolPrice, swapPrice, calculatedSlippage, outAmountDecimal, @@ -818,13 +784,6 @@ 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); @@ -836,7 +795,7 @@ export function useBurnSwapAmounts( }; fetchBurnSwapAmounts(); - }, [publicClient, mounted, poolAddress, lpAmount, inputTokenIndex, lpTokenPrice, tokenDecimals]); + }, [publicClient, mounted, poolAddress, lpAmount, inputTokenIndex, tokenDecimals]); return { burnSwapAmounts,