All Blog

Web3 Community Building: Real-Time Engagement Tools, Strategies & Implementation Guide

8 min read
May 27, 2026

web3-community-building-guide

Web3 communities don't fail because of bad tokenomics or weak roadmaps. They fail because members have no reason to come back tomorrow. Discord servers with 50,000 members and 12 active chatters. Telegram groups where the only activity is price speculation. Twitter Spaces that nobody remembers attending.

The missing piece isn't another governance token or quest platform. It's real-time engagement infrastructure—voice rooms where members can actually talk, live town halls where the team shows their faces, and persistent chat channels that feel alive 24/7.

This guide covers the complete web3 community building stack: strategy frameworks, platform selection, and—critically—how to implement the real-time communication layer that transforms passive holders into active participants.

Why Most Web3 Communities Die (And What Survivors Do Differently)

The median lifespan of a Web3 community is under 5 months from launch to ghost town. Research across thousands of projects reveals three failure patterns:

Pattern 1: Announcement-Only Channels Communities built around one-way broadcasts (updates, airdrops, partnerships) have no social gravity. Members join, get what they came for, and leave.

Pattern 2: Text-Only Engagement Text chat creates a 90-9-1 distribution: 90% lurk, 9% occasionally react, 1% produce content. Voice and video dramatically shift this ratio because the barrier to participation drops—talking is easier than typing thoughtful messages.

Pattern 3: No Ritual or Rhythm Communities without recurring live events (weekly AMAs, daily voice lounges, monthly town halls) lose the "appointment viewing" effect that drives habit formation.

What Surviving Communities Share

Projects that maintain engagement beyond 12 months share structural elements that create compounding engagement:

ElementImplementationEngagement Impact
Synchronous voice eventsWeekly AMAs, daily voice lounges3-5x message volume on event days
Live video town hallsMonthly team updates with Q&A40% higher retention vs text-only updates
Persistent async channelsToken-gated topic channelsContinuous low-friction participation
On-chain triggersWallet events → community notificationsReal-time relevance and urgency

The rest of this guide shows you how to build all four.

Web3 Community Building Strategy Framework

Before selecting tools, you need a strategy that maps community activities to business outcomes. The RIVER framework provides this structure:

The RIVER Framework for Web3 Community Management

R — Rituals (Recurring events that create habit loops)

  • Daily: Voice lounge open hours (casual, no agenda)
  • Weekly: AMA with team or guest speakers
  • Monthly: Town hall with roadmap updates + live vote
  • Quarterly: Community awards + contributor recognition

I — Incentives (Why members participate beyond speculation)

  • Contribution rewards (governance tokens for active participants)
  • Access gates (NFT holders get exclusive voice channels)
  • Reputation systems (on-chain credentials for consistent participation)

V — Voice (Synchronous audio/video communication)

  • Voice rooms for casual conversation and AMAs
  • Live streaming for large-scale town halls
  • Screen sharing for technical demos and tutorials

E — Engagement Loops (Mechanics that bring people back)

  • On-chain event notifications (whale moves, governance proposals)
  • Daily discussion prompts in chat channels
  • Voice room highlights posted as async content

R — Reputation (On-chain identity and social capital)

  • Attendance POAPs for live events
  • Contribution NFTs for active builders
  • Governance weight based on participation history

Mapping Strategy to Infrastructure

Each element of RIVER requires specific technical infrastructure:

RIVER ElementRequired InfrastructureKey Capability
RitualsScheduling + Voice RoomsLow-latency audio, room management
IncentivesSmart contracts + ChatToken gating, wallet verification
VoiceRTC Engine + Room SDKSub-300ms latency, 1000+ listeners
Engagement LoopsWebhooks + Chat APIOn-chain event → real-time notification
ReputationOn-chain data + DisplayWallet-linked profiles, badge display

Web3 Community Platform Comparison

The web3 community platform landscape splits into three tiers: general-purpose platforms, Web3-native tools, and infrastructure SDKs for custom builds.

Tier 1: General-Purpose Platforms

PlatformStrengthsWeb3 Limitations
DiscordFamiliar UX, bot ecosystem, voice channelsCentralized, no wallet-native auth, server raids, platform risk
TelegramMobile-first, large group support, fastLimited voice features, no thread structure, bot-heavy spam
FarcasterDecentralized, on-chain social graphSmall user base, limited real-time features
Twitter/X SpacesViral reach, discovery, large audiencesNo persistence, no token-gating, unreliable infrastructure

Tier 2: Web3-Native Community Tools

ToolFunctionGap
DeBoxDID-based communities + DAO toolsLimited voice/live capabilities
Collab.LandToken-gating for Discord/TelegramBolt-on, not integrated experience
Guild.xyzRole management + multi-chain accessNo communication layer
SnapshotOff-chain governance votingAsync only, no real-time discussion
Dework/WonderverseTask/bounty managementNo community communication
CoordinapeContributor rewards allocationNo real-time coordination
CommonwealthGovernance forum + proposalsText-only, no voice/live

Tier 3: Communication Infrastructure (Build Your Own Platform)

For projects that need full control over their community experience—custom token-gating logic, branded interfaces, on-chain integrated notifications—you need communication SDKs that provide the building blocks.

This is where Tencent RTC fits the stack. Rather than using Discord (centralized, limited customization) or building audio/video from scratch (years of engineering), TRTC provides production-ready real-time communication that you assemble into your own web3 community platform:

  • Voice Rooms: For AMAs, casual conversations, and token-holder lounges
  • Live Streaming: For town halls with 10,000+ concurrent viewers at sub-300ms latency
  • Chat Channels: Persistent messaging with custom moderation and token-gating

The key advantage for web3 projects: you own the infrastructure. No risk of Discord banning your server, no dependency on Telegram's API limits, and full control over wallet-based authentication.

When to Use Each Tier

Community StageRecommended ApproachReason
0-500 membersTier 1 (Discord) + Tier 2 (Collab.Land)Low effort, test community fit
500-5,000 membersTier 1 + Custom voice/live (Tier 3)Need branded experiences, outgrow Discord limits
5,000+ membersFully custom (Tier 3)Control, scalability, multi-chain support
DAO with governanceTier 2 (Snapshot) + Tier 3 (voice + chat)Governance needs real-time discussion

Web3 Community Management: The Real-Time Engagement Stack

Effective web3 community management requires more than moderators and rules. It requires a technical stack that enables synchronous engagement at scale.

