getting quotes from unsiwap
This commit is contained in:
@@ -11,6 +11,7 @@ import { useTokenDetails, useGetPoolsByToken, type TokenDetails } from '@/hooks/
|
|||||||
import { useSwapAmounts, useSwap, selectBestSwapRoute, type ActualSwapAmounts } from '@/hooks/usePartyPool';
|
import { useSwapAmounts, useSwap, selectBestSwapRoute, type ActualSwapAmounts } from '@/hooks/usePartyPool';
|
||||||
import { formatUnits, parseUnits } from 'viem';
|
import { formatUnits, parseUnits } from 'viem';
|
||||||
import { SwapReviewModal } from './swap-review-modal';
|
import { SwapReviewModal } from './swap-review-modal';
|
||||||
|
import UniswapQuote from './uniswap-quote';
|
||||||
|
|
||||||
type TransactionStatus = 'idle' | 'pending' | 'success' | 'error';
|
type TransactionStatus = 'idle' | 'pending' | 'success' | 'error';
|
||||||
|
|
||||||
@@ -404,6 +405,11 @@ export function SwapForm() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Uniswap Quote */}
|
||||||
|
{fromAmount && (
|
||||||
|
<UniswapQuote amountIn={fromAmount} />
|
||||||
|
)}
|
||||||
|
|
||||||
{/* 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">
|
||||||
|
|||||||
234
src/components/uniswap-quote.tsx
Normal file
234
src/components/uniswap-quote.tsx
Normal file
@@ -0,0 +1,234 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
|
||||||
|
// ETH and SHIB addresses
|
||||||
|
const ETH_ADDRESS = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
|
||||||
|
const SHIB_ADDRESS = '0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE';
|
||||||
|
|
||||||
|
interface UniswapQuoteProps {
|
||||||
|
amountIn: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TokenPrices {
|
||||||
|
eth: number;
|
||||||
|
shib: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function UniswapQuote({ amountIn }: UniswapQuoteProps) {
|
||||||
|
const [quote, setQuote] = useState<any>(null);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
const [prices, setPrices] = useState<TokenPrices | null>(null);
|
||||||
|
|
||||||
|
// Fetch token prices from CoinGecko
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchPrices = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
'https://api.coingecko.com/api/v3/simple/price?ids=ethereum,shiba-inu&vs_currencies=usd'
|
||||||
|
);
|
||||||
|
const data = await response.json();
|
||||||
|
setPrices({
|
||||||
|
eth: data.ethereum.usd,
|
||||||
|
shib: data['shiba-inu'].usd
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to fetch prices:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchPrices();
|
||||||
|
// Refresh prices every 30 seconds
|
||||||
|
const interval = setInterval(fetchPrices, 30000);
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getQuote = async () => {
|
||||||
|
if (!amountIn || parseFloat(amountIn) <= 0) {
|
||||||
|
setError('Please enter a valid amount');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
setError('');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Convert ETH amount to wei
|
||||||
|
const amountInWei = (parseFloat(amountIn) * 1e18).toString();
|
||||||
|
|
||||||
|
const response = await fetch('https://api.uniswap.org/v2/quote', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Origin': 'https://app.uniswap.org'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
amount: amountInWei,
|
||||||
|
tokenIn: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', // WETH
|
||||||
|
tokenInChainId: 1,
|
||||||
|
tokenOut: SHIB_ADDRESS,
|
||||||
|
tokenOutChainId: 1,
|
||||||
|
type: 'EXACT_INPUT',
|
||||||
|
configs: [
|
||||||
|
{
|
||||||
|
protocols: ['V2', 'V3', 'V4'],
|
||||||
|
enableUniversalRouter: true,
|
||||||
|
routingType: 'CLASSIC'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error('Failed to fetch quote');
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log('Uniswap Quote:', data);
|
||||||
|
setQuote(data);
|
||||||
|
} catch (err: any) {
|
||||||
|
setError(err.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatSHIB = (amount: string) => {
|
||||||
|
return (parseFloat(amount) / 1e18).toLocaleString('en-US', { maximumFractionDigits: 0 });
|
||||||
|
};
|
||||||
|
|
||||||
|
const getQuoteAmount = () => {
|
||||||
|
if (!quote) return '0';
|
||||||
|
// Handle nested quote structure
|
||||||
|
return quote.quote?.quote || quote.quote || '0';
|
||||||
|
};
|
||||||
|
|
||||||
|
const calculateCostBreakdown = () => {
|
||||||
|
if (!quote || !prices) return null;
|
||||||
|
|
||||||
|
const ethAmount = parseFloat(amountIn);
|
||||||
|
const tradeValueUSD = ethAmount * prices.eth;
|
||||||
|
|
||||||
|
// Access nested quote object
|
||||||
|
const quoteData = quote.quote || quote;
|
||||||
|
|
||||||
|
// 1. Gas Cost
|
||||||
|
const gasCostUSD = parseFloat(quoteData.gasUseEstimateUSD || '0');
|
||||||
|
|
||||||
|
// 2. Price Impact
|
||||||
|
const priceImpactPercent = parseFloat(quoteData.priceImpact || '0');
|
||||||
|
const priceImpactUSD = (priceImpactPercent / 100) * tradeValueUSD;
|
||||||
|
|
||||||
|
// 3. Trading Fees (estimate 0.3% for typical Uniswap pools)
|
||||||
|
const tradingFeePercent = 0.3;
|
||||||
|
const tradingFeeUSD = (tradingFeePercent / 100) * tradeValueUSD;
|
||||||
|
|
||||||
|
const totalCostUSD = gasCostUSD + priceImpactUSD + tradingFeeUSD;
|
||||||
|
|
||||||
|
console.log('Cost breakdown calc:', {
|
||||||
|
gasCostUSD,
|
||||||
|
priceImpactPercent,
|
||||||
|
priceImpactUSD,
|
||||||
|
tradingFeeUSD,
|
||||||
|
totalCostUSD,
|
||||||
|
tradeValueUSD,
|
||||||
|
quoteData
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
gasCostUSD,
|
||||||
|
priceImpactPercent,
|
||||||
|
priceImpactUSD,
|
||||||
|
tradingFeePercent,
|
||||||
|
tradingFeeUSD,
|
||||||
|
totalCostUSD,
|
||||||
|
tradeValueUSD
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const costBreakdown = calculateCostBreakdown();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full max-w-md p-4">
|
||||||
|
<button
|
||||||
|
onClick={getQuote}
|
||||||
|
disabled={loading}
|
||||||
|
className="w-full bg-blue-500 hover:bg-blue-600 disabled:bg-gray-400 text-white font-semibold py-2 px-4 rounded"
|
||||||
|
>
|
||||||
|
{loading ? 'Getting Quote...' : 'Get Quote'}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<div className="mt-3 p-3 bg-red-50 text-red-700 rounded text-sm">
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{quote && costBreakdown && (
|
||||||
|
<div className="mt-4 space-y-3">
|
||||||
|
<div className="p-3 bg-gray-50 rounded">
|
||||||
|
<div className="text-sm text-gray-600">You Get (SHIB)</div>
|
||||||
|
<div className="text-xl font-bold">
|
||||||
|
{formatSHIB(getQuoteAmount())}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Total Costs Breakdown */}
|
||||||
|
<div className="p-4 bg-blue-50 rounded-lg space-y-3">
|
||||||
|
<h3 className="font-semibold text-lg text-gray-800">Total Costs Breakdown</h3>
|
||||||
|
|
||||||
|
{/* 1. Gas Cost */}
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-sm font-medium text-gray-700">1. Gas Cost (Network Fee)</span>
|
||||||
|
<span className="text-sm font-bold text-gray-900">
|
||||||
|
${costBreakdown.gasCostUSD.toFixed(2)} USD
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 2. Price Impact */}
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-sm font-medium text-gray-700">2. Price Impact</span>
|
||||||
|
<span className="text-sm font-bold text-gray-900">
|
||||||
|
{costBreakdown.priceImpactPercent.toFixed(2)}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-600 pl-4">
|
||||||
|
Cost: ~${costBreakdown.priceImpactUSD.toFixed(2)} USD
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 3. Trading Fees */}
|
||||||
|
<div className="space-y-1">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-sm font-medium text-gray-700">3. Trading Fees (DEX Fees)</span>
|
||||||
|
<span className="text-sm font-bold text-gray-900">
|
||||||
|
{costBreakdown.tradingFeePercent}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-600 pl-4">
|
||||||
|
${costBreakdown.tradingFeeUSD.toFixed(2)} USD
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Total */}
|
||||||
|
<div className="pt-2 border-t border-gray-300">
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<span className="text-sm font-bold text-gray-800">Total Estimated Cost</span>
|
||||||
|
<span className="text-base font-bold text-red-600">
|
||||||
|
${costBreakdown.totalCostUSD.toFixed(2)} USD
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Trade Value Reference */}
|
||||||
|
<div className="text-xs text-gray-500 text-center pt-1">
|
||||||
|
Trade Value: ${costBreakdown.tradeValueUSD.toFixed(2)} USD
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user