# Master Prompt: Building a Decentralized Forum App with @opchan/react ## Objective Build a complete, production-ready decentralized forum application using the `@opchan/react` library. The app should support anonymous and wallet-based authentication, real-time content updates, and a modern, cyberpunk-inspired UI. ## Core Requirements ### 1. Authentication System Implement a **dual-mode authentication system**: #### Anonymous Mode - Users can browse and interact immediately without wallet - Generate browser-based session (UUID) with ed25519 keypair - Allow posting, commenting, and voting - Support optional call sign for identity - Session persists in IndexedDB #### Wallet Mode - Support Ethereum wallet connection (MetaMask, WalletConnect, Coinbase) - Use wagmi v2.x for wallet management - Implement ENS verification for premium features - Support browser key delegation to reduce signature prompts **Implementation Pattern:** ```tsx const { currentUser, connect, startAnonymous, verificationStatus } = useAuth(); // Offer both options // Check verification level if (verificationStatus === EVerificationStatus.ANONYMOUS) { // Show call sign setup } else if (verificationStatus === EVerificationStatus.ENS_VERIFIED) { // Show cell creation } ``` ### 2. Content Management Implement a **hierarchical content system**: - **Cells** - Discussion boards (require ENS to create) - **Posts** - Threads within cells (any authenticated user) - **Comments** - Replies to posts (any authenticated user) - **Votes** - Upvote/downvote (any authenticated user) **Implementation Pattern:** ```tsx const { createPost, createComment, vote, posts, comments } = useContent(); // Create content await createPost({ cellId, title, content }); await createComment({ postId, content }); await vote({ targetId: postId, isUpvote: true }); // Display content with grouped data const { postsByCell, commentsByPost } = useContent(); const cellPosts = postsByCell[cellId] || []; const postComments = commentsByPost[postId] || []; ``` ### 3. Permission System Implement **role-based access control**: | Action | Anonymous | Wallet | ENS Verified | |--------|-----------|--------|--------------| | View | ✅ | ✅ | ✅ | | Vote | ✅ | ✅ | ✅ | | Comment | ✅ | ✅ | ✅ | | Post | ✅ | ✅ | ✅ | | Create Cell | ❌ | ❌ | ✅ | | Moderate | ❌ | ❌ | ✅ (own cells) | **Implementation Pattern:** ```tsx const { canPost, canComment, canCreateCell, canModerate, check } = usePermissions(); // Show/hide UI based on permissions {canCreateCell && } // Detailed permission check const { allowed, reason } = check('canPost'); {!allowed &&

