noise -> signal

000

press any key to skip

Non-custodial Ethereum wallet dashboard: connect, switch chains, send ETH, sign messages

Problem

Interacting with Ethereum networks typically requires juggling separate tools for connecting a wallet, switching between mainnet/testnets/L2s, sending funds, and proving address ownership via signatures.

What it does

A React single-page dApp that connects browser wallets via wagmi (injected + Safe connectors) to Ethereum mainnet, Sepolia, and Arbitrum. A landing page offers wallet connection; a dashboard shows the connected account's ETH balance with a USD estimate fetched from CoinGecko, lets users switch chains from a dropdown, send ETH transactions (address validation, wait-for-receipt, toasts), receive funds via a QR code of their address, and sign arbitrary messages — all non-custodial, with no backend of its own.

Architecture

[landing] Home page (wallet connect dialog) (client)
[dashboard] Dashboard (balance, send/receive, sign) (client)
[wagmi] wagmi/viem client (injected + Safe connectors, 3 chains) (client)
[wallet] Browser wallet (MetaMask/Safe) (external)
[rpc] Ethereum JSON-RPC (mainnet/Sepolia/Arbitrum) (external)
[coingecko] CoinGecko price API (external)

landing ──▶ wagmi · connect wallet via dialog
dashboard ──▶ wagmi · balance, switchChain, sendTransaction, signMessage
wagmi ──▶ wallet · connector handshakes / signing requests
wagmi ──▶ rpc · http() transports
dashboard ──▶ coingecko · fetch ETH/USD price for balance estimate

Simplified — box-and-arrow sketch of the repo structure, not a system diagram.

Decisions

  1. 01

    wagmi + viem for all on-chain interaction

    Single typed client handles connectors, chain switching, transactions, receipt waiting, and message signing without bespoke RPC code

    Evidence: config/config.ts

  2. 02

    Pure frontend, no backend

    All state lives in the wallet and public RPC transports; the only server call is the public CoinGecko price endpoint (inferred from absence of any server code and direct fetch)

    Evidence: src/components/AssetOverview.tsx

  3. 03

    Route guard by connection state instead of protected routes

    Dashboard redirects to / in a mount useEffect when the wallet is disconnected, rather than using a route loader/guard

    Evidence: src/pages/Dashboard.tsx

  4. 04

    Connector allowlist by array slicing

    Supported wallets are picked by connectors.slice(2), skipping the first two connectors (injected/safe order) — fragile positional filtering

    Evidence: src/components/WalletConnector.tsx

  5. 05

    shadcn/ui vendored components with Tailwind v4

    UI primitives (dialog, select, hover-card, tabs) are copied into src/components/ui for fast dark-themed dashboard UI

    Evidence: src/components/ui/button.tsx

The hard parts

  • Multi-chain state consistency: chain selection, switchChain mutation side effects, and per-chain balance refetch are coordinated manually with useEffect/useMutation callbacks (Dashboard.tsx, SendRecieveTrans.tsx)
  • Transaction lifecycle UX: pending/success/error toasts plus balance refetch are wired across two effects reacting to useSendTransaction and useWaitForTransactionReceipt state
  • Reconnect-on-mount flow that hides the landing page until isReconnecting settles to avoid connector flash

Known limitations

  • No transaction history, token support beyond native ETH, or ENS resolution — the AssetOverview card is hard-labeled 'Ethereum' regardless of selected chain
  • Sepolia balances are hard-coded to $0.00 (special-cased in AssetOverview.tsx)
  • GSAP is installed and registered but no animation code is actually invoked in the read components (inferred: unused dependency)
  • No tests, no CI, no license, no environment configuration; no deployment config (no Dockerfile, no hosting manifest)
  • Recipient-address and amount validation is minimal (required-only via react-hook-form; zero/negative amounts not guarded beyond HTML input type)
  • Price fetch lacks debounce/caching and can leave USD estimate stale on chain switch

Command palette

Jump to a section, project or link