Architecture Overview

┌─────────────────────────────────────────────────────┐
│                  Community Frontend                   │
│         (Web App / Mobile App / dApp)                │
├─────────────────────────────────────────────────────┤
│                                                      │
│  ┌──────────┐  ┌──────────┐  ┌──────────────────┐  │
│  │  Voice   │  │   Live   │  │   Chat Engine    │  │
│  │  Rooms   │  │  Stream  │  │   (Channels)     │  │
│  │  (TRTC)  │  │  (TRTC)  │  │   (TRTC Chat)   │  │
│  └────┬─────┘  └────┬─────┘  └────────┬─────────┘  │
│       │              │                  │            │
├───────┴──────────────┴──────────────────┴───────────┤
│              Wallet Auth + Token Gate Layer           │
│         (MetaMask / WalletConnect / Privy)           │
├─────────────────────────────────────────────────────┤
│              On-Chain Event Webhooks                  │
│     (Contract events → Community notifications)      │
└─────────────────────────────────────────────────────┘

Component Breakdown

1. Wallet-Based Authentication Replace username/password with wallet signatures. This enables:

  • Token-gated access (only holders of specific NFTs/tokens can enter rooms)
  • On-chain reputation display (show governance participation, contribution history)
  • Cross-platform identity (same wallet = same identity across your community tools)

2. Voice Rooms (AMAs & Casual Lounges) Synchronous audio with speaker/listener roles:

  • Host controls: mute, invite to speak, remove participants
  • Capacity: Up to 50 speakers, 1,000+ listeners
  • Token-gating: Verify wallet balance before room entry

3. Live Streaming (Town Halls) One-to-many broadcast with interactive features:

  • Ultra-low latency (<300ms) for real-time Q&A
  • Screen sharing for roadmap presentations
  • Chat overlay for audience participation
  • Recording for async replay

4. Chat Channels (Persistent Communication) Structured messaging with Web3-native features:

  • Topic-based channels (general, governance, dev, alpha)
  • Token-gated channels (NFT holders only, whale channels)
  • Webhook integrations (on-chain events → chat notifications)
  • Message reactions, threads, and pinned messages

Implementation Guide: Voice Room AMA with Token-Gating

Voice AMAs are the highest-ROI engagement format for web3 community building. A founder answering questions live for 45 minutes builds more trust than a month of blog posts. Here's how to implement a token-gated voice AMA room.

Prerequisites

  • TRTC account (sign up free)
  • Node.js 18+ environment
  • Web3 wallet integration (ethers.js v6)
  • Smart contract address for token-gating

Step 1: Project Setup

mkdir web3-community-voice
cd web3-community-voice
npm init -y
npm install trtc-sdk-v5 ethers express jsonwebtoken

Step 2: Token-Gate Verification Middleware

Before a user enters the voice room, verify they hold the required token:

// lib/token-gate.js
import { ethers } from 'ethers';

const ERC721_ABI = [
  'function balanceOf(address owner) view returns (uint256)',
  'function ownerOf(uint256 tokenId) view returns (address)'
];

const ERC20_ABI = [
  'function balanceOf(address account) view returns (uint256)',
  'function decimals() view returns (uint8)'
];

export class TokenGate {
  constructor(rpcUrl) {
    this.provider = new ethers.JsonRpcProvider(rpcUrl);
  }

  async verifyERC721Holder(walletAddress, contractAddress, minBalance = 1) {
    const contract = new ethers.Contract(contractAddress, ERC721_ABI, this.provider);
    const balance = await contract.balanceOf(walletAddress);
    return Number(balance) >= minBalance;
  }

  async verifyERC20Holder(walletAddress, contractAddress, minAmount) {
    const contract = new ethers.Contract(contractAddress, ERC20_ABI, this.provider);
    const [balance, decimals] = await Promise.all([
      contract.balanceOf(walletAddress),
      contract.decimals()
    ]);
    const threshold = ethers.parseUnits(minAmount.toString(), decimals);
    return balance >= threshold;
  }

  async verifyAccess(walletAddress, gateConfig) {
    const { type, contractAddress, minBalance } = gateConfig;
    
    switch (type) {
      case 'ERC721':
        return this.verifyERC721Holder(walletAddress, contractAddress, minBalance);
      case 'ERC20':
        return this.verifyERC20Holder(walletAddress, contractAddress, minBalance);
      case 'MULTI': {
        // Require multiple conditions
        const results = await Promise.all(
          gateConfig.conditions.map(cond => this.verifyAccess(walletAddress, cond))
        );
        return gateConfig.operator === 'AND' 
          ? results.every(r => r) 
          : results.some(r => r);
      }
      default:
        throw new Error(`Unknown gate type: ${type}`);
    }
  }
}

export async function generateRoomAccess(walletAddress, roomId, gateConfig) {
  const tokenGate = new TokenGate(process.env.RPC_URL);
  
  const hasAccess = await tokenGate.verifyAccess(walletAddress, gateConfig);
  
  if (!hasAccess) {
    throw new Error('Token gate: wallet does not meet access requirements');
  }
  
  // Generate TRTC UserSig for authenticated room access
  const userSig = generateUserSig({
    sdkAppId: parseInt(process.env.TRTC_APP_ID),
    secretKey: process.env.TRTC_SECRET_KEY,
    userId: walletAddress.toLowerCase(),
    expire: 7200 // 2 hours
  });
  
  return { userSig, roomId, userId: walletAddress.toLowerCase() };
}

Step 3: Voice Room AMA Core Implementation

// voice-room-ama.js
import TRTC from 'trtc-sdk-v5';

class Web3VoiceAMA {
  constructor(config) {
    this.trtc = TRTC.create();
    this.roomId = config.roomId;
    this.role = config.role; // 'host', 'speaker', 'listener'
    this.walletAddress = config.walletAddress;
    this.speakers = new Set();
    this.speakerQueue = [];
    this.listenerCount = 0;
  }

  async enterRoom(userSig) {
    try {
      await this.trtc.enterRoom({
        roomId: this.roomId,
        sdkAppId: parseInt(process.env.TRTC_APP_ID),
        userId: this.walletAddress,
        userSig: userSig,
        scene: 'live', // Live scene for AMA: hosts broadcast, audience listens
        role: this.role === 'listener' ? 'audience' : 'anchor'
      });

      console.log(`Entered room ${this.roomId} as ${this.role}`);
      this.setupEventListeners();
      
      // Auto-publish audio if host or speaker
      if (this.role !== 'listener') {
        await this.startSpeaking();
      }
    } catch (error) {
      console.error('Failed to enter room:', error);
      throw error;
    }
  }

