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,