{reason}

} // Conditional features {canModerate(cellId) && } ``` ### 4. User Identity Display Implement **flexible identity rendering** for both wallet and anonymous users: **Implementation Pattern:** ```tsx const { displayName, callSign, ensName, ensAvatar } = useUserDisplay(address); // Render user identity
{ensAvatar && } {displayName} {/* Verification badges */} {callSign && Call Sign} {ensName && ENS} {isAnonymous(address) && Anonymous}
// Detect anonymous users function isAnonymous(address: string): boolean { return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(address); } ``` ### 5. Onboarding Flow Create a **3-step wizard for wallet users**, **instant access for anonymous**: **Wallet Wizard Steps:** 1. **Connect Wallet** - Choose wallet connector 2. **Verify Ownership** - Check for ENS (optional, can skip) 3. **Delegate Key** - Generate browser keys for better UX **Anonymous Flow:** - Click "Continue Anonymously" → Wizard closes immediately - Show call sign setup in header dropdown - Allow interaction immediately **Implementation Pattern:** ```tsx // In wallet wizard const handleStepComplete = (step) => { if (step === 1 && verificationStatus === EVerificationStatus.ANONYMOUS) { // Close wizard immediately for anonymous users onComplete(); return; } // Continue to next step for wallet users setCurrentStep(step + 1); }; ``` ### 6. Real-Time Updates Implement **optimistic UI updates** with network sync: **Implementation Pattern:** ```tsx const { pending, lastSync } = useContent(); const isPending = pending.isPending(post.id); // Show pending state {isPending && } // Listen to pending changes useEffect(() => { return pending.onChange(() => { // Update UI when pending state changes }); }, []); // Network status {!network.isConnected && } ``` ### 7. Call Sign System Allow **all users (wallet and anonymous) to set call signs**: **Implementation Patterns:** ```tsx // Anonymous users: Show inline prompt {currentUser?.verificationStatus === EVerificationStatus.ANONYMOUS && !currentUser.callSign && ( )} // All users: Header dropdown option setCallSignDialogOpen(true)}> {currentUser?.callSign ? 'Update' : 'Set'} Call Sign // Update profile await updateProfile({ callSign: 'my_username' }); // Display shows call sign when available {callSign || 'Anonymous User'} ``` ### 8. Moderation System Implement **cell-based moderation** (no global censorship): **Implementation Pattern:** ```tsx const { moderate } = useContent(); const { canModerate } = usePermissions(); if (canModerate(cellId)) { // Moderate content await moderate.post(cellId, postId, 'Spam'); await moderate.comment(cellId, commentId, 'Off-topic'); await moderate.user(cellId, userAddress, 'Harassment'); // Unmoderate await moderate.unpost(cellId, postId); } // Hide moderated content by default const visiblePosts = posts.filter(p => !p.moderated); // Admin toggle to show moderated const [showModerated, setShowModerated] = useUIState('showModerated', false); const displayPosts = showModerated ? posts : visiblePosts; ``` ### 9. Relevance Scoring UI Display **relevance scores** to help users find quality content: **Implementation Pattern:** ```tsx // Sort by relevance const sortedPosts = [...posts].sort((a, b) => (b.relevanceScore || 0) - (a.relevanceScore || 0) ); // Show relevance details {post.relevanceDetails && (
Score: {post.relevanceDetails.finalScore.toFixed(1)} Upvotes: {post.relevanceDetails.upvotes} Verified: {post.relevanceDetails.verifiedUpvotes}
)} ``` ### 10. Network Status Indicator Show **Waku network connection state**: **Implementation Pattern:** ```tsx const { isConnected, statusMessage, isHydrated } = useNetwork(); // Health indicator
{statusMessage}
// Wait for hydration before showing content {isHydrated ? : } ``` ## Critical Implementation Details ### State Preservation for Anonymous Users **Problem**: Wallet sync effects may clear anonymous users **Solution**: Check verification status before clearing: ```tsx // In useAuth hook useEffect(() => { if (!wallet.isConnected && currentUser && currentUser.verificationStatus !== EVerificationStatus.ANONYMOUS) { // Only clear non-anonymous users clearUser(); } }, [wallet.isConnected, currentUser]); ``` ### Verification Status Preservation **Problem**: Profile updates may reset verification status **Solution**: Force preservation when updating: ```tsx const updateProfile = async (updates) => { const res = await client.userIdentityService.updateProfile(address, updates); const updated = { ...currentUser, ...res.identity, verificationStatus: currentUser.verificationStatus, // Preserve! }; setOpchanState(prev => ({ ...prev, session: { ...prev.session, currentUser: updated } })); }; ``` ### Verification Status Mapping **Problem**: Status mapper doesn't handle ANONYMOUS **Solution**: Add ANONYMOUS case: ```tsx private mapVerificationStatus(status: string): EVerificationStatus { switch (status) { case EVerificationStatus.ANONYMOUS: return EVerificationStatus.ANONYMOUS; // Add this! case EVerificationStatus.WALLET_CONNECTED: return EVerificationStatus.WALLET_CONNECTED; // ... other cases default: return EVerificationStatus.WALLET_UNCONNECTED; } } ``` ### Message Verification **Problem**: Network peers must accept anonymous messages **Solution**: Make delegationProof optional in verification: ```tsx async verify(message: OpchanMessage): Promise { // Verify message signature (always required) if (!verifyMessageSignature(message)) return false; // If has delegationProof, verify it (wallet user) if (message.delegationProof) { return verifyDelegationProof(message.delegationProof, message.author); } // Anonymous message - verify session ID format return isValidSessionId(message.author); } ``` ## UI/UX Guidelines ### Design System - **Colors**: Dark theme with cyan/blue accents - **Typography**: Monospace fonts for technical aesthetic - **Components**: Use shadcn/ui patterns - **Spacing**: Consistent spacing with Tailwind scale - **Icons**: Lucide React icons ### Key UI Components to Build 1. **Header Component** - Logo/branding - Navigation (Home, Cells, Profile, Bookmarks) - Auth button (Connect/User dropdown) - Network status indicator - Call sign setup for anonymous users 2. **Wallet Wizard** - Step indicator (1/2/3) - Wallet connection (Step 1) - ENS verification (Step 2) - Key delegation (Step 3) - Anonymous bypass option 3. **Cell List** - Grid/list view of cells - Active member count - Recent activity indicator - "Create Cell" button (ENS only) 4. **Post List** - Post cards with vote buttons - Author display with badges - Relevance score indicator - Create post form (if has permission) - Call sign prompt (anonymous without call sign) 5. **Post Detail** - Full post content (markdown) - Vote buttons - Comment thread - Comment form - Moderation tools (if cell admin) 6. **Author Display** - Handle wallet addresses (0x...) - Handle anonymous (UUID pattern) - Show call sign when available - Show ENS when available - Badge system (ENS, Call Sign, Anonymous) 7. **Profile Page** - User information - Call sign management - Display preferences - Security section (wallet: delegation status, anonymous: session info) 8. **Inline Call Sign Input** - For anonymous users without call sign - Contextual placement (where interaction blocked) - Validation (3-20 chars, alphanumeric) - Immediate feedback ## Data Patterns ### Content Grouping ```tsx // Group posts by cell const { postsByCell } = useContent(); const cellPosts = postsByCell[cellId] || []; // Group comments by post const { commentsByPost } = useContent(); const postComments = commentsByPost[postId] || []; // User verification status cache const { userVerificationStatus } = useContent(); const isVerified = userVerificationStatus[authorAddress]?.isVerified; ``` ### Bookmarks ```tsx const { bookmarks, togglePostBookmark, toggleCommentBookmark } = useContent(); // Check if bookmarked const isBookmarked = bookmarks.some(b => b.targetId === post.id && b.type === 'post'); // Toggle bookmark await togglePostBookmark(post, cellId); await toggleCommentBookmark(comment, postId); ``` ### Sorting ```tsx // Sort by relevance (default) const sorted = [...posts].sort((a, b) => (b.relevanceScore || 0) - (a.relevanceScore || 0) ); // Sort by time const recent = [...posts].sort((a, b) => b.timestamp - a.timestamp); // Sort by votes const popular = [...posts].sort((a, b) => (b.upvotes?.length || 0) - (a.upvotes?.length || 0) ); ``` ## Critical Success Factors ### ✅ Must Have 1. **Immediate Engagement** - Anonymous users can interact without barriers 2. **Clear Permission Feedback** - Users know what they can/can't do and why 3. **Identity Flexibility** - Support wallet addresses, ENS names, call signs, and anonymous 4. **Optimistic UI** - Instant feedback with background sync 5. **Network Resilience** - Handle disconnections gracefully 6. **State Preservation** - Don't clear anonymous users on wallet events 7. **Verification Preservation** - Maintain verification status through profile updates ### ⚠️ Common Pitfalls to Avoid 1. ❌ **Clearing anonymous sessions** - Check verification status before clearing users 2. ❌ **Hardcoding wallet-only flows** - Always support anonymous mode 3. ❌ **Forgetting to add ANONYMOUS to enums** - Update all status mappers 4. ❌ **Blocking anonymous users** - They should interact freely (except cell creation) 5. ❌ **Not showing call sign option** - Make it prominent for anonymous users 6. ❌ **Complex onboarding** - Keep anonymous flow instant (no wizard steps) 7. ❌ **Assuming addresses are 0x format** - Support both wallet addresses and UUIDs ## Complete Feature Checklist ### Authentication & Identity - [ ] Anonymous session creation - [ ] Wallet connection (Ethereum) - [ ] ENS verification - [ ] Key delegation (wallet users) - [ ] Call sign setup (all users) - [ ] Display preferences - [ ] Session persistence - [ ] Disconnect/exit flows ### Content Features - [ ] Cell creation (ENS only) - [ ] Post creation - [ ] Comment creation (nested threading) - [ ] Upvote/downvote - [ ] Bookmark management - [ ] Markdown support - [ ] Content sorting (relevance, new, top) - [ ] Author attribution ### Moderation - [ ] Moderate posts - [ ] Moderate comments - [ ] Moderate users - [ ] Admin-only access - [ ] Show/hide moderated toggle - [ ] Moderation reasons ### UI Components - [ ] Header with auth state - [ ] Wallet wizard (3 steps) - [ ] Cell list with stats - [ ] Post list/feed - [ ] Post detail with comments - [ ] Comment threads - [ ] Vote buttons - [ ] Author display with badges - [ ] Network status indicator - [ ] Loading states - [ ] Error handling - [ ] Call sign input (inline + dialog) - [ ] Profile page ### Developer Experience - [ ] TypeScript strict mode - [ ] Proper error handling - [ ] Loading states - [ ] Optimistic updates - [ ] Hot reload support - [ ] Build optimization ## Technical Stack ### Required Dependencies ```json { "dependencies": { "@opchan/react": "^1.1.0", "@opchan/core": "^1.0.0", "react": "^18.3.0", "react-dom": "^18.3.0", "react-router-dom": "^6.20.0", "@tanstack/react-query": "^5.0.0", "wagmi": "^2.0.0", "viem": "^2.0.0", "buffer": "^6.0.3" }, "devDependencies": { "@types/react": "^18.3.0", "typescript": "^5.3.0", "vite": "^5.0.0", "tailwindcss": "^3.4.0" } } ``` ### Recommended UI Libraries - **shadcn/ui** - Accessible component primitives - **Radix UI** - Unstyled accessible components - **Tailwind CSS** - Utility-first styling - **Lucide React** - Icon library - **date-fns** - Date formatting - **react-hook-form** - Form handling - **zod** - Schema validation ## Example App Structure ``` src/ ├── main.tsx # Entry point with OpChanProvider ├── App.tsx # Router setup ├── hooks/ │ └── index.ts # Re-export @opchan/react hooks ├── components/ │ ├── ui/ # shadcn/ui components │ │ ├── wallet-wizard.tsx # 3-step onboarding │ │ ├── wallet-connection-step.tsx │ │ ├── verification-step.tsx │ │ ├── delegation-step.tsx │ │ ├── author-display.tsx # User identity display │ │ ├── inline-callsign-input.tsx │ │ ├── call-sign-setup-dialog.tsx │ │ └── ... # Other UI primitives │ ├── Header.tsx # Navigation + auth │ ├── PostList.tsx # Post feed │ ├── PostDetail.tsx # Single post view │ ├── PostCard.tsx # Post preview │ ├── CommentCard.tsx # Comment display │ ├── CellList.tsx # Cell grid │ └── CreateCellDialog.tsx # Cell creation modal ├── pages/ │ ├── Dashboard.tsx # Landing page │ ├── Index.tsx # Cell list page │ ├── CellPage.tsx # Cell detail │ ├── PostPage.tsx # Post detail │ ├── ProfilePage.tsx # User profile │ ├── BookmarksPage.tsx # Saved content │ └── NotFound.tsx # 404 page └── utils/ └── sorting.ts # Content sorting utilities ``` ## Code Examples ### Complete Anonymous Flow Implementation ```tsx // 1. Wallet Wizard with Anonymous Option export function WalletWizard() { const { startAnonymous, verificationStatus } = useAuth(); const handleAnonymous = async () => { const sessionId = await startAnonymous(); if (sessionId) { onComplete(); // Close wizard immediately } }; return ( {/* Wallet connection UI */} {/* Anonymous option */}
Or

