first pass on user alert for slippage (this does not include bug where we calculate neg slippage because of price issue)

This commit is contained in:
2025-11-25 19:02:41 -04:00
parent 8cc9d00521
commit c8c23a4f54
2 changed files with 60 additions and 5 deletions

View File

@@ -370,6 +370,18 @@ export function SwapForm() {
</div> </div>
)} )}
{/* High slippage warning - show if calculated slippage exceeds max slippage OR 5% */}
{swapAmounts && swapAmounts.length > 0 && swapAmounts[0].calculatedSlippage !== undefined && (
Math.abs(swapAmounts[0].calculatedSlippage) > Math.max(currentSlippage, 5)
) && (
<div className="px-4 py-3 bg-yellow-500/10 border border-yellow-500/20 rounded-lg">
<p className="text-sm text-yellow-600 dark:text-yellow-500 font-medium"> High Slippage Warning</p>
<p className="text-xs text-yellow-600/80 dark:text-yellow-500/80 mt-1">
The estimated slippage for this swap is {Math.abs(swapAmounts[0].calculatedSlippage).toFixed(2)}%. You may lose money due to low liquidity in this pool.
</p>
</div>
)}
{/* Gas Estimate, Slippage, and Fees */} {/* Gas Estimate, Slippage, and Fees */}
{isConnected && fromAmount && toAmount && ( {isConnected && fromAmount && toAmount && (
<div className="px-4 py-2 bg-muted/30 rounded-lg space-y-2"> <div className="px-4 py-2 bg-muted/30 rounded-lg space-y-2">

View File

@@ -2,9 +2,10 @@
import { useState, useEffect, useCallback } from 'react'; import { useState, useEffect, useCallback } from 'react';
import { usePublicClient, useWalletClient } from 'wagmi'; import { usePublicClient, useWalletClient } from 'wagmi';
import { decodeEventLog } from 'viem'; import { decodeEventLog, parseUnits } from 'viem';
import IPartyPoolABI from '@/contracts/IPartyPoolABI'; import IPartyPoolABI from '@/contracts/IPartyPoolABI';
import chainInfo from '../../../lmsr-amm/liqp-deployments.json'; import chainInfo from '../contracts/liqp-deployments.json';
import IPartyInfoABI from '@/contracts/IPartyInfoABI';
import type { AvailableToken } from './usePartyPlanner'; import type { AvailableToken } from './usePartyPlanner';
// Q96 constant for price calculations // Q96 constant for price calculations
@@ -20,6 +21,7 @@ export interface SwapAmountResult {
kappa: bigint; kappa: bigint;
inputTokenIndex: number; inputTokenIndex: number;
outputTokenIndex: number; outputTokenIndex: number;
calculatedSlippage?: number; // Percentage, e.g. 5.5 means 5.5%
} }
/** /**
@@ -95,8 +97,7 @@ export function useSwapAmounts(
try { try {
setLoading(true); setLoading(true);
const amountInWei = BigInt(Math.floor(parseFloat(fromAmount) * Math.pow(10, fromTokenDecimals))); const amountInTokenUnits = parseUnits(fromAmount, fromTokenDecimals);
// Calculate limit price based on slippage tolerance // Calculate limit price based on slippage tolerance
// limitPrice in Q96 format = Q96 * (100 + slippage%) / 100 // limitPrice in Q96 format = Q96 * (100 + slippage%) / 100
// This represents the maximum acceptable price ratio (1 + slippage%) // This represents the maximum acceptable price ratio (1 + slippage%)
@@ -111,6 +112,8 @@ export function useSwapAmounts(
}); });
const results: SwapAmountResult[] = []; const results: SwapAmountResult[] = [];
const chainId = await publicClient.getChainId();
const partyInfoAddress = (chainInfo as any)[chainId]?.v1?.PartyInfo as `0x${string}` | undefined;
// Calculate swap amounts for ALL routes of each token // Calculate swap amounts for ALL routes of each token
for (const token of availableTokens) { for (const token of availableTokens) {
@@ -129,7 +132,7 @@ export function useSwapAmounts(
args: [ args: [
BigInt(route.inputTokenIndex), BigInt(route.inputTokenIndex),
BigInt(route.outputTokenIndex), BigInt(route.outputTokenIndex),
amountInWei, amountInTokenUnits,
limitPrice, limitPrice,
], ],
}) as readonly [bigint, bigint, bigint]; }) as readonly [bigint, bigint, bigint];
@@ -143,6 +146,45 @@ export function useSwapAmounts(
functionName: 'kappa', functionName: 'kappa',
}) as bigint; }) as bigint;
// Calculate slippage for this route
let calculatedSlippage: number | undefined;
if (partyInfoAddress) {
try {
// Get swap amounts with NO LIMIT (0 means no price limit)
const [swapInputAmount, swapOutputAmount, inFee] = await publicClient.readContract({
address: route.poolAddress,
abi: IPartyPoolABI,
functionName: 'swapAmounts',
args: [
BigInt(route.inputTokenIndex),
BigInt(route.outputTokenIndex),
amountInTokenUnits,
0n, // NO LIMIT
],
}) as readonly [bigint, bigint, bigint];
// Get the current market price from PoolInfo
const priceInt128 = await publicClient.readContract({
address: partyInfoAddress,
abi: IPartyInfoABI,
functionName: 'price',
args: [route.poolAddress, BigInt(route.inputTokenIndex), BigInt(route.outputTokenIndex)],
}) as bigint;
// Convert Q64 format to decimal (price = priceValue / 2^64)
const price = Number(priceInt128) / 2 ** 64;
// Calculate actual swap price with decimal correction
const swapPrice = Number(swapOutputAmount) / ((Number(swapInputAmount)) - Number(inFee));
// Calculate slippage: 1 - (actualPrice / marketPrice)
const slippage = 1 - (swapPrice / price);
calculatedSlippage = slippage * 100; // Convert to percentage
console.log('calculatedSlippage', calculatedSlippage)
} catch (slippageErr) {
console.error(`Error calculating slippage for ${token.symbol}:`, slippageErr);
}
}
routeResults.push({ routeResults.push({
tokenAddress: token.address, tokenAddress: token.address,
tokenSymbol: token.symbol, tokenSymbol: token.symbol,
@@ -153,6 +195,7 @@ export function useSwapAmounts(
kappa, kappa,
inputTokenIndex: route.inputTokenIndex, inputTokenIndex: route.inputTokenIndex,
outputTokenIndex: route.outputTokenIndex, outputTokenIndex: route.outputTokenIndex,
calculatedSlippage,
}); });
} catch (err) { } catch (err) {
console.error(`Error calculating swap for ${token.symbol} via ${route.poolAddress}:`, err); console.error(`Error calculating swap for ${token.symbol} via ${route.poolAddress}:`, err);