import { BASE_RPC_URL } from "@hey/data/constants"; import type { GetCoinResponse } from "@zoralabs/coins-sdk"; import { createTradeCall, type TradeParameters, tradeCoin } from "@zoralabs/coins-sdk"; import { useEffect, useMemo, useState } from "react"; import { toast } from "sonner"; import type { Address } from "viem"; import { createPublicClient, erc20Abi, formatEther, formatUnits, http, parseEther, parseUnits } from "viem"; import { base } from "viem/chains"; import { useAccount, useConfig, useWalletClient } from "wagmi"; import { getWalletClient } from "wagmi/actions"; import { Button, Image, Input, Tabs, Tooltip } from "@/components/Shared/UI"; import useHandleWrongNetwork from "@/hooks/useHandleWrongNetwork"; interface TradeModalProps { coin: NonNullable; onClose: () => void; } type Mode = "buy" | "sell"; const Trade = ({ coin, onClose }: TradeModalProps) => { const { address } = useAccount(); const config = useConfig(); const { data: walletClient } = useWalletClient({ chainId: base.id }); const publicClient = useMemo( () => createPublicClient({ chain: base, transport: http(BASE_RPC_URL, { batch: { batchSize: 30 } }) }), [] ); const handleWrongNetwork = useHandleWrongNetwork(); const [mode, setMode] = useState("buy"); const [amount, setAmount] = useState(""); const [loading, setLoading] = useState(false); const [ethBalance, setEthBalance] = useState(0n); const [tokenBalance, setTokenBalance] = useState(0n); const [estimatedOut, setEstimatedOut] = useState(""); useEffect(() => { (async () => { if (!address) return; try { const [eth, token] = await Promise.all([ publicClient.getBalance({ address }), publicClient.readContract({ abi: erc20Abi, address: coin.address as Address, args: [address], functionName: "balanceOf" }) ]); setEthBalance(eth); setTokenBalance(token as bigint); } catch {} })(); }, [address, coin.address, publicClient]); const tokenDecimals = 18; const setPercentAmount = (pct: number) => { const decimals = 6; if (mode === "buy") { const available = Number(formatEther(ethBalance)); const gasReserve = 0.0002; const baseAmt = (available * pct) / 100; const amt = pct === 100 ? Math.max(baseAmt - gasReserve, 0) : baseAmt; setAmount(amt.toFixed(decimals)); } else { const available = Number(formatUnits(tokenBalance, tokenDecimals)); const amt = Math.max((available * pct) / 100, 0); setAmount(amt.toFixed(decimals)); } }; const makeParams = (address: Address): TradeParameters | null => { if (!amount || Number(amount) <= 0) return null; if (mode === "buy") { return { amountIn: parseEther(amount), buy: { address: coin.address as Address, type: "erc20" }, sell: { type: "eth" }, sender: address, slippage: 0.1 }; } return { amountIn: parseUnits(amount, tokenDecimals), buy: { type: "eth" }, sell: { address: coin.address as Address, type: "erc20" }, sender: address, slippage: 0.1 }; }; const handleSubmit = async () => { if (!address) { return toast.error("Connect a wallet to trade"); } const params = makeParams(address); if (!params) return; try { setLoading(true); await handleWrongNetwork({ chainId: base.id }); const client = (await getWalletClient(config, { chainId: base.id })) || walletClient; if (!client) { setLoading(false); return toast.error("Please switch to Base network"); } await tradeCoin({ account: client.account, publicClient, tradeParameters: params, validateTransaction: false, walletClient: client }); toast.success("Trade submitted"); onClose(); } catch { toast.error("Trade failed"); } finally { setLoading(false); } }; useEffect(() => { let cancelled = false; let intervalId: ReturnType | undefined; let timeoutId: ReturnType | undefined; const run = async () => { const sender = (address as Address) || undefined; if (!sender || !amount) { setEstimatedOut(""); return; } const params: TradeParameters = mode === "buy" ? { amountIn: parseEther(amount), buy: { address: coin.address as Address, type: "erc20" }, sell: { type: "eth" }, sender, slippage: 0.1 } : { amountIn: parseUnits(amount, tokenDecimals), buy: { type: "eth" }, sell: { address: coin.address as Address, type: "erc20" }, sender, slippage: 0.1 }; try { const q = await createTradeCall(params); if (!cancelled) { const out = q.quote.amountOut || "0"; setEstimatedOut(out); } } catch { if (!cancelled) setEstimatedOut(""); } }; timeoutId = setTimeout(() => { void run(); }, 300); intervalId = setInterval(() => { void run(); }, 8000); return () => { cancelled = true; if (intervalId) clearInterval(intervalId); if (timeoutId) clearTimeout(timeoutId); }; }, [address, amount, coin.address, mode]); const symbol = coin.symbol || ""; const balanceLabel = mode === "buy" ? `Balance: ${Number(formatEther(ethBalance)).toFixed(6)}` : `Balance: ${Number(formatUnits(tokenBalance, tokenDecimals)).toFixed(3)}`; return (
setMode(t as Mode)} tabs={[ { name: "Buy", type: "buy" }, { name: "Sell", type: "sell" } ]} />
setAmount(e.target.value)} placeholder={mode === "buy" ? "0.01" : "0"} prefix={ mode === "buy" ? ( "ETH" ) : ( {coin.name} ) } value={amount} />
Estimated amount:{" "} {estimatedOut ? mode === "buy" ? `${Number( formatUnits(BigInt(estimatedOut), tokenDecimals) ).toFixed(0)}` : `${Number(formatEther(BigInt(estimatedOut))).toFixed(6)} ETH` : "-"}
{balanceLabel}
{[25, 50, 75].map((p) => ( ))}
); }; export default Trade;