Skip to content

HavenCTO/web3-shoutbox-platform

Repository files navigation

Web3 Shoutbox

XMTP GunDB Vite License: MIT

A decentralized, embeddable shoutbox widget powered by XMTP for end-to-end encrypted messaging and GunDB for real-time presence. No backend server required β€” the entire system runs in the browser.

Web3 Shoutbox Screenshot

What is this?

Web3 Shoutbox is a real-time chat widget that any website can embed via an <iframe>. Identity is anchored to crypto wallets, all messages are end-to-end encrypted via XMTP's MLS protocol, and presence (who is online) is tracked peer-to-peer through GunDB. Zero server infrastructure required β€” just static hosting.

Features

  • πŸ” End-to-end encryption β€” MLS protocol (RFC 9420) via XMTP, mandatory on all messages
  • πŸ‘› Wallet-based identity β€” connect with MetaMask, Rainbow, Coinbase, or any WalletConnect wallet
  • πŸ‘₯ Real-time presence β€” see who is online via GunDB CRDT-based peer-to-peer sync
  • πŸ“¦ Embeddable widget β€” drop an <iframe> on any website with a single line of HTML
  • πŸ”„ Sliding window groups β€” XMTP groups rotate on a schedule, naturally resetting member counts
  • πŸ—οΈ No backend β€” fully JAMstack, deploy to any static host
  • πŸ—³οΈ Deterministic leader election β€” client-side, coordination-free group creation
  • πŸŒ™ Dark mode β€” full light/dark theme support
  • πŸ“± Mobile responsive β€” works down to 280px width
  • πŸ”” Toast notifications β€” real-time feedback for connections, errors, and transitions

Quick Start

  1. Clone the repository

    git clone <repository-url>
    cd web3-shoutbox-platform
  2. Install dependencies

    npm install
  3. Configure environment variables

    cp .env.local.example .env.local
    # Edit .env.local β€” you need a WalletConnect Project ID at minimum
  4. Run the development server

    npm run dev
  5. Open in browser Navigate to http://localhost:3000

Embed the Widget

Add this to any website to embed the shoutbox:

<iframe
  src="https://your-shoutbox-domain.com/embed/shoutbox"
  width="400"
  height="600"
  frameborder="0"
  style="border-radius: 12px; box-shadow: 0 4px 24px rgba(0,0,0,0.12);"
  allow="clipboard-write">
</iframe>

The widget auto-detects the parent page URL as the chat room. Pass ?room=my-room&theme=dark for customization.

πŸ‘‰ Full Embed Guide β†’

Tech Stack

Technology Version Purpose
Vite 7.x Build tool & dev server
React 19.x UI library
TypeScript 5.x Type safety
Tailwind CSS 4.x Styling
XMTP browser-sdk 7.x E2E encrypted messaging (MLS)
GunDB 0.2020.x CRDT-based real-time presence
wagmi 3.x Ethereum React hooks
viem 2.x Ethereum library
@reown/appkit 1.x Wallet connection (WalletConnect)
Zustand 5.x State management
Zod 4.x Runtime validation
Sonner 2.x Toast notifications

Architecture Overview

The shoutbox is a dual-protocol, browser-only system:

  • XMTP handles encrypted message transport β€” messages are signed with wallet keys and encrypted via MLS
  • GunDB handles ephemeral presence β€” who is currently on the page, tracked via heartbeat TTL

These layers are intentionally decoupled. GunDB owns the UI roster; XMTP owns message delivery. Presence changes do not trigger XMTP group mutations.

Groups use a sliding window model β€” instead of one permanent group per URL, groups rotate on a time-based schedule (default: 5 minutes). This avoids the 250-member cap and eliminates expensive removeMembers MLS operations.

πŸ‘‰ Full Architecture Doc β†’

Project Structure