  async startSpeaking() {
    await this.trtc.startLocalAudio({
      option: { profile: 'speech' } // Optimized for voice clarity
    });
    this.speakers.add(this.walletAddress);
    console.log('Microphone is live');
  }

  async stopSpeaking() {
    await this.trtc.stopLocalAudio();
    this.speakers.delete(this.walletAddress);
    console.log('Microphone muted');
  }

  // Listener raises hand to request speaking time
  async raiseHand() {
    if (this.role !== 'listener') return;
    
    await this.trtc.sendCustomMessage({
      roomId: this.roomId,
      data: JSON.stringify({
        type: 'RAISE_HAND',
        userId: this.walletAddress,
        timestamp: Date.now()
      })
    });
    
    return { status: 'hand_raised', position: this.speakerQueue.length + 1 };
  }

  // Host promotes listener to speaker
  async promoteToSpeaker(targetUserId) {
    if (this.role !== 'host') {
      throw new Error('Only host can promote speakers');
    }

    await this.trtc.sendCustomMessage({
      roomId: this.roomId,
      data: JSON.stringify({
        type: 'PROMOTE_TO_SPEAKER',
        targetUserId: targetUserId,
        promotedBy: this.walletAddress
      })
    });
  }

  // Handle promotion on the listener's side
  async handlePromotion() {
    await this.trtc.switchRole('anchor');
    this.role = 'speaker';
    await this.startSpeaking();
    console.log('Promoted to speaker — you are now live');
  }

  // Host demotes speaker back to listener
  async demoteToListener(targetUserId) {
    if (this.role !== 'host') return;

    await this.trtc.sendCustomMessage({
      roomId: this.roomId,
      data: JSON.stringify({
        type: 'DEMOTE_TO_LISTENER',
        targetUserId: targetUserId
      })
    });
  }

  async handleDemotion() {
    await this.trtc.stopLocalAudio();
    await this.trtc.switchRole('audience');
    this.role = 'listener';
    console.log('Demoted to listener');
  }

  setupEventListeners() {
    this.trtc.on(TRTC.EVENT.REMOTE_AUDIO_AVAILABLE, (event) => {
      console.log(`${this.formatAddress(event.userId)} started speaking`);
    });

    this.trtc.on(TRTC.EVENT.REMOTE_AUDIO_UNAVAILABLE, (event) => {
      console.log(`${this.formatAddress(event.userId)} stopped speaking`);
    });

    this.trtc.on(TRTC.EVENT.REMOTE_USER_ENTER, (event) => {
      this.listenerCount++;
      console.log(`${this.formatAddress(event.userId)} joined (${this.listenerCount} in room)`);
    });

    this.trtc.on(TRTC.EVENT.REMOTE_USER_EXIT, (event) => {
      this.listenerCount--;
      console.log(`${this.formatAddress(event.userId)} left`);
    });

    // Handle custom signaling messages
    this.trtc.on(TRTC.EVENT.CUSTOM_MESSAGE, (event) => {
      const message = JSON.parse(event.data);
      
      switch (message.type) {
        case 'RAISE_HAND':
          this.speakerQueue.push(message.userId);
          console.log(`Hand raised: ${this.formatAddress(message.userId)} (queue: ${this.speakerQueue.length})`);
          break;
          
        case 'PROMOTE_TO_SPEAKER':
          if (message.targetUserId === this.walletAddress) {
            this.handlePromotion();
          }
          break;
          
        case 'DEMOTE_TO_LISTENER':
          if (message.targetUserId === this.walletAddress) {
            this.handleDemotion();
          }
          break;
          
        case 'AMA_ENDED':
          console.log('AMA session has ended. Thank you for participating!');
          this.leaveRoom();
          break;
      }
    });
  }

  formatAddress(address) {
    return `${address.slice(0, 6)}...${address.slice(-4)}`;
  }

  async endAMA() {
    // Notify all participants
    await this.trtc.sendCustomMessage({
      roomId: this.roomId,
      data: JSON.stringify({ type: 'AMA_ENDED', endedBy: this.walletAddress })
    });
    
    await this.leaveRoom();
  }

  async leaveRoom() {
    await this.trtc.exitRoom();
    this.trtc.destroy();
  }
}

// Full usage example
async function runWeeklyAMA() {
  const gateConfig = {
    type: 'ERC721',
    contractAddress: '0xYourNFTContract...',
    minBalance: 1
  };

  // Host starts the AMA
  const host = new Web3VoiceAMA({
    roomId: 'weekly-ama-2026-05-26',
    role: 'host',
    walletAddress: '0xFounderWallet...'
  });

  const { userSig } = await generateRoomAccess(
    host.walletAddress,
    host.roomId,
    gateConfig
  );
  await host.enterRoom(userSig);

  // Community member joins as listener
  const listener = new Web3VoiceAMA({
    roomId: 'weekly-ama-2026-05-26',
    role: 'listener',
    walletAddress: '0xMemberWallet...'
  });

  const memberAccess = await generateRoomAccess(
    listener.walletAddress,
    listener.roomId,
    gateConfig
  );
  await listener.enterRoom(memberAccess.userSig);

  // Member raises hand → host promotes → member speaks
  await listener.raiseHand();
  await host.promoteToSpeaker(listener.walletAddress);
}

Implementation Guide: Chat Channels for Daily Community Engagement

Persistent chat channels form the backbone of daily web3 community management. Here's how to implement token-gated, topic-based channels with on-chain event integration.

Channel Architecture Design

// chat-channels.js
import TIMChat from '@tencentcloud/chat';
import { TokenGate } from './lib/token-gate.js';

class Web3CommunityChat {
  constructor(config) {
    this.chat = TIMChat.create({
      SDKAppID: config.sdkAppId
    });
    this.tokenGate = new TokenGate(config.rpcUrl);
    this.channels = new Map();
  }

  async initialize(walletAddress, userSig) {
    await this.chat.login({
      userID: walletAddress.toLowerCase(),
      userSig: userSig
    });
    console.log('Chat initialized for', walletAddress);
  }

