921 lines
34 KiB
TypeScript
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,
|
|
};
|
|
} |