web3-shoutbox-platform/
β”œβ”€β”€ src/
β”‚   β”œβ”€β”€ components/
β”‚   β”‚   β”œβ”€β”€ auth/              # ConnectWallet button
β”‚   β”‚   β”œβ”€β”€ chat/              # ChatContainer, MessageList, MessageBubble, MessageInput
β”‚   β”‚   β”œβ”€β”€ layout/            # AppLayout, Header
β”‚   β”‚   β”œβ”€β”€ presence/          # PresencePanel, UserAvatar
β”‚   β”‚   β”œβ”€β”€ providers/         # Web3Provider, XmtpProvider, GunProvider, ThemeProvider
β”‚   β”‚   β”œβ”€β”€ ui/                # Skeleton, XmtpStepIndicator
β”‚   β”‚   └── ErrorBoundary.tsx  # Top-level error boundary
β”‚   β”œβ”€β”€ config/
β”‚   β”‚   └── env.ts             # Zod-validated environment variables
β”‚   β”œβ”€β”€ hooks/
β”‚   β”‚   β”œβ”€β”€ useEmbed.ts        # Embed detection, PostMessage API, auto-resize
β”‚   β”‚   β”œβ”€β”€ useGroupLifecycle.ts # Window management, leader election orchestration
β”‚   β”‚   β”œβ”€β”€ useLeaderElection.ts # Deterministic leader computation
β”‚   β”‚   β”œβ”€β”€ useOnlineUsers.ts  # Presence subscription β†’ OnlineUser[]
β”‚   β”‚   β”œβ”€β”€ usePresence.ts     # Join/leave room, heartbeat lifecycle
β”‚   β”‚   β”œβ”€β”€ useShoutboxRoom.ts # Unified room hook (presence + messaging + groups)
β”‚   β”‚   β”œβ”€β”€ useXmtpClient.ts   # XMTP client state from provider
β”‚   β”‚   └── useXmtpConversation.ts # Group message send/receive
β”‚   β”œβ”€β”€ lib/
β”‚   β”‚   β”œβ”€β”€ embed-messaging.ts # PostMessage protocol, auto-resize, config parsing
β”‚   β”‚   β”œβ”€β”€ group-lifecycle.ts # GunDB group read/write/subscribe
β”‚   β”‚   β”œβ”€β”€ gun.ts             # GunDB singleton instance
β”‚   β”‚   β”œβ”€β”€ gun-presence.ts    # Low-level presence read/write
β”‚   β”‚   β”œβ”€β”€ leader-election.ts # Deterministic leader election algorithm
β”‚   β”‚   β”œβ”€β”€ retry.ts           # Generic retry with exponential backoff
β”‚   β”‚   β”œβ”€β”€ url-utils.ts       # URL normalization + SHA-256 room keys
β”‚   β”‚   β”œβ”€β”€ utils.ts           # Tailwind cn() helper
β”‚   β”‚   └── xmtp.ts            # XMTP client factory
β”‚   β”œβ”€β”€ pages/
β”‚   β”‚   β”œβ”€β”€ embed/
β”‚   β”‚   β”‚   └── EmbedShoutboxPage.tsx  # Compact embed widget page
β”‚   β”‚   β”œβ”€β”€ NotFoundPage.tsx
β”‚   β”‚   β”œβ”€β”€ RoomBrowserPage.tsx
β”‚   β”‚   β”œβ”€β”€ SettingsPage.tsx
β”‚   β”‚   └── ShoutboxPage.tsx   # Main standalone shoutbox page
β”‚   β”œβ”€β”€ services/
β”‚   β”‚   β”œβ”€β”€ groupLifecycleService.ts  # Group creation, discovery, failover
β”‚   β”‚   β”œβ”€β”€ messagingService.ts       # XMTP send/receive with retry
β”‚   β”‚   └── presenceService.ts        # GunDB presence join/leave/heartbeat
β”‚   β”œβ”€β”€ stores/
β”‚   β”‚   β”œβ”€β”€ authStore.ts       # Wallet connection state
β”‚   β”‚   β”œβ”€β”€ chatStore.ts       # Messages and chat UI state
β”‚   β”‚   └── presenceStore.ts   # Online users state
β”‚   β”œβ”€β”€ types/
β”‚   β”‚   β”œβ”€β”€ embed.ts           # PostMessage event/command types
β”‚   β”‚   β”œβ”€β”€ errors.ts          # Typed error classes + error classifiers
β”‚   β”‚   β”œβ”€β”€ group.ts           # GroupWindow, GroupState
β”‚   β”‚   β”œβ”€β”€ gun.d.ts           # GunDB type declarations
β”‚   β”‚   β”œβ”€β”€ message.ts         # ShoutboxMessage
β”‚   β”‚   β”œβ”€β”€ presence.ts        # PresenceRecord, OnlineUser
β”‚   β”‚   └── result.ts          # Result<T, E> pattern (ok/err)
β”‚   β”œβ”€β”€ App.tsx                # Router + provider tree
β”‚   β”œβ”€β”€ globals.css            # Tailwind base + custom animations
β”‚   └── main.tsx               # Entry point
β”œβ”€β”€ public/
β”‚   β”œβ”€β”€ test-embed.html        # Interactive embed test page
β”‚   β”œβ”€β”€ _headers               # CDN headers for iframe embedding
β”‚   └── _redirects              # SPA fallback redirects
β”œβ”€β”€ docs/                      # Documentation
β”‚   β”œβ”€β”€ ARCHITECTURE.md
β”‚   β”œβ”€β”€ CONTRIBUTING.md
β”‚   β”œβ”€β”€ EMBED_GUIDE.md
β”‚   └── SETUP.md
β”œβ”€β”€ e2e/                       # Playwright E2E test directory
β”œβ”€β”€ .env.local.example         # Environment variable template
β”œβ”€β”€ eslint.config.mjs          # ESLint configuration
β”œβ”€β”€ index.html                 # Vite entry HTML
β”œβ”€β”€ package.json               # Dependencies & scripts
β”œβ”€β”€ playwright.config.ts       # Playwright configuration
β”œβ”€β”€ postcss.config.mjs         # PostCSS (Tailwind)
β”œβ”€β”€ tsconfig.json              # TypeScript configuration
β”œβ”€β”€ vite.config.ts             # Vite configuration
└── vitest.config.ts           # Vitest configuration