You can post, comment, and vote (but not create cells)

); } // 2. Header with Call Sign Access export function Header() { const { currentUser } = useAuth(); const [callSignOpen, setCallSignOpen] = useState(false); return (
{currentUser && ( {currentUser.displayName} {currentUser.verificationStatus === EVerificationStatus.ANONYMOUS && ( setCallSignOpen(true)}> {currentUser.callSign ? 'Update' : 'Set'} Call Sign )} Profile {currentUser.verificationStatus === EVerificationStatus.ANONYMOUS ? 'Exit Anonymous' : 'Disconnect'} )}
); } // 3. Author Display with Anonymous Support export function AuthorDisplay({ address }: { address: string }) { const { displayName, callSign, ensName } = useUserDisplay(address); const isAnonymous = /^[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}$/i.test(address); return (
{displayName} {isAnonymous ? ( callSign ? ( Call Sign ) : ( Anonymous ) ) : ( ensName && ENS )}
); } // 4. Inline Call Sign Input export function InlineCallSignInput() { const { currentUser, updateProfile } = useAuth(); const [callSign, setCallSign] = useState(''); // Only show for anonymous users without call sign if (currentUser?.verificationStatus !== EVerificationStatus.ANONYMOUS || currentUser?.callSign) { return null; } const handleSubmit = async () => { const success = await updateProfile({ callSign }); if (success) { toast({ title: 'Call Sign Set!' }); } }; return (