  // Create the full channel structure for a DAO community
  async createCommunityStructure(communityId) {
    const channelDefinitions = [
      {
        id: `${communityId}_general`,
        name: '#general',
        description: 'Open discussion for all community members',
        gate: null // No token gate - open access
      },
      {
        id: `${communityId}_governance`,
        name: '#governance',
        description: 'Proposal discussions, voting coordination, delegate updates',
        gate: { type: 'ERC20', contractAddress: process.env.GOV_TOKEN, minBalance: 100 }
      },
      {
        id: `${communityId}_builders`,
        name: '#builders',
        description: 'Technical discussion, grant updates, code reviews',
        gate: { type: 'ERC721', contractAddress: process.env.BUILDER_NFT, minBalance: 1 }
      },
      {
        id: `${communityId}_alpha`,
        name: '#alpha',
        description: 'Market insights and strategy (whale channel)',
        gate: { type: 'ERC20', contractAddress: process.env.GOV_TOKEN, minBalance: 50000 }
      },
      {
        id: `${communityId}_announcements`,
        name: '#announcements',
        description: 'Official team updates (admin post only)',
        gate: null,
        adminOnly: true
      },
      {
        id: `${communityId}_onchain`,
        name: '#on-chain-alerts',
        description: 'Automated blockchain event notifications',
        gate: null,
        botOnly: true
      }
    ];

    for (const channel of channelDefinitions) {
      const group = await this.chat.createGroup({
        groupID: channel.id,
        name: channel.name,
        type: TIMChat.TYPES.GRP_COMMUNITY,
        introduction: channel.description,
        groupCustomField: [
          { key: 'gate', value: JSON.stringify(channel.gate) },
          { key: 'adminOnly', value: String(!!channel.adminOnly) },
          { key: 'botOnly', value: String(!!channel.botOnly) }
        ]
      });

      this.channels.set(channel.id, { ...channel, group: group.data.group });
    }

    console.log(`Created ${channelDefinitions.length} channels for ${communityId}`);
    return Array.from(this.channels.values());
  }

  // Join a channel with token-gate verification
  async joinChannel(channelId, walletAddress) {
    const channel = this.channels.get(channelId);
    if (!channel) throw new Error(`Channel ${channelId} not found`);

    // Check token gate
    if (channel.gate) {
      const hasAccess = await this.tokenGate.verifyAccess(walletAddress, channel.gate);
      if (!hasAccess) {
        const requirement = channel.gate.type === 'ERC721'
          ? `${channel.gate.minBalance} NFT(s) from ${channel.gate.contractAddress}`
          : `${channel.gate.minBalance} tokens from ${channel.gate.contractAddress}`;
        throw new Error(`Access denied to ${channel.name}. Requires: ${requirement}`);
      }
    }

    await this.chat.joinGroup({ groupID: channelId });
    return { success: true, channel: channel.name };
  }

  // Send a message with wallet-verified identity
  async sendMessage(channelId, text, walletAddress) {
    const channel = this.channels.get(channelId);
    
    if (channel.adminOnly) {
      const isAdmin = await this.checkAdminRole(walletAddress);
      if (!isAdmin) throw new Error('This channel is admin-only');
    }

    const message = this.chat.createTextMessage({
      to: channelId,
      conversationType: TIMChat.TYPES.CONV_GROUP,
      payload: { text },
      cloudCustomData: JSON.stringify({
        wallet: walletAddress,
        verified: true,
        sentAt: Date.now()
      })
    });

    return await this.chat.sendMessage(message);
  }

  // Post on-chain event notification to alerts channel
  async postOnChainAlert(communityId, eventData) {
    const channelId = `${communityId}_onchain`;
    const formatted = this.formatEvent(eventData);

    const message = this.chat.createCustomMessage({
      to: channelId,
      conversationType: TIMChat.TYPES.CONV_GROUP,
      payload: {
        data: JSON.stringify(eventData),
        description: formatted,
        extension: 'chain_event'
      }
    });

    return await this.chat.sendMessage(message);
  }

  formatEvent(event) {
    const formatters = {
      whale_transfer: (e) => `🐋 Whale Alert: ${e.amount} tokens moved (${e.from} → ${e.to})`,
      proposal_created: (e) => `📋 New Proposal: "${e.title}" — voting opens in ${e.delay}`,
      vote_cast: (e) => `🗳️ ${e.voter} voted ${e.support} on Proposal #${e.id} (${e.weight} votes)`,
      proposal_executed: (e) => `✅ Proposal #${e.id} has been executed`,
      nft_mint: (e) => `🎨 New member! Token #${e.tokenId} minted by ${e.minter}`,
      treasury_deposit: (e) => `💰 Treasury received ${e.amount} from ${e.sender}`
    };

    const formatter = formatters[event.type];
    return formatter ? formatter(event) : `📡 ${event.type}: ${JSON.stringify(event)}`;
  }

  // Listen for incoming messages
  onMessage(callback) {
    this.chat.on(TIMChat.EVENT.MESSAGE_RECEIVED, (event) => {
      event.data.forEach(msg => callback(msg));
    });
  }

  async checkAdminRole(walletAddress) {
    // Check if wallet is in admin list or holds admin NFT
    const adminList = process.env.ADMIN_WALLETS?.split(',') || [];
    return adminList.includes(walletAddress.toLowerCase());
  }
}

// Usage: Set up community chat
async function setupCommunityChat() {
  const communityChat = new Web3CommunityChat({
    sdkAppId: process.env.TRTC_APP_ID,
    rpcUrl: process.env.RPC_URL
  });

  await communityChat.initialize('0xAdminWallet...', adminUserSig);
  await communityChat.createCommunityStructure('my_dao');

  // Member joins governance channel (requires 100 tokens)
  await communityChat.joinChannel('my_dao_governance', '0xMemberWallet...');
  
  // Member posts in governance
  await communityChat.sendMessage(
    'my_dao_governance',
    'Proposal #12 looks solid. The treasury diversification into stablecoins reduces runway risk.',
    '0xMemberWallet...'
  );

  // On-chain bot posts an alert
  await communityChat.postOnChainAlert('my_dao', {
    type: 'proposal_created',
    title: 'Allocate 50 ETH to Developer Grants Program',
    delay: '48 hours',
    proposer: '0x1234...abcd'
  });
}

Implementation Guide: Live Streaming for Community Town Halls

Monthly town halls are the highest-leverage community event. A single well-executed town hall can re-engage dormant members and align the entire community around shared goals.

