import { useTheme } from "@/context"; import { useChains, useMoonAccount } from "@/hooks"; import type { InputBody, SimulateAssetChangesChange } from "@moonup/moon-api"; import * as Progress from "@radix-ui/react-progress"; import * as Dialog from "@radix-ui/react-dialog"; import { useQuery } from "@tanstack/react-query"; import { formatEther, formatUnits } from "ethers"; import { AnimatePresence, motion } from "framer-motion"; import type React from "react"; import { useEffect, useState, useCallback } from "react"; import { CheckCircleIcon, XCircleIcon } from "lucide-react"; import pkg from "react-copy-to-clipboard"; const { CopyToClipboard } = pkg; import { Tooltip, Button } from "@/components"; import { TooltipProvider } from "@radix-ui/react-tooltip"; interface TransactionProps { wallet: string; transaction: InputBody; autoExecute?: boolean; isModal?: boolean; onSuccess?: (receipt: any) => void; onError?: (error: string) => void; } export const Transaction: React.FC = ({ wallet, transaction, autoExecute = false, isModal = false, onSuccess, onError, }) => { const theme = useTheme(); const [isOpen, setIsOpen] = useState(isModal); useEffect(() => { if (isModal) { setIsOpen(true); } }, [isModal]); return ( {isModal ? ( {/* Your trigger component, e.g., a button */} {/* Render the transaction component */} ) : ( )} ); }; const TransactionContent: React.FC< Pick< TransactionProps, "wallet" | "transaction" | "autoExecute" | "onSuccess" | "onError" > > = ({ wallet, transaction, autoExecute, onSuccess, onError }) => { const [status, setStatus] = useState< "initial" | "simulating" | "ready" | "pending" | "completed" | "failed" >("initial"); const [txHash, setTxHash] = useState(null); const [txReceipt, setTxReceipt] = useState(null); const [error, setError] = useState(null); const { selectedChain } = useChains(); const theme = useTheme(); const { sendTransaction, signTransaction, watchTransactionStatus } = useMoonAccount(); const { data: simulationResult, error: simulationError, refetch: simulateTransaction, } = useQuery({ queryKey: ["simulateTransaction", transaction], queryFn: async () => { setStatus("simulating"); try { const result = await signTransaction(wallet, { ...transaction, simulate: true, }); console.log(result); setStatus("ready"); return result; } catch (error) { const typedError = error as Error; setError(typedError.message || "Simulation failed"); setStatus("failed"); throw error; } }, enabled: false, }); useEffect(() => { if (simulationError) { setStatus("failed"); setError(simulationError.message || "Simulation failed"); } }, [simulationError]); const handleSign = useCallback(async () => { setStatus("pending"); setError(null); try { const txResult = await sendTransaction({ ...transaction, from: wallet, }); console.log(txResult); setTxHash(txResult.transaction_hash); watchTransactionStatus(txResult.transaction_hash) .then((result) => { setTxReceipt(txResult); setStatus(result.status); if (result.status === "completed" && onSuccess) { onSuccess(txResult); } }) .catch((error) => { setError(error.message); setStatus("failed"); if (onError) { onError(error.message); } }); } catch (error) { const typedError = error as Error; console.error("Transaction failed:", error); setError(typedError.message || "Transaction failed"); setStatus("failed"); if (onError) { onError(typedError.message); } } }, [ transaction, wallet, sendTransaction, watchTransactionStatus, onSuccess, onError, ]); const handleCancel = useCallback(() => { setStatus("failed"); setError("Transaction cancelled by user."); setTxHash(null); setTxReceipt(null); if (onError) { onError("Transaction cancelled by user."); } }, [txHash, onError]); useEffect(() => { if (autoExecute) { handleSign(); } else { simulateTransaction(); } }, [transaction, autoExecute, simulateTransaction, handleSign]); const renderTransactionDetails = () => { if (status === "simulating" || status === "pending") { return (

Transaction Details

Simulating transaction...

); } if (simulationResult && simulationResult.transaction) { const tx = simulationResult.transaction; return (

Transaction Details

To:
{tx.to}
From:
{tx.from}
Value:
{formatEther(tx.value || "0")}{" "} {tx.chainId === "137" ? "MATIC" : "ETH"}
{tx.gasLimit && ( <>
Gas Limit:
{tx.gasLimit}
)} {tx.maxFeePerGas && ( <>
Max Fee:
{`${formatUnits(tx.maxFeePerGas, "gwei")} Gwei`}
)} {tx.maxPriorityFeePerGas && ( <>
Max Priority Fee:
{`${formatUnits(tx.maxPriorityFeePerGas, "gwei")} Gwei`}
)}
Nonce:
{tx.nonce}
Chain ID:
{tx.chainId}
{tx.data && ( <>
Data:
{tx.data}
)} {tx.access_list?.length > 0 && ( <>
Access List:
{tx.access_list.join(", ")}
)} {tx.blob_gas && ( <>
Blob Gas:
{tx.blob_gas}
)} {tx.blob_gas_fee_cap && ( <>
Blob Gas Fee Cap:
{tx.blob_gas_fee_cap}
)} {tx.blob_hashes?.length > 0 && ( <>
Blob Hashes:
{tx.blob_hashes.join(", ")}
)}
); } return null; }; const renderAssetChanges = (changes: SimulateAssetChangesChange[]) => (

Asset Changes

{changes.map((change, index) => ( {`${change.name}

{change.changeType} {change.amount} {change.symbol}

From: {change.from.slice(0, 6)}...{change.from.slice(-4)} → To:{" "} {change.to.slice(0, 6)}...{change.to.slice(-4)}

))}
); const renderStatusIndicator = () => (
); return ( {error && ( {error} )} {renderTransactionDetails()} {status === "ready" && simulationResult && simulationResult.simulation && ( <> {renderAssetChanges(simulationResult.simulation.changes || [])}

Review the transaction details and asset changes carefully before proceeding.

)} {(status === "pending" || status === "completed") && renderStatusIndicator()} {status === "ready" && !autoExecute && !error && (
Sign and Send Transaction
)} {status === "pending" && ( Transaction is pending. Please wait... )} {status === "completed" && txReceipt && (

Transaction Completed

Transaction Hash:{" "} alert("Transaction hash copied to clipboard!")} > {selectedChain?.explorers && Array.isArray(selectedChain.explorers) && selectedChain.explorers.length > 0 ? ( {txHash} ) : (

{txHash}
)}

Block Number: {txReceipt.blockNumber}

{txReceipt.gasUsed && (

Gas Used: {txReceipt.gasUsed.toString()}

)}
)} {status === "failed" && ( Transaction failed. Please try again. )}
); };