Set a call sign to personalize your identity

setCallSign(e.target.value)} maxLength={20} />
); } // 5. Permission-Based Content Forms export function PostDetail({ postId }: { postId: string }) { const { permissions, currentUser } = useForum(); const { createComment } = useContent(); return (
{/* Post content */} {/* Call sign suggestion for anonymous users */} {currentUser?.verificationStatus === EVerificationStatus.ANONYMOUS && !currentUser.callSign && permissions.canComment && ( )} {/* Comment form */} {permissions.canComment && ( createComment({ postId, content })} /> )} {/* Blocked state */} {!permissions.canComment && (

Connect your wallet to comment

)}
); } ``` ## Testing Your Implementation ### Manual Test Checklist **Anonymous Flow:** - [ ] Can start anonymous session - [ ] Can post without call sign - [ ] Can comment without call sign - [ ] Can vote without call sign - [ ] Can set call sign from header - [ ] Call sign appears in author display - [ ] Session persists after call sign update - [ ] Cannot create cells - [ ] Can exit anonymous session **Wallet Flow:** - [ ] Can connect wallet - [ ] ENS resolves automatically - [ ] Can delegate browser keys - [ ] Can create cells (if ENS verified) - [ ] Can post without repeated signatures - [ ] Can moderate own cells - [ ] Can update profile - [ ] Can disconnect wallet **Edge Cases:** - [ ] Anonymous user sets call sign → remains anonymous - [ ] Wallet disconnects → doesn't clear anonymous users - [ ] Profile update → preserves verification status - [ ] Network disconnect → shows reconnect option - [ ] Content pending → shows sync status ## Performance Optimization 1. **Memoization** - Use React.memo for expensive components 2. **Virtual Lists** - For long comment threads 3. **Lazy Loading** - Code-split routes 4. **Image Optimization** - Lazy load ENS avatars 5. **Bundle Size** - Tree-shake unused components ## Deployment ### Build Commands ```bash # Build all packages npm run build # Build for production cd app npm run build # Output: app/dist/ ``` ### Environment Variables ```bash # .env.production VITE_REOWN_SECRET=your_production_reown_id ``` ### Static Hosting Deploy `app/dist/` to: - Vercel - Netlify - GitHub Pages - IPFS Configure SPA routing: ```json { "routes": [ { "handle": "filesystem" }, { "src": "/(.*)", "dest": "/index.html" } ] } ``` ## Summary Building with `@opchan/react` requires understanding: 1. **Dual authentication model** - Anonymous and wallet modes 2. **Permission-based UI** - Show features based on capabilities 3. **Key delegation** - Browser keys reduce wallet friction 4. **Local-first architecture** - IndexedDB with network sync 5. **Real-time updates** - React state tied to network messages 6. **Identity flexibility** - Handle addresses, ENS, call signs, and sessions 7. **Verification preservation** - Critical for anonymous users 8. **Message verification** - Optional delegation proofs for protocol v2 Follow these patterns and you'll build a robust, user-friendly decentralized forum that works for everyone - from crypto natives to complete newcomers. --- **For complete examples, see the reference implementation in `/app`**