# @opchan/core Architecture Guide Deep dive into the architecture, design patterns, and implementation details of the OpChan Core SDK. --- ## Table of Contents 1. [System Overview](#system-overview) 2. [Core Architecture](#core-architecture) 3. [Data Flow](#data-flow) 4. [Key Subsystems](#key-subsystems) 5. [Cryptographic Design](#cryptographic-design) 6. [Storage Strategy](#storage-strategy) 7. [Network Layer](#network-layer) 8. [Design Patterns](#design-patterns) 9. [Performance Considerations](#performance-considerations) 10. [Security Model](#security-model) --- ## System Overview OpChan Core is a decentralized forum infrastructure built on three pillars: 1. **Cryptographic Identity** - Ed25519 key delegation with wallet authorization 2. **P2P Messaging** - Waku protocol for decentralized communication 3. **Local-First Storage** - IndexedDB with in-memory caching ### Design Philosophy - **Local-First**: All data persisted locally, network as synchronization mechanism - **Optimistic UI**: Immediate feedback, eventual consistency - **Privacy-Preserving**: No centralized servers, peer-to-peer architecture - **Framework-Agnostic**: Pure TypeScript library, no UI framework dependencies --- ## Core Architecture ``` ┌───────────────────────────────────────────────────────────────────┐ │ OpChanClient │ │ Entry point orchestrating all subsystems │ └───────────────────────────────────────────────────────────────────┘ │ ┌────────────────────────┼────────────────────────┐ │ │ │ ▼ ▼ ▼ ┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐ │ MessageManager │ │ LocalDatabase │ │ DelegationManager│ │ │ │ │ │ │ │ - WakuNode │ │ - IndexedDB │ │ - Key Generation │ │ - Reliable │ │ - In-memory │ │ - Signing │ │ Messaging │ │ Cache │ │ - Verification │ │ - Health Monitor │ │ - Validation │ │ │ └──────────────────┘ └──────────────────┘ └──────────────────┘ │ │ │ │ │ │ ▼ ▼ ▼ ┌──────────────────────────────────────────────────────────────────┐ │ Service Layer │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ ForumActions │ │ Identity │ │ Relevance │ │ │ │ │ │ Service │ │ Calculator │ │ │ │ - Create │ │ │ │ │ │ │ │ - Moderate │ │ - ENS Lookup │ │ - Scoring │ │ │ │ - Vote │ │ - Profiles │ │ - Time Decay │ │ │ └──────────────┘ └──────────────┘ └──────────────┘ │ └──────────────────────────────────────────────────────────────────┘ ``` ### Component Responsibilities #### OpChanClient - **Role**: Facade pattern - single entry point for all operations - **Responsibilities**: - Instantiate and wire all services - Configure environment - Initialize message manager with Waku config - Provide unified API surface #### MessageManager - **Role**: Network abstraction layer - **Responsibilities**: - Manage Waku node lifecycle - Handle message sending/receiving - Monitor network health - Implement reliable messaging (retries, acknowledgments) - Manage subscriptions to content topics #### LocalDatabase - **Role**: Persistence and caching layer - **Responsibilities**: - IndexedDB operations (async) - In-memory cache (sync) - Message validation - Deduplication - State management (pending, syncing) #### DelegationManager - **Role**: Cryptographic signing system - **Responsibilities**: - Generate Ed25519 keypairs - Create wallet-authorized delegations - Sign messages with browser keys - Verify message signatures - Verify delegation proofs --- ## Data Flow ### Outbound Message Flow (Creating Content) ``` 1. User Action └─> ForumActions.createPost() │ ├─> Validate permissions (wallet or anonymous) │ ├─> Create unsigned message with UUID │ ├─> DelegationManager.signMessage() │ ├─> Load cached delegation │ ├─> Check expiry │ ├─> Sign with browser private key (Ed25519) │ └─> Attach signature + browserPubKey + delegationProof │ ├─> LocalDatabase.applyMessage() │ ├─> Validate signature │ ├─> Check for duplicates │ ├─> Store in cache │ └─> Persist to IndexedDB │ ├─> LocalDatabase.markPending() │ ├─> Call updateCallback() [UI refresh] │ └─> MessageManager.sendMessage() ├─> Encode with protobuf ├─> Send via Waku └─> Wait for acknowledgment └─> LocalDatabase.clearPending() ``` ### Inbound Message Flow (Receiving Content) ``` 1. Waku Network └─> WakuNodeManager receives message │ ├─> Decode protobuf │ ├─> ReliableMessaging handles deduplication │ └─> MessageService.onMessageReceived() callback │ └─> LocalDatabase.applyMessage() │ ├─> MessageValidator.isValidMessage() │ ├─> Check required fields │ ├─> Verify signature (Ed25519) │ ├─> If delegationProof: │ │ ├─> Verify auth message format │ │ ├─> Verify wallet signature (viem) │ │ └─> Check expiry (optional) │ └─> If anonymous: │ └─> Verify session ID format (UUID) │ ├─> Check duplicate (type:id:timestamp key) │ ├─> Store in appropriate cache collection │ ├─> cells[id] │ ├─> posts[id] │ ├─> comments[id] │ ├─> votes[targetId:author] │ ├─> moderations[key] │ └─> userIdentities[address] │ ├─> Persist to IndexedDB │ ├─> Update lastSync timestamp │ └─> Return true (new message) ``` --- ## Key Subsystems ### 1. Delegation Subsystem **Purpose**: Reduce wallet signature prompts while maintaining cryptographic security. **Components**: - **DelegationManager** - Core delegation logic - **DelegationStorage** - IndexedDB persistence - **DelegationCrypto** - Ed25519 signing/verification **Delegation Process (Wallet)**: ```typescript // 1. Generate browser keypair const keypair = DelegationCrypto.generateKeypair(); // { publicKey: string, privateKey: string } // 2. Create authorization message const authMessage = ` Authorize browser key: Public Key: ${keypair.publicKey} Wallet: ${walletAddress} Expires: ${new Date(expiryTimestamp).toISOString()} Nonce: ${nonce} `; // 3. Sign with wallet (happens once) const walletSignature = await signFunction(authMessage); // 4. Store delegation const delegation: WalletDelegationInfo = { authMessage, walletSignature, expiryTimestamp, walletAddress, browserPublicKey: keypair.publicKey, browserPrivateKey: keypair.privateKey, nonce }; await DelegationStorage.store(delegation); // 5. Subsequent messages signed with browser key const signature = DelegationCrypto.signRaw(messageJson, privateKey); ``` **Delegation Process (Anonymous)**: ```typescript // 1. Generate browser keypair const keypair = DelegationCrypto.generateKeypair(); // 2. Generate session ID (UUID) const sessionId = crypto.randomUUID(); // 3. Store anonymous delegation const delegation: AnonymousDelegationInfo = { sessionId, browserPublicKey: keypair.publicKey, browserPrivateKey: keypair.privateKey, expiryTimestamp, nonce }; // 4. No wallet signature required // User address = sessionId ``` **Verification Process**: ```typescript // 1. Verify message signature const messagePayload = JSON.stringify({ ...message, signature: undefined, browserPubKey: undefined, delegationProof: undefined }); const signatureValid = DelegationCrypto.verifyRaw( messagePayload, message.signature, message.browserPubKey ); if (!signatureValid) return false; // 2. Verify delegation authorization if (message.delegationProof) { // Wallet user - verify delegation proof const proofValid = await DelegationCrypto.verifyWalletSignature( message.delegationProof.authMessage, message.delegationProof.walletSignature, message.delegationProof.walletAddress ); // Check auth message contains browser key, wallet address, expiry const authMessageValid = message.delegationProof.authMessage.includes(message.browserPubKey) && message.delegationProof.authMessage.includes(message.author) && message.delegationProof.authMessage.includes( message.delegationProof.expiryTimestamp.toString() ); return proofValid && authMessageValid; } else { // Anonymous user - verify session ID format return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test( message.author ); } ``` --- ### 2. Storage Subsystem **Purpose**: Fast local access with persistent storage. **Two-Tier Architecture**: 1. **In-Memory Cache** (Tier 1) - Synchronous access - Fast reads for UI rendering - Volatile (resets on page reload) 2. **IndexedDB** (Tier 2) - Asynchronous access - Persistent across sessions - Hydrates cache on startup **IndexedDB Schema**: ```typescript const schema = { // Content stores (keyPath = 'id') cells: { keyPath: 'id' }, posts: { keyPath: 'id' }, comments: { keyPath: 'id' }, // Votes (keyPath = 'key', composite: targetId:author) votes: { keyPath: 'key' }, // Moderations (keyPath = 'key', composite varies by type) moderations: { keyPath: 'key' }, // User identities (keyPath = 'address') userIdentities: { keyPath: 'address', indexes: [] }, // Bookmarks (keyPath = 'id') bookmarks: { keyPath: 'id', indexes: [{ name: 'by_userId', keyPath: 'userId' }] }, // Auth/state stores (keyPath = 'key') userAuth: { keyPath: 'key' }, delegation: { keyPath: 'key' }, uiState: { keyPath: 'key' }, meta: { keyPath: 'key' } }; ``` **Cache Synchronization**: ``` ┌─────────────────────────────────────────────────────┐ │ Write Operation │ │ │ │ 1. Update in-memory cache (immediate) │ │ 2. Write to IndexedDB (async, fire-and-forget) │ │ 3. Notify listeners (for UI update) │ └─────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────┐ │ Read Operation │ │ │ │ 1. Check in-memory cache (fast path) │ │ 2. If not found, return null/empty │ │ (IndexedDB only used for hydration on startup) │ └─────────────────────────────────────────────────────┘ ┌─────────────────────────────────────────────────────┐ │ Startup Hydration │ │ │ │ 1. Open IndexedDB connection │ │ 2. Load all stores in parallel │ │ 3. Populate in-memory cache │ │ 4. Ready for use │ └─────────────────────────────────────────────────────┘ ``` **Deduplication**: Messages are deduplicated using a composite key: ```typescript const messageKey = `${message.type}:${message.id}:${message.timestamp}`; ``` This allows the same logical message to be received multiple times without creating duplicates. --- ### 3. Identity Subsystem **Purpose**: Resolve and cache user identities with ENS integration. **Resolution Strategy**: ``` ┌────────────────────────────────────────────────────┐ │ Identity Resolution Flow │ │ │ │ 1. Check LocalDatabase cache │ │ └─> If found and fresh (< 5 min): return │ │ │ │ 2. Check if Ethereum address │ │ └─> If not (anonymous/UUID): return null │ │ │ │ 3. Resolve ENS via PublicClient │ │ ├─> Get ENS name │ │ ├─> Get ENS avatar │ │ └─> Cache result for 5 minutes │ │ │ │ 4. Build UserIdentity object │ │ ├─> address │ │ ├─> ensName │ │ ├─> ensAvatar │ │ ├─> callSign (from profile messages) │ │ ├─> displayPreference │ │ ├─> displayName (computed) │ │ ├─> verificationStatus (computed) │ │ └─> lastUpdated │ │ │ │ 5. Store in LocalDatabase │ │ │ │ 6. Return identity │ └────────────────────────────────────────────────────┘ ``` **Display Name Resolution**: ```typescript function getDisplayName(identity: UserIdentity): string { // Priority 1: Call sign (if preference is CALL_SIGN) if ( identity.callSign && identity.displayPreference === EDisplayPreference.CALL_SIGN ) { return identity.callSign; } // Priority 2: ENS name if (identity.ensName) { return identity.ensName; } // Priority 3: Shortened address return `${identity.address.slice(0, 6)}...${identity.address.slice(-4)}`; } ``` **Profile Updates**: User profile updates are broadcast as USER_PROFILE_UPDATE messages: ```typescript { type: MessageType.USER_PROFILE_UPDATE, id: uuid(), timestamp: Date.now(), author: userAddress, callSign: 'alice', // optional displayPreference: EDisplayPreference.CALL_SIGN, signature: '...', browserPubKey: '...', delegationProof: { ... } } ``` These messages update the `userIdentities` cache, propagating changes across the network. --- ### 4. Relevance Subsystem **Purpose**: Score content based on engagement, verification, time, and moderation. **Scoring Algorithm**: ```typescript interface RelevanceFactors { base: 100; engagement: { upvoteWeight: 10; commentWeight: 3; }; verification: { authorBonus: 20; // ENS verified author upvoteBonus: 5; // Per ENS verified upvoter commenterBonus: 10; // Per ENS verified commenter }; timeDecay: { halfLifeDays: 7; formula: 'exponential'; // exp(-0.693 * days / halfLife) }; moderation: { penalty: 0.5; // 50% reduction if moderated }; } function calculateScore( post: Post, votes: Vote[], comments: Comment[], verifications: Map
, moderations: Map