Full Town Hall Implementation

// town-hall-live.js
import TRTC from 'trtc-sdk-v5';

class CommunityTownHall {
  constructor(config) {
    this.trtc = TRTC.create();
    this.roomId = config.roomId;
    this.title = config.title;
    this.agenda = config.agenda || [];
    this.currentAgendaIndex = 0;
    this.isRecording = false;
    this.viewerCount = 0;
  }

  // Presenter starts the town hall with camera + mic
  async startTownHall(presenterUserId, userSig) {
    await this.trtc.enterRoom({
      roomId: this.roomId,
      sdkAppId: parseInt(process.env.TRTC_APP_ID),
      userId: presenterUserId,
      userSig: userSig,
      scene: 'live',
      role: 'anchor'
    });

    // Start camera (face-to-face builds trust)
    await this.trtc.startLocalVideo({
      view: 'presenter-camera',
      option: { 
        profile: '1080p',
        mirror: false
      }
    });

    // Start microphone
    await this.trtc.startLocalAudio({
      option: { profile: 'speech' }
    });

    // Broadcast town hall start
    await this.broadcastEvent('TOWN_HALL_STARTED', {
      title: this.title,
      agenda: this.agenda,
      startedAt: Date.now()
    });

    console.log(`Town Hall "${this.title}" is LIVE`);
  }

  // Share screen for roadmap/treasury presentations
  async startScreenShare() {
    await this.trtc.startScreenShare({
      view: 'screen-share-container',
      option: {
        systemAudio: true,
        profile: '1080p'
      }
    });
    console.log('Screen sharing active — presenting to community');
  }

  async stopScreenShare() {
    await this.trtc.stopScreenShare();
  }

  // Viewer joins the stream
  async joinAsViewer(viewerUserId, userSig) {
    await this.trtc.enterRoom({
      roomId: this.roomId,
      sdkAppId: parseInt(process.env.TRTC_APP_ID),
      userId: viewerUserId,
      userSig: userSig,
      scene: 'live',
      role: 'audience'
    });

    // Auto-subscribe to presenter's streams
    this.trtc.on(TRTC.EVENT.REMOTE_VIDEO_AVAILABLE, (event) => {
      this.trtc.startRemoteVideo({
        userId: event.userId,
        view: `video-${event.userId}-${event.streamType}`,
        streamType: event.streamType
      });
    });

    this.viewerCount++;
    console.log(`Viewer joined (${this.viewerCount} watching)`);
  }

  // Live polling during town hall
  async createPoll(question, options, durationMs = 60000) {
    const poll = {
      id: `poll_${Date.now()}`,
      question,
      options: options.map((text, i) => ({ id: i, text, votes: 0 })),
      endsAt: Date.now() + durationMs,
      status: 'active'
    };

    await this.broadcastEvent('POLL_CREATED', poll);
    
    // Auto-close poll after duration
    setTimeout(async () => {
      poll.status = 'closed';
      await this.broadcastEvent('POLL_CLOSED', poll);
    }, durationMs);

    return poll;
  }

  // Advance to next agenda item
  async nextAgendaItem() {
    this.currentAgendaIndex++;
    if (this.currentAgendaIndex >= this.agenda.length) {
      await this.broadcastEvent('AGENDA_COMPLETE', {});
      return null;
    }
    
    const current = this.agenda[this.currentAgendaIndex];
    await this.broadcastEvent('AGENDA_ADVANCED', {
      current,
      index: this.currentAgendaIndex,
      total: this.agenda.length
    });
    return current;
  }

