'use client' import { useEffect, useState, useCallback } from 'react' import { Button, Select, InputNumber, Switch, App, Spin, Image, Card, } from 'antd' import { SwapOutlined } from '@ant-design/icons' interface MintInfo { mint: string symbol: string decimals: number logoURI: string price: string address: string } interface QuoteInfo { inAmount: string outAmount: string outAmountUi: number priceImpact: string route: string } const USDC_MINT = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v' function SwapPageContent() { const { message } = App.useApp() const [mintList, setMintList] = useState([]) const [loading, setLoading] = useState(false) const [quoting, setQuoting] = useState(false) const [swapLoading, setSwapLoading] = useState(false) const [inputToken, setInputToken] = useState(USDC_MINT) const [outputToken, setOutputToken] = useState('') const [inputAmount, setInputAmount] = useState(null) const [isUsdMode, setIsUsdMode] = useState(true) const [quoteInfo, setQuoteInfo] = useState(null) const fetchMintList = async () => { setLoading(true) try { const response = await fetch('/api/my-lp/mintList') const data = await response.json() setMintList(data.records || []) } catch (error) { console.error('Failed to fetch mint list:', error) message.error('获取代币列表失败') } finally { setLoading(false) } } const getQuote = useCallback(async () => { if (!inputToken || !outputToken || !inputAmount || inputAmount <= 0) { setQuoteInfo(null) return } setQuoting(true) try { const response = await fetch( `/api/swap?inputMint=${inputToken}&outputMint=${outputToken}&amount=${inputAmount}&mode=${isUsdMode ? 'usd' : 'amount'}` ) const data = await response.json() if (data.success) { setQuoteInfo({ inAmount: data.inAmount, outAmount: data.outAmount, outAmountUi: data.outAmountUi, priceImpact: data.priceImpact, route: data.route, }) } else { setQuoteInfo(null) console.error('Quote error:', data.error) } } catch (error) { console.error('Failed to get quote:', error) setQuoteInfo(null) } finally { setQuoting(false) } }, [inputToken, outputToken, inputAmount, isUsdMode]) useEffect(() => { fetchMintList() }, []) useEffect(() => { const timer = setTimeout(() => { if (inputAmount && inputAmount > 0) { getQuote() } }, 500) return () => clearTimeout(timer) }, [inputAmount, inputToken, outputToken, isUsdMode, getQuote]) const handleSwap = async () => { if (!inputToken || !outputToken || !inputAmount || inputAmount <= 0) { message.warning('请选择代币并输入金额') return } setSwapLoading(true) message.loading({ content: '正在兑换...', key: 'swap', duration: 0 }) try { const response = await fetch('/api/swap', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ inputMint: inputToken, outputMint: outputToken, amount: inputAmount, mode: isUsdMode ? 'usd' : 'amount', slippageBps: 200, }), }) const data = await response.json() if (data.success) { message.success({ content: `兑换成功!交易: ${data.txid.slice(0, 8)}...`, key: 'swap', }) window.open(`https://solscan.io/tx/${data.txid}`, '_blank') setInputAmount(null) setQuoteInfo(null) } else { message.error({ content: data.error || '兑换失败', key: 'swap' }) } } catch (error) { console.error('Swap error:', error) message.error({ content: '兑换失败', key: 'swap' }) } finally { setSwapLoading(false) } } const handleSwitchTokens = () => { const temp = inputToken setInputToken(outputToken) setOutputToken(temp) setQuoteInfo(null) } const getTokenInfo = (address: string): MintInfo | undefined => { return mintList.find((m) => m.address === address) } const formatNumber = (num: number, decimals: number = 6): string => { if (num >= 1000) { return num.toFixed(2) } else if (num >= 1) { return num.toFixed(4) } return num.toFixed(decimals) } const getPlaceholder = (): string => { if (isUsdMode) { return '输入 USD 金额' } const token = getTokenInfo(inputToken) return `输入 ${token?.symbol || '代币'} 数量` } return (
{isUsdMode ? '按 USD 金额' : '按代币数量'}
支付
{ setOutputToken(value) setQuoteInfo(null) }} style={{ width: 150 }} showSearch placeholder="选择代币" optionFilterProp="label" options={mintList.map((m) => ({ label: m.symbol, value: m.address, }))} optionRender={(option) => (
m.address === option.value) ?.logoURI } alt={option.label as string} width={20} height={20} style={{ borderRadius: '50%' }} preview={false} fallback="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==" /> {option.label}
)} />
{quoting ? ( ) : quoteInfo ? (
{formatNumber(quoteInfo.outAmountUi)}
{getTokenInfo(outputToken)?.price && (
≈ $ {formatNumber( quoteInfo.outAmountUi * Number(getTokenInfo(outputToken)?.price || 0) )}
)}
) : ( - )}
{quoteInfo && (
价格影响: 1 ? '#ff4d4f' : Number(quoteInfo.priceImpact) > 0.5 ? '#faad14' : '#52c41a', }} > {(Number(quoteInfo.priceImpact) * 100).toFixed(2)}%
{quoteInfo.route && (
路由: {quoteInfo.route}
)}
)}

使用 Jupiter Ultra API 进行兑换

滑点: 2%

) } export default function SwapPage() { return }