"use client"; import Link from "next/link"; import { useRouter } from "next/navigation"; import { useEffect, useState } from "react"; import { UnlockPanel } from "@/components/mavi/unlock-panel"; import { MaviWalletLogo } from "@/components/wallet/wallet-logos"; import { Badge } from "@/components/ui/badge"; import { Button, buttonVariants } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from "@/components/ui/card"; import { ErrorBanner } from "@/components/ui/error-banner"; import { Input } from "@/components/ui/input"; import { addActivity, notifyActivityChanged, } from "@/lib/mavi/activity"; import { fetchEthBalance, isValidEthAddress, sendEth, } from "@/lib/mavi/chain"; import { getActiveNetwork } from "@/lib/mavi/network"; import { hasVault, readUnlockedSession, type UnlockedMavi, } from "@/lib/mavi/storage"; import { cn } from "@/lib/utils"; export function SendForm() { const router = useRouter(); const [mounted, setMounted] = useState(false); const [exists, setExists] = useState(false); const [unlocked, setUnlocked] = useState(null); const [to, setTo] = useState(""); const [amount, setAmount] = useState("0.01"); const [balance, setBalance] = useState(null); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const [txHash, setTxHash] = useState(null); const [netLabel, setNetLabel] = useState(""); useEffect(() => { setMounted(true); setExists(hasVault()); setUnlocked(readUnlockedSession()); setNetLabel(getActiveNetwork().shortLabel); const sync = () => { setUnlocked(readUnlockedSession()); setNetLabel(getActiveNetwork().shortLabel); }; window.addEventListener("mavi-unlock", sync); window.addEventListener("mavi-network", sync); return () => { window.removeEventListener("mavi-unlock", sync); window.removeEventListener("mavi-network", sync); }; }, []); useEffect(() => { if (!unlocked) return; let cancelled = false; void fetchEthBalance(unlocked.address).then((b) => { if (!cancelled && b.ok) setBalance(b.eth); }); return () => { cancelled = true; }; }, [unlocked, netLabel]); async function onSend() { if (!unlocked) return; setError(null); setTxHash(null); const dest = to.trim() as `0x${string}`; if (!isValidEthAddress(dest)) { setError("Enter a valid address starting with 0x (40 hex characters)."); return; } const amt = amount.trim(); if (!amt || Number.isNaN(Number(amt)) || Number(amt) <= 0) { setError("Enter an amount greater than 0."); return; } setBusy(true); try { const { hash } = await sendEth({ privateKey: unlocked.privateKey, to: dest, amountEth: amt, }); setTxHash(hash); addActivity({ kind: "send", wallet: unlocked.address, hash, to: dest, from: unlocked.address, amountEth: amt, note: getActiveNetwork().shortLabel, }); notifyActivityChanged(); const b = await fetchEthBalance(unlocked.address); if (b.ok) setBalance(b.eth); } catch (e) { const msg = e instanceof Error ? e.message : "Send failed"; setError( /connect|fetch|network|ECONNREFUSED/i.test(msg) ? "Local chain is not running. Start it with: bun run chains:up" : msg, ); } finally { setBusy(false); } } if (!mounted) { return ( Loading… ); } if (!exists) { return ( No mavi wallet Create or import a wallet before sending. Create mavi wallet Import recovery phrase ); } if (!unlocked) { return (
Unlock to send Locked
Unlock once for this tab — then send without re-entering the password until you lock.
{ setUnlocked(u); setError(null); }} title="Password" description="Same unlock as mavi home. Stays open in this tab." submitLabel="Unlock and continue" />

Or unlock on{" "} mavi home {" "} first.

); } return (
Send Unlocked
Network: {netLabel} {" · "} from{" "} {unlocked.address.slice(0, 8)}… {balance != null ? ` · balance ${trimEth(balance)} ETH` : null} . Change network on mavi home.
{error ? {error} : null} {txHash ? (

Sent

{txHash}

) : null}
setTo(e.target.value)} placeholder="0x…" spellCheck={false} autoCapitalize="none" className="font-mono" />
setAmount(e.target.value)} inputMode="decimal" placeholder="0.01" />

If the chain is down, run{" "} bun run chains:up. New wallets need test ETH — use Get test ETH on mavi home.

); } function trimEth(eth: string): string { const n = Number(eth); if (!Number.isFinite(n)) return eth; if (n === 0) return "0"; if (n >= 1) return n.toFixed(4).replace(/\.?0+$/, ""); return n.toFixed(6).replace(/\.?0+$/, ""); }