  // Start cloud recording for transparency
  async startRecording() {
    const response = await fetch(`${process.env.TRTC_API_BASE}/v4/cloud-recording/start`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.TRTC_API_SECRET}`
      },
      body: JSON.stringify({
        roomId: this.roomId,
        sdkAppId: parseInt(process.env.TRTC_APP_ID),
        recordParams: {
          fileType: ['mp4'],
          streamType: 2, // Mixed stream
          storageParams: {
            vendor: 'cos',
            bucket: process.env.RECORDING_BUCKET,
            region: process.env.RECORDING_REGION
          }
        }
      })
    });

    this.isRecording = true;
    console.log('Recording started — governance transparency enabled');
    return response.json();
  }

  async broadcastEvent(type, data) {
    await this.trtc.sendCustomMessage({
      roomId: this.roomId,
      data: JSON.stringify({ type, ...data, timestamp: Date.now() })
    });
  }

  async endTownHall() {
    await this.broadcastEvent('TOWN_HALL_ENDED', {
      duration: Date.now() - this.startedAt,
      peakViewers: this.viewerCount
    });

    await this.trtc.stopLocalVideo();
    await this.trtc.stopLocalAudio();
    await this.trtc.exitRoom();
    this.trtc.destroy();

    console.log(`Town Hall "${this.title}" ended. Peak viewers: ${this.viewerCount}`);
  }
}

// Launch a monthly town hall
async function launchMonthlyTownHall() {
  const townHall = new CommunityTownHall({
    roomId: 'town-hall-june-2026',
    title: 'June 2026 Community Town Hall',
    agenda: [
      { title: 'Treasury Report', duration: '10 min', presenter: 'treasurer.eth' },
      { title: 'Q3 Roadmap Preview', duration: '15 min', presenter: 'cto.eth' },
      { title: 'Governance Proposals Review', duration: '15 min', presenter: 'gov-lead.eth' },
      { title: 'Community Poll: Q3 Priorities', duration: '5 min', presenter: 'host' },
      { title: 'Open Q&A', duration: '30 min', presenter: 'all' }
    ]
  });

  await townHall.startTownHall('host-wallet', hostUserSig);
  await townHall.startRecording();
  
  // Present treasury with screen share
  await townHall.startScreenShare();
  
  // Mid-session governance poll
  const poll = await townHall.createPoll(
    'Which initiative should we prioritize in Q3?',
    ['Cross-chain bridge', 'Mobile app', 'Staking V2', 'Developer grants expansion'],
    120000 // 2 minutes to vote
  );
}

Webhook Integration: On-Chain Events → Community Notifications

The most engaged web3 communities react to on-chain events in real-time. When a whale buys, when a governance proposal passes, when the treasury receives funds—these moments should trigger immediate community notifications across all channels.

Event Router Implementation

// webhooks/on-chain-router.js
import express from 'express';
import { ethers } from 'ethers';
import crypto from 'crypto';

const app = express();
app.use(express.json());

// Event classification and routing rules
const EVENT_ROUTING = {
  WHALE_TRANSFER: {
    threshold: ethers.parseEther('50000'), // 50K tokens
    channels: ['onchain', 'alpha'],
    priority: 'high',
    announceInVoice: true
  },
  GOVERNANCE_PROPOSAL: {
    channels: ['onchain', 'governance'],
    priority: 'high',
    announceInVoice: true,
    autoCreateRoom: true // Auto-create discussion voice room
  },
  VOTE_CAST: {
    channels: ['onchain', 'governance'],
    priority: 'medium',
    announceInVoice: false,
    onlyWhaleVotes: true,
    whaleThreshold: ethers.parseEther('10000')
  },
  NEW_MEMBER_MINT: {
    channels: ['onchain', 'general'],
    priority: 'low',
    announceInVoice: false
  },
  TREASURY_MOVEMENT: {
    channels: ['onchain', 'announcements'],
    priority: 'high',
    announceInVoice: true
  }
};

class OnChainEventRouter {
  constructor(communityChat, voiceRoomManager) {
    this.chat = communityChat;
    this.voiceRooms = voiceRoomManager;
    this.provider = new ethers.JsonRpcProvider(process.env.RPC_URL);
  }

  // Start real-time contract monitoring
  async startDirectMonitoring() {
    // Governance contract
    const govContract = new ethers.Contract(
      process.env.GOVERNANCE_CONTRACT,
      [
        'event ProposalCreated(uint256 proposalId, address proposer, string description, uint256 startBlock, uint256 endBlock)',
        'event VoteCast(address indexed voter, uint256 proposalId, uint8 support, uint256 weight)',
        'event ProposalExecuted(uint256 proposalId)'
      ],
      this.provider
    );

    govContract.on('ProposalCreated', async (proposalId, proposer, description, startBlock, endBlock) => {
      await this.routeEvent({
        type: 'GOVERNANCE_PROPOSAL',
        data: {
          type: 'proposal_created',
          id: proposalId.toString(),
          title: description.slice(0, 120),
          proposer: `${proposer.slice(0, 6)}...${proposer.slice(-4)}`,
          delay: `${((Number(endBlock) - Number(startBlock)) * 12) / 3600} hours`
        }
      });

      // Auto-create voice room for proposal discussion
      if (EVENT_ROUTING.GOVERNANCE_PROPOSAL.autoCreateRoom) {
        await this.voiceRooms.createRoom({
          roomId: `proposal-${proposalId}-discussion`,
          name: `Discussion: ${description.slice(0, 50)}`,
          duration: 48 * 3600 // 48 hours
        });
      }
    });

    govContract.on('VoteCast', async (voter, proposalId, support, weight) => {
      if (weight >= EVENT_ROUTING.VOTE_CAST.whaleThreshold) {
        await this.routeEvent({
          type: 'VOTE_CAST',
          data: {
            type: 'vote_cast',
            voter: `${voter.slice(0, 6)}...${voter.slice(-4)}`,
            id: proposalId.toString(),
            support: support === 1n ? 'FOR' : support === 0n ? 'AGAINST' : 'ABSTAIN',
            weight: ethers.formatEther(weight)
          }
        });
      }
    });

    // Token contract (whale monitoring)
    const tokenContract = new ethers.Contract(
      process.env.TOKEN_CONTRACT,
      ['event Transfer(address indexed from, address indexed to, uint256 value)'],
      this.provider
    );

    tokenContract.on('Transfer', async (from, to, value) => {
      if (value >= EVENT_ROUTING.WHALE_TRANSFER.threshold) {
        await this.routeEvent({
          type: 'WHALE_TRANSFER',
          data: {
            type: 'whale_transfer',
            from: `${from.slice(0, 6)}...${from.slice(-4)}`,
            to: `${to.slice(0, 6)}...${to.slice(-4)}`,
            amount: `${Number(ethers.formatEther(value)).toLocaleString()} tokens`
          }
        });
      }

      // New member detection (mint from zero address)
      if (from === ethers.ZeroAddress) {
        await this.routeEvent({
          type: 'NEW_MEMBER_MINT',
          data: {
            type: 'nft_mint',
            minter: `${to.slice(0, 6)}...${to.slice(-4)}`,
            tokenId: value.toString()
          }
        });
      }
    });

    console.log('Direct on-chain monitoring active');
  }

  // Route event to appropriate channels
  async routeEvent(event) {
    const routing = EVENT_ROUTING[event.type];
    if (!routing) return;

    // Post to all configured chat channels
    for (const channel of routing.channels) {
      await this.chat.postOnChainAlert(process.env.COMMUNITY_ID, event.data);
    }

    // Announce in active voice rooms if high priority
    if (routing.announceInVoice && routing.priority === 'high') {
      await this.announceInActiveRooms(event.data);
    }

    console.log(`[${routing.priority.toUpperCase()}] ${event.type} routed to ${routing.channels.join(', ')}`);
  }

  async announceInActiveRooms(eventData) {
    const activeRooms = await this.voiceRooms.getActiveRooms();
    for (const room of activeRooms) {
      await room.broadcastEvent('CHAIN_ALERT', eventData);
    }
  }
}

// Webhook endpoint for external indexers (Alchemy, The Graph, Moralis)
app.post('/webhooks/chain-events', async (req, res) => {
  // Verify webhook signature
  const signature = req.headers['x-alchemy-signature'];
  const expectedSig = crypto
    .createHmac('sha256', process.env.WEBHOOK_SECRET)
    .update(JSON.stringify(req.body))
    .digest('hex');

  if (signature !== expectedSig) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const { event, data } = req.body;
  
  try {
    await eventRouter.routeEvent({ type: event, data });
    res.json({ status: 'routed', event });
  } catch (error) {
    console.error('Webhook routing error:', error);
    res.status(500).json({ error: error.message });
  }
});

app.get('/webhooks/health', (req, res) => {
  res.json({ status: 'healthy', uptime: process.uptime() });
});

app.listen(3001, () => console.log('Webhook router on port 3001'));

// Initialize
const eventRouter = new OnChainEventRouter(communityChat, voiceRoomManager);
await eventRouter.startDirectMonitoring();

Accelerating Development with TRTC MCP Server

Building the integrations above involves significant SDK documentation reading and API exploration. The TRTC MCP (Model Context Protocol) server accelerates this by giving AI coding assistants direct access to TRTC's documentation, code examples, and API references within your development environment.

What MCP Enables for Web3 Community Developers

With the TRTC MCP server connected to your AI coding environment (Claude Code, Cursor, VS Code with Copilot), you can:

  • Generate integration code from natural language descriptions ("add token-gated voice room to my Next.js app")
  • Get context-aware API suggestions based on your existing codebase architecture
  • Access up-to-date SDK documentation without leaving your editor or switching tabs
  • Debug TRTC-specific issues with AI that understands SDK internals and common pitfalls

Example MCP Workflow

Developer prompt:
"I need a voice room where only holders of NFT contract 0xABC... on Ethereum 
mainnet can join. Include hand-raising, speaker queue, and recording. 
The room should auto-close after 60 minutes."

TRTC MCP response: Generates complete implementation including:
  ├── Token verification middleware (ethers.js + contract ABI)
  ├── TRTC room configuration (scene: 'live', roles, audio profiles)
  ├── Hand-raise signaling via custom messages
  ├── Speaker queue management with priority system
  ├── Cloud recording API integration
  └── Auto-close timer with graceful shutdown

This reduces implementation time from days to hours for each community feature, letting your team focus on community strategy rather than SDK plumbing.

Web3 Community Engagement Strategies That Drive Retention

Tools without strategy produce ghost towns. Here are proven engagement patterns used by communities that sustain activity through bear and bull markets alike.

Strategy 1: The Weekly Rhythm That Creates Habit Loops

Successful communities run on predictable schedules. Consistency beats frequency—a reliable weekly AMA outperforms sporadic daily attempts.

DayActivityFormatDurationEngagement Type
MondayWeek KickoffChat post + voice standup15 minAsync + sync
TuesdayBuilder ShowcaseScreen share in voice room30 minSync
WednesdayCommunity AMAToken-gated voice room45 minSync (recorded)
ThursdayGovernance DiscussionChat thread + voice debate60 minHybrid
FridayAlpha CallWhale-gated voice room30 minSync (exclusive)
WeekendOpen LoungeUnstructured voice hangout2 hrCasual sync
MonthlyTown HallLive stream + Q&A + poll75 minSync (everyone)

Strategy 2: Contribution-Based Access Tiers

Don't just gate by token holdings. Gate by contribution, creating a meritocratic progression:

Tier 0 (Open): #general, #announcements, #on-chain-alerts
  └─ Requirement: Connect wallet

Tier 1 (Holder): #governance, weekly AMA access, daily lounge
  └─ Requirement: Hold 100+ governance tokens

Tier 2 (Contributor): #builders, working group voice rooms, early access
  └─ Requirement: Hold 1,000 tokens OR earn Builder Badge NFT (via accepted PRs/proposals)

Tier 3 (Core): #treasury, admin voice rooms, multi-sig coordination
  └─ Requirement: Elected by Tier 2 members + hold Delegate NFT

This structure means a prolific developer with 0 tokens can earn the same access as a 50,000-token holder—keeping your community meritocratic and attracting talent, not just capital.

Strategy 3: Event-Driven Engagement Loops

Tie community activities directly to on-chain events:

  1. Proposal Created → Auto-post in #governance → Open discussion voice room → Notify all delegates
  2. Voting Opens → Host "debate night" live stream → Both sides present → Community polls during stream
  3. Proposal Passes → Celebration moment in voice room → Implementation plan posted → Working group created
  4. Treasury Threshold Reached → Auto-transparency report → Schedule Q&A town hall → Community poll on allocation

This creates a predictable, reactive community that feels alive because it responds to real events in real time.

Strategy 4: Onboarding Flow That Converts Lurkers

Most new community members never post a single message. Convert them with automated onboarding tied to wallet connection:

Day 0: Welcome message with community overview → Channel guide → First AMA scheduled notification Day 3: "Introduce yourself" prompt → Small task reward (react to a post, vote on a poll) Day 7: Working group invitation → Mentor pairing → First meaningful contribution opportunity Day 14: Contribution recognition → Role upgrade → Path to deeper involvement revealed Day 30: Review engagement → Offer contributor badge → Invite to exclusive channels

Strategy 5: Cross-Community Collaboration

The most resilient communities have alliances. Joint AMAs, shared governance discussions, and inter-DAO voice rooms create network effects:

  • Host monthly "alliance AMA" with 2-3 partner projects in one voice room
  • Create shared channels for ecosystem updates
  • Run joint town halls for cross-protocol governance (e.g., DeFi composability decisions)

Measuring Web3 Community Health: Metrics That Matter

Track these metrics weekly. If you only look at member count and message volume, you're flying blind.

Engagement Depth Metrics

MetricHealthy RangeWarningCritical
Voice AMA attendance (% of members)5-15%2-5%<2%
Average voice session duration15-45 min5-15 min<5 min
Chat messages per active user per day3-101-3<1
Town hall completion rate (stay until end)60-80%40-60%<40%
New member first-message (days to first post)1-3 days3-7 days>7 days
Governance vote participation10-25%5-10%<5%

Community Health Indicators

SignalMeaningAction
Voice rooms with <5 people consistentlyWrong time or boring topicsSurvey members, rotate times
Chat channels with only bot postsCommunity disengagedReduce automation, increase human presence
80% drop-off in first 10 min of town hallContent irrelevant or too longRestructure agenda, lead with announcements
Same 5 people in every AMACommunity becoming insularGuest speakers, themed AMAs, time rotation
High join rate but low retentionAttracting wrong audienceTighten token gates, improve onboarding

Common Web3 Community Building Mistakes (And Fixes)

Mistake 1: Platform Dependency Without Exit Strategy

Building your entire community on Discord means one ban, one policy change, or one infrastructure outage kills everything. Discord has banned multiple Web3 servers without warning.

Fix: Use Discord/Telegram for discovery and initial onboarding. Build owned infrastructure (with TRTC SDKs) for high-value interactions: voice AMAs, gated channels, town halls. You control the data, the access rules, and the uptime.

Mistake 2: All Async, Zero Sync

Text channels with no voice events feel dead. A 50,000-member Discord with no one talking is worse than a 200-person community with a daily voice lounge—because the former demonstrates apathy while the latter demonstrates life.

Fix: Schedule minimum one weekly voice event. Even a 30-minute "office hours" voice room with a core team member creates proof-of-life that encourages text participation the rest of the week.

Mistake 3: Governance Without Deliberation

Snapshot votes with no preceding live discussion produce voter apathy. Members need to hear arguments, challenge assumptions, and deliberate—not just read a proposal and click "For."

Fix: Every governance proposal gets a mandatory discussion period with at least one live voice session before the vote opens. Record these sessions so async voters can listen before deciding.

Mistake 4: Ignoring Timezone Diversity

If all events happen at 2 PM EST, you've excluded Asia, Africa, and most of Europe—often your largest potential member base.

Fix: Rotate event times across 3 timezone blocks. Run your AMA twice per week at different times. Use TRTC's global edge network (2,500+ nodes) to ensure audio quality is identical whether the participant is in Lagos, Seoul, or São Paulo.

Mistake 5: Buying Members Instead of Earning Them

Airdrop campaigns that reward wallet creation (not participation) produce communities with 50,000 addresses and 50 actual humans.

Fix: Gate your highest-value experiences behind contribution, not just token holdings. A builder who ships code should have equivalent access to a whale who bought tokens. This attracts genuine stakeholders.

Production Deployment Checklist

Before launching your web3 community platform's real-time features:

Infrastructure

Security

Community Readiness

The Future of Web3 Community Platforms (2026-2027)

Trend 1: AI-Assisted Community Management

AI moderators that understand context, summarize voice room discussions, auto-generate meeting notes from town halls, and translate real-time. Not replacing human community managers—handling the 80% of repetitive tasks so CMs can focus on strategy and relationship building.

Trend 2: Spatial Audio and 3D Community Spaces

Voice rooms evolving from flat participant lists to spatial experiences where proximity determines who you hear. Creates natural conversation clusters in large gatherings—like walking between groups at a conference.

Trend 3: Cross-Chain Unified Identity

Single wallet identity working across Ethereum, Solana, and L2s. Your reputation, access rights, and governance participation follow you across all communities in the ecosystem.

Trend 4: On-Chain Reputation as Primary Access Control

Moving beyond simple "hold X tokens" to reputation-weighted systems. Consistent participation in governance votes, code contributions, and community support earns access that money alone cannot buy.

Trend 5: Real-Time Translation Breaking Language Barriers

AI-powered live translation in voice rooms: speak in Mandarin, heard in English. This unlocks truly global web3 communities where language is no longer a participation barrier.

Conclusion: Building Communities People Actually Return To

Building a thriving web3 community requires more than a token and a Telegram group. It demands intentional design of real-time experiences that create emotional bonds text cannot replicate.

The formula that works:

  1. Real-time voice and video create trust and belonging
  2. Token-gated access creates aspiration and exclusivity
  3. On-chain event triggers keep the community reactive and relevant
  4. Consistent weekly rhythms build habits that survive bear markets
  5. Owned infrastructure eliminates platform dependency risk

The code examples in this guide give you a production-ready starting point for the three core engagement features: voice room AMAs, live streaming town halls, and persistent chat channels with on-chain integration.

Start with one weekly voice AMA. Measure attendance and session duration. Iterate on format and timing. Then layer in town halls, structured channels, and automated event routing as your community grows.

The communities that survive the next cycle won't be the ones with the most members. They'll be the ones where members actually know each other's voices.

Ready to build? Start with the TRTC Web3 solution for infrastructure, set up your first voice chat room for community AMAs, or integrate persistent chat channels for daily engagement. The free tier supports everything you need to validate your community's real-time engagement model.

Frequently Asked Questions

What is a web3 community and how does it differ from traditional online communities?

A web3 community is a group organized around a blockchain-based project, protocol, or DAO where membership, governance, and engagement are tied to on-chain assets (tokens, NFTs, reputation credentials). Unlike traditional communities, web3 communities feature wallet-based identity instead of usernames, token-gated access control, decentralized governance, and transparent treasury management. Members are often stakeholders with economic alignment, not just users.

What are the essential tools for web3 community building?

The essential stack includes five layers: (1) Communication infrastructure—voice rooms for AMAs, chat channels for daily discussion, live streaming for town halls; (2) Wallet authentication—WalletConnect, Privy, or RainbowKit for sign-in; (3) Token-gating—smart contract verification for access control; (4) Governance—Snapshot or Tally for voting, forums for deliberation; (5) On-chain monitoring—Alchemy or Moralis webhooks for event notifications. For the communication layer, TRTC provides a complete SDK covering voice, chat, and live streaming without platform dependency.

How do voice AMAs improve web3 community engagement compared to text-only channels?

Voice AMAs shift the participation ratio from 90-9-1 (90% lurk in text) to approximately 70-20-10 because listening requires less effort than reading long threads, and speaking requires less effort than writing thoughtful posts. Data across successful projects shows communities with weekly voice events achieve 3-5x higher message volume on event days, 40% better 30-day retention, and stronger trust between teams and holders. The parasocial effect of hearing a founder's voice builds credibility faster than any written update.

How do I prevent my web3 community from dying after the initial hype?

Three structural requirements prevent community death: (1) Predictable weekly rhythms—at minimum one voice event and one async engagement prompt per week; (2) Contribution-based progression—give active members access and status that pure token-holders don't automatically get; (3) On-chain reactivity—tie community activity to real blockchain events so there's always something relevant to discuss. Communities that have all three survive bear markets. Those missing any one tend to fade within 3-6 months.

What's the best web3 community platform for a new project?

For projects under 500 members: start with Discord + Collab.Land for token-gating. Low setup cost, familiar UX, sufficient for testing community-market fit. Between 500-5,000 members: add custom voice/live infrastructure (TRTC SDK) for branded AMAs and town halls while keeping Discord for casual chat. Above 5,000 members or with serious governance: build fully custom on communication SDKs for control, scalability, and elimination of platform risk. The migration path matters—build your strategy so you can transition from Tier 1 to Tier 3 without losing members.

How should I structure token-gating for my community channels?

Use a tiered approach that rewards both holding and contributing. Base tier (open): announcements, on-chain alerts, and general chat—anyone can join. Holder tier (e.g., 100+ tokens): governance discussion, weekly AMAs, daily voice lounge. Contributor tier (significant holdings OR earned credentials like a Builder NFT): technical channels, working groups, early access. Core tier (elected/delegated): treasury visibility, admin coordination, multi-sig communication. This prevents plutocracy while still rewarding economic commitment.