Development

Command Description
npm run dev Start development server on port 3000
npm run build Type-check and build for production (output: out/)
npm run preview Preview production build locally
npm run test Run unit tests with Vitest
npm run test:watch Run unit tests in watch mode
npm run test:e2e Run Playwright E2E tests
npm run lint Run ESLint

Environment Variables

Create a .env.local file from the template:

cp .env.local.example .env.local
Variable Required Default Description
VITE_WALLETCONNECT_PROJECT_ID Yes β€” WalletConnect Project ID from Reown Dashboard
VITE_XMTP_ENV Yes β€” XMTP network environment: dev or production
VITE_APP_URL Yes β€” Application base URL (e.g., http://localhost:3000)
VITE_GUN_RELAY_PEERS No Public relays Comma-separated GunDB relay peer URLs
VITE_SLIDING_WINDOW_MINUTES No 5 Duration of each sliding window epoch in minutes

Deployment

The shoutbox is a static site β€” build it and deploy the out/ directory to any static host.

npm run build

Vercel

npx vercel --prod

Set the build output directory to out and add environment variables in the Vercel dashboard.

Netlify

The public/_redirects file handles SPA routing automatically. Deploy via:

npx netlify deploy --prod --dir=out

Cloudflare Pages

The public/_headers file configures iframe embedding headers. Connect your repository in the Cloudflare Pages dashboard with:

  • Build command: npm run build
  • Build output directory: out

Documentation

  • Architecture β€” System design, data flow, and key decisions
  • Embed Guide β€” Third-party integration guide with PostMessage API
  • Setup Guide β€” Detailed environment setup and troubleshooting
  • Contributing β€” Code style, workflow, and PR guidelines

License

MIT License β€” see LICENSE for details.

About

Demo site

Resources

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors