Files
web/src/hooks/usePartyPool.ts
2025-12-01 12:40:34 -04:00

921 lines
34 KiB
TypeScript

'use client';
import { useState, useEffect, useCallback } from 'react';
import { usePublicClient, useWalletClient } from 'wagmi';
import { decodeEventLog, parseUnits } from 'viem';
import IPartyPoolABI from '@/contracts/IPartyPoolABI';
import chainInfo from '../contracts/liqp-deployments.json';
import IPartyInfoABI from '@/contracts/IPartyInfoABI';
import type { AvailableToken } from './usePartyPlanner';
// Q96 constant for price calculations
const Q96 = 1n << 96n;
/**
* Calculate slippage percentage based on market price and actual swap execution price
* @param marketPrice The current market price from the pool (in Q64 format, already converted to decimal)
* @param swapOutputAmount The output amount from the swap
* @param swapInputAmount The input amount for the swap
* @param swapFee The fee charged for the swap
* @returns Slippage as a percentage (e.g., 5.5 means 5.5%)
*/
export function calculateSlippage(
marketPrice: number,
swapOutputAmount: bigint,
swapInputAmount: bigint,
swapFee: bigint
): number {
// Calculate actual swap price with decimal correction
const swapPrice = Number(swapOutputAmount) / (Number(swapInputAmount) - Number(swapFee));
// Calculate slippage percentage: ((swapPrice - marketPrice) / marketPrice) * 100
const slippage = ((marketPrice - swapPrice) / marketPrice) * 100;
return slippage;
}
export interface SwapAmountResult {
tokenAddress: `0x${string}`;
tokenSymbol: string;
amountIn: bigint;
amountOut: bigint;
fee: bigint;
poolAddress: `0x${string}`;
kappa: bigint;
inputTokenIndex: number;
outputTokenIndex: number;
calculatedSlippage?: number; // Percentage, e.g. 5.5 means 5.5%
}
/**
* Selects the best swap route from an array of routes
* Primary criterion: lowest fee
* Secondary criterion: highest kappa (if fees are equal)
*/
export function selectBestSwapRoute(routes: SwapAmountResult[]): SwapAmountResult | null {
console.log('selectBestSwapRoute called with', routes.length, 'routes');
if (routes.length === 0) {
console.log('No routes available');
return null;
}
console.log('All routes:', routes.map(r => ({
token: r.tokenSymbol,
pool: r.poolAddress,
fee: r.fee.toString(),
kappa: r.kappa.toString(),
amountOut: r.amountOut.toString(),
})));
const bestRoute = routes.reduce((best, current) => {
// Primary: lowest fee
if (current.fee < best.fee) return current;
if (current.fee > best.fee) return best;
// Secondary: if fees are equal, highest kappa
if (current.kappa > best.kappa) return current;
return best;
});
console.log('Selected best route:', {
token: bestRoute.tokenSymbol,
pool: bestRoute.poolAddress,
fee: bestRoute.fee.toString(),
kappa: bestRoute.kappa.toString(),
amountOut: bestRoute.amountOut.toString(),
});
return bestRoute;
}
export function useSwapAmounts(
availableTokens: AvailableToken[] | null,
fromAmount: string,
fromTokenDecimals: number,
slippagePercent: number
) {
const publicClient = usePublicClient();
const [mounted, setMounted] = useState(false);
const [swapAmounts, setSwapAmounts] = useState<SwapAmountResult[] | null>(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
useEffect(() => {
if (!mounted || !availableTokens || !fromAmount || parseFloat(fromAmount) <= 0) {
setSwapAmounts(null);
setLoading(false);
return;
}
const calculateSwapAmounts = async () => {
if (!publicClient) {
setLoading(false);
return;
}
try {
setLoading(true);
const amountInTokenUnits = parseUnits(fromAmount, fromTokenDecimals);
// Calculate limit price based on slippage tolerance
// limitPrice in Q96 format = Q96 * (100 + slippage%) / 100
// This represents the maximum acceptable price ratio (1 + slippage%)
const slippageBasisPoints = BigInt(Math.floor(slippagePercent * 100)); // Convert to basis points (0.5% = 50)
const limitPrice = (Q96 * (10000n + slippageBasisPoints)) / 10000n;
console.log('Limit Price Calculation:', {
slippagePercent,
slippageBasisPoints: slippageBasisPoints.toString(),
limitPriceQ96: limitPrice.toString(),
Q96: Q96.toString(),
});
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
for (const token of availableTokens) {
if (token.swapRoutes.length === 0) continue;
const routeResults: SwapAmountResult[] = [];
// Evaluate ALL routes for this token
for (const route of token.swapRoutes) {
try {
// Get swap amounts
const swapResult = await publicClient.readContract({
address: route.poolAddress,
abi: IPartyPoolABI,
functionName: 'swapAmounts',
args: [
BigInt(route.inputTokenIndex),
BigInt(route.outputTokenIndex),
amountInTokenUnits,
limitPrice,
],
}) as readonly [bigint, bigint, bigint];
const [amountIn, amountOut, fee] = swapResult;
// Get kappa for this pool
const kappa = await publicClient.readContract({
address: route.poolAddress,
abi: IPartyPoolABI,
functionName: 'kappa',
}) 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 marketPrice = Number(priceInt128) / 2 ** 64;
// Calculate slippage using the reusable function
calculatedSlippage = calculateSlippage(marketPrice, swapOutputAmount, swapInputAmount, inFee);
console.log('calculatedSlippage', calculatedSlippage)
} catch (slippageErr) {
console.error(`Error calculating slippage for ${token.symbol}:`, slippageErr);
}
}
routeResults.push({
tokenAddress: token.address,
tokenSymbol: token.symbol,
amountIn,
amountOut,
fee,
poolAddress: route.poolAddress,
kappa,
inputTokenIndex: route.inputTokenIndex,
outputTokenIndex: route.outputTokenIndex,
calculatedSlippage,
});
} catch (err) {
console.error(`Error calculating swap for ${token.symbol} via ${route.poolAddress}:`, err);
}
}
// Select the best route for this token using the shared helper
const bestRoute = selectBestSwapRoute(routeResults);
if (bestRoute) {
results.push(bestRoute);
}
}
setSwapAmounts(results);
} catch (err) {
console.error('Error calculating swap amounts:', err);
} finally {
setLoading(false);
}
};
calculateSwapAmounts();
}, [publicClient, mounted, availableTokens, fromAmount, fromTokenDecimals, slippagePercent]);
return {
swapAmounts,
loading,
};
}
export interface GasEstimate {
totalGas: bigint;
gasPrice: bigint;
estimatedCostEth: string;
estimatedCostUsd: string;
}
export interface ActualSwapAmounts {
amountIn: bigint;
amountOut: bigint;
lpFee: bigint;
protocolFee: bigint;
}
export interface ActualSwapMintAmounts {
amountInUsed: bigint;
lpMinted: bigint;
lpFee: bigint;
protocolFee: bigint;
}
export interface ActualBurnSwapAmounts {
amountIn: bigint;
amountOut: bigint;
lpFee: bigint;
protocolFee: bigint;
}
export function useSwap() {
const { data: walletClient } = useWalletClient();
const publicClient = usePublicClient();
const [isSwapping, setIsSwapping] = useState(false);
const [swapHash, setSwapHash] = useState<`0x${string}` | null>(null);
const [swapError, setSwapError] = useState<string | null>(null);
const [gasEstimate, setGasEstimate] = useState<GasEstimate | null>(null);
const [isEstimatingGas, setIsEstimatingGas] = useState(false);
const estimateGas = useCallback(async () => {
if (!publicClient) {
console.error('Public client not available');
return null;
}
try {
setIsEstimatingGas(true);
// Get current gas price from the network
const gasPrice = await publicClient.getGasPrice();
// Use fixed, typical gas amounts for AMM operations
const approvalGas = 50000n; // ERC20 approval typically uses ~50k gas
const swapGas = 150000n; // AMM swap typically uses ~150k gas
const totalGas = approvalGas + swapGas;
const estimatedCostWei = totalGas * gasPrice;
// Use more decimal places for testnets with very low gas prices
const estimatedCostEth = (Number(estimatedCostWei) / 1e18).toFixed(9);
// Fetch ETH price in USD
let estimatedCostUsd = '0.00';
try {
const response = await fetch('https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd');
const data = await response.json();
const ethPriceUsd = data.ethereum?.usd || 0;
const costInUsd = parseFloat(estimatedCostEth) * ethPriceUsd;
// Show "< $0.01" for amounts less than a cent, otherwise show 2 decimal places
estimatedCostUsd = costInUsd < 0.01 && costInUsd > 0 ? '< $0.01' : costInUsd.toFixed(2);
} catch (priceErr) {
console.error('Error fetching ETH price:', priceErr);
}
const estimate: GasEstimate = {
totalGas,
gasPrice,
estimatedCostEth,
estimatedCostUsd,
};
setGasEstimate(estimate);
return estimate;
} catch (err) {
console.error('Error estimating gas:', err);
return null;
} finally {
setIsEstimatingGas(false);
}
}, [publicClient]);
const executeSwap = async (
poolAddress: `0x${string}`,
inputTokenAddress: `0x${string}`,
inputTokenIndex: number,
outputTokenIndex: number,
maxAmountIn: bigint,
slippagePercent: number
) => {
if (!walletClient || !publicClient) {
setSwapError('Wallet not connected');
return;
}
try {
setIsSwapping(true);
setSwapError(null);
setSwapHash(null);
const userAddress = walletClient.account.address;
// STEP 1: Approve the pool to spend the input token
console.log('🔐 Approving token spend...');
console.log('Token to approve:', inputTokenAddress);
console.log('Spender (pool):', poolAddress);
console.log('Amount:', maxAmountIn.toString());
const approvalHash = await walletClient.writeContract({
address: inputTokenAddress,
abi: [
{
name: 'approve',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'spender', type: 'address' },
{ name: 'amount', type: 'uint256' }
],
outputs: [{ name: '', type: 'bool' }]
}
],
functionName: 'approve',
args: [poolAddress, maxAmountIn],
});
console.log('✅ Approval transaction submitted:', approvalHash);
await publicClient.waitForTransactionReceipt({ hash: approvalHash });
console.log('✅ Approval confirmed');
// STEP 2: Calculate limit price and deadline
const slippageBasisPoints = BigInt(Math.floor(slippagePercent * 100));
const limitPrice = (Q96 * (10000n + slippageBasisPoints)) / 10000n;
const deadline = BigInt(Math.floor(Date.now() / 1000) + 1200);
console.log('🚀 Executing swap with params:', {
pool: poolAddress,
payer: userAddress,
receiver: userAddress,
inputTokenIndex,
outputTokenIndex,
maxAmountIn: maxAmountIn.toString(),
limitPrice: limitPrice.toString(),
deadline: deadline.toString(),
unwrap: false,
});
// STEP 3: Execute the swap transaction
const hash = await walletClient.writeContract({
address: poolAddress,
abi: IPartyPoolABI,
functionName: 'swap',
args: [
userAddress, // payer
'0x00000000', // selector (bytes4(0))
userAddress, // receiver
BigInt(inputTokenIndex),
BigInt(outputTokenIndex),
maxAmountIn,
limitPrice,
deadline,
false, // unwrap
'0x', // cbData (empty bytes)
],
});
setSwapHash(hash);
console.log('✅ Swap transaction submitted:', hash);
// Wait for transaction confirmation
const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log('✅ Swap transaction confirmed:', receipt);
// Parse the Swap event from the receipt logs
let actualSwapAmounts: ActualSwapAmounts | null = null;
for (const log of receipt.logs) {
try {
const decodedLog = decodeEventLog({
abi: IPartyPoolABI,
data: log.data,
topics: log.topics,
});
if (decodedLog.eventName === 'Swap') {
const { amountIn, amountOut, lpFee, protocolFee } = decodedLog.args as {
amountIn: bigint;
amountOut: bigint;
lpFee: bigint;
protocolFee: bigint;
};
actualSwapAmounts = {
amountIn,
amountOut,
lpFee,
protocolFee,
};
console.log('📊 Actual swap amounts from event:', {
amountIn: amountIn.toString(),
amountOut: amountOut.toString(),
lpFee: lpFee.toString(),
protocolFee: protocolFee.toString(),
});
break;
}
} catch (err) {
// Skip logs that don't match our ABI
continue;
}
}
return { receipt, actualSwapAmounts };
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Swap failed';
setSwapError(errorMessage);
console.error('❌ Swap error:', err);
throw err;
} finally {
setIsSwapping(false);
}
};
return {
executeSwap,
estimateGas,
isSwapping,
swapHash,
swapError,
gasEstimate,
isEstimatingGas,
};
}
export function useSwapMint() {
const publicClient = usePublicClient();
const { data: walletClient } = useWalletClient();
const [isSwapMinting, setIsSwapMinting] = useState(false);
const [swapMintHash, setSwapMintHash] = useState<`0x${string}` | null>(null);
const [swapMintError, setSwapMintError] = useState<string | null>(null);
const executeSwapMint = async (
poolAddress: `0x${string}`,
inputTokenAddress: `0x${string}`,
inputTokenIndex: number,
maxAmountIn: bigint
) => {
if (!walletClient || !publicClient) {
setSwapMintError('Wallet not connected');
return;
}
try {
setIsSwapMinting(true);
setSwapMintError(null);
setSwapMintHash(null);
const userAddress = walletClient.account.address;
// STEP 1: Approve the pool to spend the input token
console.log('🔐 Approving token spend for swap mint...');
console.log('Token to approve:', inputTokenAddress);
console.log('Spender (pool):', poolAddress);
console.log('Amount:', maxAmountIn.toString());
const approvalHash = await walletClient.writeContract({
address: inputTokenAddress,
abi: [
{
name: 'approve',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'spender', type: 'address' },
{ name: 'amount', type: 'uint256' }
],
outputs: [{ name: '', type: 'bool' }]
}
],
functionName: 'approve',
args: [poolAddress, maxAmountIn],
});
console.log('✅ Approval transaction submitted:', approvalHash);
await publicClient.waitForTransactionReceipt({ hash: approvalHash });
console.log('✅ Approval confirmed');
// STEP 2: Calculate deadline (5 minutes from now)
const deadline = BigInt(Math.floor(Date.now() / 1000) + 300); // 5 minutes = 300 seconds
console.log('🚀 Executing swapMint with params:', {
pool: poolAddress,
payer: userAddress,
receiver: userAddress,
inputTokenIndex,
maxAmountIn: maxAmountIn.toString(),
deadline: deadline.toString(),
});
// STEP 3: Execute the swapMint transaction
const hash = await walletClient.writeContract({
address: poolAddress,
abi: IPartyPoolABI,
functionName: 'swapMint',
args: [
userAddress, // payer
userAddress, // receiver
BigInt(inputTokenIndex),
maxAmountIn,
deadline,
],
});
setSwapMintHash(hash);
console.log('✅ SwapMint transaction submitted:', hash);
// Wait for transaction confirmation
const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log('✅ SwapMint transaction confirmed:', receipt);
// Parse the SwapMint event from the receipt logs
let actualSwapMintAmounts: ActualSwapMintAmounts | null = null;
for (const log of receipt.logs) {
try {
const decodedLog = decodeEventLog({
abi: IPartyPoolABI,
data: log.data,
topics: log.topics,
});
if (decodedLog.eventName === 'SwapMint') {
const { amountIn, amountOut, lpFee, protocolFee } = decodedLog.args as {
amountIn: bigint;
amountOut: bigint;
lpFee: bigint;
protocolFee: bigint;
};
actualSwapMintAmounts = {
amountInUsed: amountIn,
lpMinted: amountOut,
lpFee,
protocolFee,
};
console.log('📊 Actual swap mint amounts from event:', {
amountInUsed: amountIn.toString(),
lpMinted: amountOut.toString(),
lpFee: lpFee.toString(),
protocolFee: protocolFee.toString(),
});
break;
}
} catch (err) {
// Skip logs that don't match our ABI
continue;
}
}
return { receipt, actualSwapMintAmounts };
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'SwapMint failed';
setSwapMintError(errorMessage);
console.error('❌ SwapMint error:', err);
throw err;
} finally {
setIsSwapMinting(false);
}
};
return {
executeSwapMint,
isSwapMinting,
swapMintHash,
swapMintError,
};
}
export function useBurnSwap() {
const publicClient = usePublicClient();
const { data: walletClient } = useWalletClient();
const [isBurnSwapping, setIsBurnSwapping] = useState(false);
const [burnSwapHash, setBurnSwapHash] = useState<`0x${string}` | null>(null);
const [burnSwapError, setBurnSwapError] = useState<string | null>(null);
const executeBurnSwap = async (
poolAddress: `0x${string}`,
lpAmount: bigint,
inputTokenIndex: number,
unwrap: boolean = false
) => {
if (!walletClient || !publicClient) {
setBurnSwapError('Wallet not connected');
return;
}
try {
setIsBurnSwapping(true);
setBurnSwapError(null);
setBurnSwapHash(null);
const userAddress = walletClient.account.address;
// STEP 1: Approve the pool to spend the LP tokens
console.log('🔐 Approving LP token spend for burn swap...');
console.log('LP token (pool) to approve:', poolAddress);
console.log('Spender (pool):', poolAddress);
console.log('Amount:', lpAmount.toString());
const approvalHash = await walletClient.writeContract({
address: poolAddress,
abi: [
{
name: 'approve',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'spender', type: 'address' },
{ name: 'amount', type: 'uint256' }
],
outputs: [{ name: '', type: 'bool' }]
}
],
functionName: 'approve',
args: [poolAddress, lpAmount],
});
console.log('✅ Approval transaction submitted:', approvalHash);
await publicClient.waitForTransactionReceipt({ hash: approvalHash });
console.log('✅ Approval confirmed');
// STEP 2: Calculate deadline (5 minutes from now)
const deadline = BigInt(Math.floor(Date.now() / 1000) + 300); // 5 minutes = 300 seconds
console.log('🚀 Executing burnSwap with params:', {
pool: poolAddress,
payer: userAddress,
receiver: userAddress,
lpAmount: lpAmount.toString(),
inputTokenIndex,
deadline: deadline.toString(),
unwrap,
});
// STEP 3: Execute the burnSwap transaction
const hash = await walletClient.writeContract({
address: poolAddress,
abi: IPartyPoolABI,
functionName: 'burnSwap',
args: [
userAddress, // payer
userAddress, // receiver
lpAmount,
BigInt(inputTokenIndex),
deadline,
unwrap,
],
});
setBurnSwapHash(hash);
console.log('✅ BurnSwap transaction submitted:', hash);
// Wait for transaction confirmation
const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log('✅ BurnSwap transaction confirmed:', receipt);
// Parse the BurnSwap event from the receipt logs
let actualBurnSwapAmounts: ActualBurnSwapAmounts | null = null;
for (const log of receipt.logs) {
try {
const decodedLog = decodeEventLog({
abi: IPartyPoolABI,
data: log.data,
topics: log.topics,
});
if (decodedLog.eventName === 'BurnSwap') {
const { amountIn, amountOut, lpFee, protocolFee } = decodedLog.args as {
amountIn: bigint;
amountOut: bigint;
lpFee: bigint;
protocolFee: bigint;
};
actualBurnSwapAmounts = {
amountIn,
amountOut,
lpFee,
protocolFee,
};
console.log('📊 Actual burn swap amounts from event:', {
amountIn: amountIn.toString(),
amountOut: amountOut.toString(),
lpFee: lpFee.toString(),
protocolFee: protocolFee.toString(),
});
break;
}
} catch (err) {
// Skip logs that don't match our ABI
continue;
}
}
return { receipt, actualBurnSwapAmounts };
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'BurnSwap failed';
setBurnSwapError(errorMessage);
console.error('❌ BurnSwap error:', err);
throw err;
} finally {
setIsBurnSwapping(false);
}
};
return {
executeBurnSwap,
isBurnSwapping,
burnSwapHash,
burnSwapError,
};
}
export interface ActualBurnAmounts {
lpBurned: bigint;
withdrawAmounts: bigint[];
}
export function useBurn() {
const publicClient = usePublicClient();
const { data: walletClient } = useWalletClient();
const [isBurning, setIsBurning] = useState(false);
const [burnHash, setBurnHash] = useState<`0x${string}` | null>(null);
const [burnError, setBurnError] = useState<string | null>(null);
const executeBurn = async (
poolAddress: `0x${string}`,
lpAmount: bigint,
unwrap: boolean = false
) => {
if (!walletClient || !publicClient) {
setBurnError('Wallet not connected');
return;
}
try {
setIsBurning(true);
setBurnError(null);
setBurnHash(null);
const userAddress = walletClient.account.address;
// STEP 1: Approve the pool to spend the LP tokens
console.log('🔐 Approving LP token spend for burn...');
console.log('LP token (pool) to approve:', poolAddress);
console.log('Spender (pool):', poolAddress);
console.log('Amount:', lpAmount.toString());
const approvalHash = await walletClient.writeContract({
address: poolAddress,
abi: [
{
name: 'approve',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'spender', type: 'address' },
{ name: 'amount', type: 'uint256' }
],
outputs: [{ name: '', type: 'bool' }]
}
],
functionName: 'approve',
args: [poolAddress, lpAmount],
account: userAddress,
});
console.log('✅ Approval transaction submitted:', approvalHash);
await publicClient.waitForTransactionReceipt({ hash: approvalHash });
console.log('✅ Approval confirmed');
// STEP 2: Calculate deadline (20 minutes from now)
const deadline = BigInt(Math.floor(Date.now() / 1000) + 1200); // 20 minutes = 1200 seconds
console.log('🚀 Executing burn with params:', {
pool: poolAddress,
payer: userAddress,
receiver: userAddress,
lpAmount: lpAmount.toString(),
deadline: deadline.toString(),
unwrap,
});
// STEP 3: Execute the burn transaction
const hash = await walletClient.writeContract({
address: poolAddress,
abi: IPartyPoolABI,
functionName: 'burn',
args: [
userAddress, // payer
userAddress, // receiver
lpAmount,
deadline,
unwrap,
],
account: userAddress,
});
setBurnHash(hash);
console.log('✅ Burn transaction submitted:', hash);
// Wait for transaction confirmation
const receipt = await publicClient.waitForTransactionReceipt({ hash });
console.log('✅ Burn transaction confirmed:', receipt);
// Parse the Burn event from the receipt logs
let actualBurnAmounts: ActualBurnAmounts | null = null;
for (const log of receipt.logs) {
try {
const decodedLog = decodeEventLog({
abi: IPartyPoolABI,
data: log.data,
topics: log.topics,
});
if (decodedLog.eventName === 'Burn') {
// @ts-ignore
const { amounts, lpBurned } = decodedLog.args as {
amounts: bigint[];
lpBurned: bigint;
};
actualBurnAmounts = {
lpBurned,
withdrawAmounts: amounts,
};
console.log('📊 Actual burn amounts from event:', {
lpBurned: lpBurned.toString(),
withdrawAmounts: amounts.map(a => a.toString()),
});
break;
}
} catch (err) {
// Skip logs that don't match our ABI
continue;
}
}
return { receipt, actualBurnAmounts };
} catch (err) {
const errorMessage = err instanceof Error ? err.message : 'Burn failed';
setBurnError(errorMessage);
console.error('❌ Burn error:', err);
throw err;
} finally {
setIsBurning(false);
}
};
return {
executeBurn,
isBurning,
burnHash,
burnError,
};
}