All Blog

Web3 Messaging: How Decentralized Communication Works (And How to Build It)

12 min read
May 27, 2026

web3-social-networks-guide

Web3 messaging represents a fundamental shift in how digital communication works. Instead of trusting a corporation with your messages, metadata, and social graph, web3 chat systems give users ownership of their identity through cryptographic wallets, encrypt communications end-to-end by default, and resist censorship at the protocol level.

But here's the reality most articles won't tell you: pure decentralization alone doesn't solve the communication problem. Users need real-time delivery, reliable presence indicators, group conversations that scale to thousands, and message history that doesn't disappear when a relay node goes offline. That's where a hybrid architecture—combining decentralized identity and storage with battle-tested real-time transport infrastructure—becomes the only practical path to production.

This guide covers the full landscape of web3 messaging: from protocols and architecture patterns to a hands-on tutorial where you'll build a decentralized web3 chat app using wallet-based identity and Tencent RTC (TRTC) as the real-time transport layer.

What Is Web3 Messaging?

Web3 messaging is a communication paradigm where:

  • Identity is wallet-based: Your Ethereum address (or any blockchain address) is your username. No phone numbers, no email signup, no KYC for basic communication.
  • Messages are user-owned: Communication data isn't stored on corporate servers. It lives on decentralized networks, encrypted so only participants can read it.
  • Transport is censorship-resistant: No single entity can block, filter, or eavesdrop on conversations.
  • Interoperability is native: Messages can flow between apps because they share open protocols rather than walled gardens.
  • Composability is built-in: Messages can trigger smart contract calls—token transfers, governance votes, NFT trades—natively within the conversation.

A web3 messaging app differs from traditional encrypted messengers (Signal, WhatsApp) in a critical way: it decentralizes not just the encryption, but the entire identity and discovery layer. Your communication identity isn't controlled by any platform—it's your wallet, secured by your private key.

Why Decentralized Communication Matters

The problems with centralized messaging are not theoretical:

Platform risk is real. Telegram banned crypto groups in multiple jurisdictions. Discord servers get nuked without warning. Twitter/X suspended accounts discussing competing protocols. Your community loses years of context and relationships overnight with no recourse.

Data exploitation is the business model. Meta monetizes WhatsApp metadata—who you talk to, when, how often, your location patterns. Even "encrypted" platforms profit from the social graph. In web3 messaging, there's no central entity collecting metadata because there's no central server.

Single points of failure affect everyone. When AWS us-east-1 goes down, half the internet's chat infrastructure fails. When a messaging company gets acquired, API changes break thousands of apps. Decentralized protocols eliminate these failure modes.

Identity fragmentation creates friction. Your Discord identity, Telegram handle, and Twitter DMs are three separate personas with no cryptographic link. In web3 chat, one wallet address unifies your identity across every application in the ecosystem.

Censorship affects legitimate communication. Financial messaging between users in different jurisdictions gets blocked. DAO governance discussions get flagged. Legitimate coordination becomes impossible when a single company decides what's allowed.

Web2 vs. Web3 Messaging: A Technical Comparison

Understanding the architectural differences clarifies where each approach excels and where hybrid solutions fill the gaps.

DimensionWeb2 (WhatsApp/Telegram)Pure Web3 (XMTP/Push Protocol)Hybrid (TRTC + Web3 Identity)
IdentityPhone number / emailWallet addressWallet address mapped to TRTC userId
EncryptionServer-controlled E2E (metadata exposed)User-controlled E2E (metadata-minimal)User-controlled E2E with TRTC key exchange
TransportCentralized serversP2P / relay nodesTRTC global edge network (2,800+ nodes)
Latency~100ms500ms–5s (variable)<200ms (guaranteed)
Offline deliveryServer queues messagesStore nodes (best-effort)TRTC offline push + on-chain backup
Group scale100K+ members~1,000 (protocol-dependent)100K+ with TRTC infrastructure
Media supportRich (images, video, voice)Limited (text-focused)Full (text, voice, video, files, custom types)
Mobile pushNativeRequires separate serviceBuilt into TRTC SDK
ComposabilityNoneNative on-chainSmart contract triggers via custom messages

The key insight: pure decentralized protocols excel at identity sovereignty and censorship resistance but struggle with performance, reliability, and rich media. A hybrid approach—using decentralized identity and encryption with professional real-time infrastructure—delivers both the values of web3 and the experience users demand.

Web3 Messaging Architecture

Every web3 messaging system, regardless of protocol, decomposes into three layers:

Layer 1: Identity (Wallet-Based)

The identity layer maps blockchain addresses to communication endpoints. When you send a message to 0x742d35Cc6634C0532925a3b844Bc9e7595f2bD28, the system needs to:

  1. Resolve that address to a reachable communication endpoint
  2. Retrieve the recipient's public encryption key
  3. Verify the sender's identity through cryptographic signatures

This replaces traditional username/password or phone-number-based systems entirely. The wallet is the identity—no registration flow needed. ENS names (like vitalik.eth) provide human-readable aliases, but the underlying identity is always the cryptographic address.

User Wallet (0x7a3B...9f2E)
    ↓ signs authentication challenge
Identity Verification
    ↓ maps wallet to
TRTC userId: "7a3b4c8d...9f2e"
    ↓ enables
Wallet-to-wallet messaging

Layer 2: Transport (Real-Time Delivery)

The transport layer handles message routing, delivery guarantees, and presence. This is where most pure-decentralized systems hit practical limitations:

  • P2P gossip protocols (like libp2p) provide censorship resistance but add 3-10 hops of latency
  • Relay nodes (like Waku) handle message forwarding but can't guarantee sub-second delivery
  • Store nodes handle offline delivery but reliability depends on node availability and incentive alignment

TRTC operates at this layer as professional-grade transport infrastructure that any web3 protocol can plug into. Its global edge network provides <200ms latency worldwide, while the application layer maintains full decentralization of identity and encryption.

Layer 3: Storage (On-Chain / Off-Chain)

Messages need persistence. The storage layer decides what lives where:

  • On-chain: Permanent, immutable, expensive. Used for governance votes, transaction records, critical notifications that need verifiability.
  • Off-chain decentralized (IPFS, Arweave): Content-addressed, censorship-resistant, variable retrieval speed. Good for media attachments and message archives.
  • Off-chain managed (TRTC roaming storage): Fast retrieval, guaranteed availability, user-controlled encryption keys. Handles day-to-day message history.
  • Ephemeral: Messages exist only in transit for sensitive conversations.

Most production systems use a hybrid: critical data on-chain, message content encrypted in managed storage, media on IPFS, and real-time delivery through optimized infrastructure.

The Web3 Messaging Protocol Landscape

XMTP (Extensible Message Transport Protocol)

XMTP is the most developer-adopted web3 messaging protocol. Backed by a16z and Coinbase Ventures, it's integrated into Coinbase Wallet, Lens Protocol, and dozens of dApps.

Architecture: XMTP uses MLS (Messaging Layer Security) for group encryption, a network of relay nodes built on Waku for message transport, and wallet signatures for identity binding.

Strengths: Strong developer tooling, growing ecosystem adoption, well-defined content types system for structured messages, cross-app message portability (messages sent via one app are readable in another XMTP client).

Limitations: Text-focused with limited media support, latency varies with network conditions (typically 1-3 seconds), group size capped around 1,000 members, still relies on centralized nodes for initial message relay.

Push Protocol (formerly EPNS)

Push Protocol focuses on notifications and messaging for dApps:

Architecture: Channel-based notification system with wallet-to-wallet chat. Uses centralized push nodes for delivery with plans for progressive decentralization.

Strengths: Notification system is production-ready (used by Aave, Uniswap, MakerDAO), good React/React Native SDKs, multichain support across Ethereum, Polygon, BSC, and Arbitrum.

Limitations: Notification-first architecture means chat is secondary, semi-decentralized (still relies on Push nodes), limited group chat capabilities, engagement rates for notifications are low.

Waku

Waku is a peer-to-peer communication protocol from the Status team:

Architecture: Modular P2P protocol with separate modules for Relay (gossipsub messaging), Store (offline message persistence), Light Push (resource-constrained devices), and Filter (selective message subscription). Uses Rate-Limiting Nullifiers (RLN) for spam prevention.

Strengths: Truly decentralized at the transport layer, minimal metadata exposure, efficient for mobile devices, composable protocol modules.

Limitations: Infrastructure layer rather than end-user facing, requires significant developer effort to build production apps, no native token means node incentive alignment is unclear, scalability vs. privacy tradeoffs.

Towns Protocol

Towns (backed by a16z and Coinbase Ventures with $25.5M raised) targets on-chain group chat:

Architecture: Smart-contract-governed communities where membership, moderation rules, and revenue sharing are all on-chain. Built on a custom L2 (River) for performance.

Strengths: Purpose-built for DAOs and token communities, on-chain governance of community rules, programmable access control, strong VC backing.

Limitations: Early stage, gas costs for on-chain operations, limited to group/community use cases, not suitable for general-purpose messaging.

Where TRTC Fits: The Real-Time Transport Layer

TRTC doesn't compete with these protocols—it complements them as the real-time transport infrastructure any of them can use. The separation is clean:

  • XMTP/Push/Waku define what gets communicated and who can participate (identity + addressing + encryption)
  • TRTC solves how messages get delivered reliably, in real-time, at scale (transport + delivery + persistence)

Any web3 messaging protocol can use TRTC's Chat SDK as its transport backbone to gain:

  • Sub-200ms global message delivery across 2,800+ edge nodes
  • Offline push notifications across iOS, Android, and web
  • Group messaging that scales to 100,000+ members per channel
  • Rich media support (voice messages, images, video, files, custom structured messages)
  • 99.99% service availability backed by SLA
  • Message roaming and cross-device sync out of the box

Think of it this way: XMTP handles "who can message whom and with what encryption." TRTC handles "how that message actually arrives in 150ms instead of 3 seconds, even when the recipient is on a flaky mobile connection in Lagos."

Build Tutorial: Web3 Chat App with TRTC

Let's build a web3 messaging app that combines wallet-based identity with TRTC's real-time infrastructure. You'll implement:

  1. Wallet authentication mapped to TRTC userId
  2. End-to-end encrypted messaging
  3. Custom message types for blockchain transactions and NFT transfers
  4. DAO group channels with token-gated access

Prerequisites

  • Node.js 18+
  • MetaMask or any Web3 wallet (WalletConnect supported)
  • TRTC account (free tier available)
  • Basic familiarity with Ethereum and JavaScript

Step 1: Project Setup and Chat SDK Initialization with Wallet Identity

mkdir web3-chat && cd web3-chat
npm init -y
npm install @tencentcloud/chat ethers@6

Initialize the TRTC Chat SDK using a wallet address as the userId:

// src/chat-init.js
import TencentCloudChat from '@tencentcloud/chat';
import { BrowserProvider, verifyMessage } from 'ethers';

const SDKAppID = Number(process.env.TRTC_SDK_APP_ID);

// Initialize TRTC Chat instance
const chat = TencentCloudChat.create({ SDKAppID });

/**
 * Connect wallet and login to TRTC Chat
 * The wallet address becomes the userId — no separate registration
 */
async function connectWalletAndLogin() {
  // 1. Connect MetaMask / WalletConnect
  const provider = new BrowserProvider(window.ethereum);
  const accounts = await provider.send('eth_requestAccounts', []);
  const signer = await provider.getSigner();
  const walletAddress = await signer.getAddress();

  // 2. Format wallet address as TRTC userId
  // Remove '0x' prefix and lowercase for consistency
  const userId = walletAddress.toLowerCase().slice(2);

  // 3. Prove wallet ownership via signature challenge
  const nonce = await fetchNonce(walletAddress); // From your backend
  const challenge = `Sign to authenticate with Web3 Chat\nNonce: ${nonce}\nTimestamp: ${Date.now()}`;
  const signature = await signer.signMessage(challenge);

  // 4. Exchange signature for TRTC UserSig (server-side verification)
  const userSig = await authenticateAndGetUserSig(walletAddress, challenge, signature);

  // 5. Login to TRTC Chat
  await chat.login({ userID: userId, userSig });

  // 6. Set profile with ENS name if available
  const ensName = await provider.lookupAddress(walletAddress);
  if (ensName) {
    await chat.updateMyProfile({
      nick: ensName,
      avatar: `https://metadata.ens.domains/mainnet/avatar/${ensName}`,
    });
  }

  console.log(`Connected: ${ensName || walletAddress}`);
  return { chat, userId, walletAddress, signer };
}

// Server-side: verify signature and issue TRTC UserSig
async function authenticateAndGetUserSig(walletAddress, challenge, signature) {
  const response = await fetch('/api/auth/web3', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ walletAddress, challenge, signature }),
  });

  if (!response.ok) throw new Error('Authentication failed');
  const { userSig } = await response.json();
  return userSig;
}

Server-side authentication handler:

// server/api/auth/web3.js
import { verifyMessage } from 'ethers';
import TLSSigAPIv2 from 'tls-sig-api-v2';

const sigApi = new TLSSigAPIv2.Api(SDK_APP_ID, SECRET_KEY);

export async function POST(req) {
  const { walletAddress, challenge, signature } = await req.json();

  // Verify the wallet actually signed the challenge
  const recovered = verifyMessage(challenge, signature);
  if (recovered.toLowerCase() !== walletAddress.toLowerCase()) {
    return Response.json({ error: 'Invalid signature' }, { status: 401 });
  }

  // Verify nonce hasn't been used (prevent replay attacks)
  const nonceValid = await validateAndConsumeNonce(walletAddress, challenge);
  if (!nonceValid) {
    return Response.json({ error: 'Invalid nonce' }, { status: 401 });
  }

  // Issue TRTC UserSig for verified wallet
  const userId = walletAddress.toLowerCase().slice(2);
  const userSig = sigApi.genUserSig(userId, 86400 * 7); // 7-day expiry

  return Response.json({ userSig, userId });
}

Why this works: The wallet address becomes the universal identifier. Any app in the ecosystem can resolve the same wallet to the same TRTC chat identity. No separate registration, no password management, no OAuth complexity.

Step 2: End-to-End Encrypted Messaging

TRTC provides transport-layer encryption by default. For true E2E encryption where not even TRTC servers can read message content, implement a client-side encryption layer using X25519 key exchange and XChaCha20-Poly1305:

// src/e2e-encryption.js
import { x25519 } from '@noble/curves/ed25519';
import { randomBytes } from '@noble/hashes/utils';
import { xchacha20poly1305 } from '@noble/ciphers/chacha';
import { hkdf } from '@noble/hashes/hkdf';
import { sha256 } from '@noble/hashes/sha256';

class E2EEncryption {
  constructor() {
    this.privateKey = randomBytes(32);
    this.publicKey = x25519.getPublicKey(this.privateKey);
    this.sharedSecrets = new Map(); // cache per-peer shared secrets
  }

  // Get public key to publish (store on-chain or in TRTC profile)
  getPublicKeyHex() {
    return Buffer.from(this.publicKey).toString('hex');
  }

  // Derive deterministic encryption keys from wallet signature
  // This ensures the same wallet always produces the same keys across devices
  static async fromWalletSignature(signer) {
    const sig = await signer.signMessage(
      'Web3 Chat E2E Encryption Key Derivation v1'
    );
    const keyMaterial = sha256(new TextEncoder().encode(sig));
    const privateKey = hkdf(sha256, keyMaterial, '', 'x25519-key', 32);

    const instance = new E2EEncryption();
    instance.privateKey = privateKey;
    instance.publicKey = x25519.getPublicKey(privateKey);
    return instance;
  }

  // Encrypt a message for a specific recipient
  encrypt(recipientPublicKeyHex, plaintext) {
    const recipientKey = Buffer.from(recipientPublicKeyHex, 'hex');

    // X25519 key agreement → shared secret
    let sharedSecret = this.sharedSecrets.get(recipientPublicKeyHex);
    if (!sharedSecret) {
      const rawSecret = x25519.getSharedSecret(this.privateKey, recipientKey);
      // HKDF to derive proper encryption key
      sharedSecret = hkdf(sha256, rawSecret, '', 'chat-encryption', 32);
      this.sharedSecrets.set(recipientPublicKeyHex, sharedSecret);
    }

    // Encrypt with XChaCha20-Poly1305
    const nonce = randomBytes(24);
    const cipher = xchacha20poly1305(sharedSecret, nonce);
    const ciphertext = cipher.encrypt(new TextEncoder().encode(plaintext));

    return {
      ciphertext: Buffer.from(ciphertext).toString('base64'),
      nonce: Buffer.from(nonce).toString('base64'),
      senderPublicKey: this.getPublicKeyHex(),
      algorithm: 'x25519-xchacha20poly1305',
      version: 1,
    };
  }

  // Decrypt a message from a sender
  decrypt(encryptedPayload) {
    const { ciphertext, nonce, senderPublicKey } = encryptedPayload;
    const senderKey = Buffer.from(senderPublicKey, 'hex');

    let sharedSecret = this.sharedSecrets.get(senderPublicKey);
    if (!sharedSecret) {
      const rawSecret = x25519.getSharedSecret(this.privateKey, senderKey);
      sharedSecret = hkdf(sha256, rawSecret, '', 'chat-encryption', 32);
      this.sharedSecrets.set(senderPublicKey, sharedSecret);
    }

    const cipher = xchacha20poly1305(
      sharedSecret,
      Buffer.from(nonce, 'base64')
    );
    const plaintext = cipher.decrypt(Buffer.from(ciphertext, 'base64'));

    return new TextDecoder().decode(plaintext);
  }
}

export { E2EEncryption };

Now integrate encryption with TRTC message sending and receiving:

// src/encrypted-messenger.js
import TencentCloudChat from '@tencentcloud/chat';
import { E2EEncryption } from './e2e-encryption.js';

class EncryptedMessenger {
  constructor(chat, e2e, userId) {
    this.chat = chat;
    this.e2e = e2e;
    this.userId = userId;
    this.peerKeys = new Map(); // walletAddress → publicKey
    this.onMessage = null; // callback for decrypted messages
    this._setupListener();
  }

  // Resolve recipient's E2E public key from their TRTC profile
  async _resolvePeerKey(recipientUserId) {
    if (this.peerKeys.has(recipientUserId)) {
      return this.peerKeys.get(recipientUserId);
    }

    const { data } = await this.chat.getUserProfile({
      userIDList: [recipientUserId],
    });
    const profile = data[0];
    const keyField = profile.profileCustomField?.find(
      f => f.key === 'Tag_Profile_Custom_E2EPubKey'
    );

    if (!keyField?.value) {
      throw new Error(`Recipient ${recipientUserId} has no E2E public key`);
    }

    this.peerKeys.set(recipientUserId, keyField.value);
    return keyField.value;
  }

  // Send an end-to-end encrypted message
  async sendEncrypted(recipientUserId, plaintext) {
    const peerKey = await this._resolvePeerKey(recipientUserId);
    const encrypted = this.e2e.encrypt(peerKey, plaintext);

    const message = this.chat.createCustomMessage({
      to: recipientUserId,
      conversationType: TencentCloudChat.TYPES.CONV_C2C,
      payload: {
        data: JSON.stringify({
          type: 'E2E_MESSAGE',
          version: 1,
          payload: encrypted,
        }),
        description: '[Encrypted Message]', // Push notification preview
        extension: '',
      },
    });

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

  // Listen for incoming messages and auto-decrypt
  _setupListener() {
    this.chat.on(TencentCloudChat.EVENT.MESSAGE_RECEIVED, (event) => {
      for (const msg of event.data) {
        if (msg.type !== TencentCloudChat.TYPES.MSG_CUSTOM) continue;

        try {
          const data = JSON.parse(msg.payload.data);
          if (data.type === 'E2E_MESSAGE') {
            const plaintext = this.e2e.decrypt(data.payload);
            this.onMessage?.({
              from: msg.from,
              content: plaintext,
              timestamp: msg.time * 1000,
              id: msg.ID,
            });
          } else {
            // Handle other custom message types
            this._handleCustomMessage(data, msg);
          }
        } catch (err) {
          console.error('Failed to process message:', err);
        }
      }
    });
  }

  _handleCustomMessage(data, msg) {
    // Route to appropriate handler based on type
    switch (data.type) {
      case 'TX_CONFIRMATION':
      case 'NFT_TRANSFER':
      case 'GOVERNANCE_PROPOSAL':
      case 'DEFI_ALERT':
        this.onMessage?.({
          from: msg.from,
          content: data,
          type: data.type,
          timestamp: msg.time * 1000,
          id: msg.ID,
        });
        break;
    }
  }
}

export { EncryptedMessenger };

Step 3: Custom Message Types for Blockchain Events

Web3 chat goes beyond text. Transaction confirmations, NFT transfers, governance proposals, and DeFi alerts are first-class message types:

// src/web3-message-types.js
import TencentCloudChat from '@tencentcloud/chat';

class Web3MessageFactory {
  constructor(chat) {
    this.chat = chat;
  }

  /**
   * Transaction confirmation message
   * Sent when a token transfer completes between two wallets
   */
  createTransactionConfirmation(recipientUserId, tx) {
    return this.chat.createCustomMessage({
      to: recipientUserId,
      conversationType: TencentCloudChat.TYPES.CONV_C2C,
      payload: {
        data: JSON.stringify({
          type: 'TX_CONFIRMATION',
          version: 1,
          payload: {
            txHash: tx.hash,
            from: tx.from,
            to: tx.to,
            value: tx.value,
            token: tx.token || 'ETH',
            decimals: tx.decimals || 18,
            chainId: tx.chainId,
            status: tx.status, // 'pending' | 'confirmed' | 'failed'
            blockNumber: tx.blockNumber,
            gasUsed: tx.gasUsed,
            timestamp: Date.now(),
          },
        }),
        description: `💸 ${tx.value} ${tx.token || 'ETH'} — ${tx.status}`,
        extension: JSON.stringify({ renderType: 'transaction-card' }),
      },
    });
  }

  /**
   * NFT transfer notification
   * Triggered when an NFT is sent, received, or listed
   */
  createNFTTransferNotification(recipientUserId, nft) {
    return this.chat.createCustomMessage({
      to: recipientUserId,
      conversationType: TencentCloudChat.TYPES.CONV_C2C,
      payload: {
        data: JSON.stringify({
          type: 'NFT_TRANSFER',
          version: 1,
          payload: {
            contractAddress: nft.contractAddress,
            tokenId: nft.tokenId,
            name: nft.name,
            collection: nft.collection,
            imageUrl: nft.imageUrl,
            from: nft.from,
            to: nft.to,
            txHash: nft.txHash,
            chainId: nft.chainId,
            standard: nft.standard, // 'ERC-721' | 'ERC-1155'
            action: nft.action, // 'transfer' | 'mint' | 'list' | 'sale'
            price: nft.price, // if sold
            timestamp: Date.now(),
          },
        }),
        description: `🖼️ ${nft.action}: ${nft.name} #${nft.tokenId}`,
        extension: JSON.stringify({
          renderType: 'nft-card',
          previewImage: nft.imageUrl,
        }),
      },
    });
  }

  /**
   * DeFi position alert
   * Liquidation warnings, yield updates, swap confirmations
   */
  createDeFiAlert(recipientUserId, alert) {
    return this.chat.createCustomMessage({
      to: recipientUserId,
      conversationType: TencentCloudChat.TYPES.CONV_C2C,
      payload: {
        data: JSON.stringify({
          type: 'DEFI_ALERT',
          version: 1,
          payload: {
            alertType: alert.type, // 'liquidation' | 'yield' | 'price' | 'swap'
            protocol: alert.protocol,
            chain: alert.chainId,
            position: alert.position,
            currentValue: alert.currentValue,
            threshold: alert.threshold,
            suggestedAction: alert.action,
            urgency: alert.urgency, // 'info' | 'warning' | 'critical'
            deepLink: alert.actionUrl,
            timestamp: Date.now(),
          },
        }),
        description: `⚠️ ${alert.urgency.toUpperCase()}: ${alert.protocol} — ${alert.type}`,
        extension: JSON.stringify({ renderType: 'defi-alert-card' }),
      },
    });
  }

  /**
   * Governance proposal message for DAO channels
   */
  createGovernanceProposal(groupId, proposal) {
    return this.chat.createCustomMessage({
      to: groupId,
      conversationType: TencentCloudChat.TYPES.CONV_GROUP,
      payload: {
        data: JSON.stringify({
          type: 'GOVERNANCE_PROPOSAL',
          version: 1,
          payload: {
            proposalId: proposal.id,
            title: proposal.title,
            summary: proposal.summary,
            fullTextUrl: proposal.url,
            options: proposal.options, // ['For', 'Against', 'Abstain']
            votingStart: proposal.startTime,
            votingEnd: proposal.endTime,
            quorum: proposal.quorum,
            snapshotBlock: proposal.snapshotBlock,
            governorContract: proposal.contractAddress,
            proposer: proposal.proposer,
            timestamp: Date.now(),
          },
        }),
        description: `🗳️ New Proposal: ${proposal.title}`,
        extension: JSON.stringify({
          renderType: 'governance-card',
          interactive: true,
        }),
      },
    });
  }
}

export { Web3MessageFactory };

Usage in your application:

// Example: Auto-send transaction confirmation after a token transfer
import { ethers } from 'ethers';

async function sendTokenAndNotify(messenger, factory, recipientWallet, amount, tokenAddress) {
  const provider = new ethers.BrowserProvider(window.ethereum);
  const signer = await provider.getSigner();

  // Execute the token transfer
  const token = new ethers.Contract(tokenAddress, ERC20_ABI, signer);
  const tx = await token.transfer(recipientWallet, ethers.parseUnits(amount, 18));
  const receipt = await tx.wait();

  // Send transaction confirmation via web3 chat
  const recipientUserId = recipientWallet.toLowerCase().slice(2);
  const confirmMsg = factory.createTransactionConfirmation(recipientUserId, {
    hash: receipt.hash,
    from: await signer.getAddress(),
    to: recipientWallet,
    value: amount,
    token: await token.symbol(),
    decimals: await token.decimals(),
    chainId: (await provider.getNetwork()).chainId,
    status: receipt.status === 1 ? 'confirmed' : 'failed',
    blockNumber: receipt.blockNumber,
    gasUsed: receipt.gasUsed.toString(),
  });

  await messenger.chat.sendMessage(confirmMsg);
}

Step 4: Group Chat for DAO Channels with Token-Gating

DAOs need structured group communication where membership is cryptographically verified by on-chain token holdings:

// src/dao-channels.js
import TencentCloudChat from '@tencentcloud/chat';
import { Contract, BrowserProvider } from 'ethers';

const ERC20_ABI = ['function balanceOf(address) view returns (uint256)'];
const ERC721_ABI = ['function balanceOf(address) view returns (uint256)'];

class DAOChannelManager {
  constructor(chat, signer) {
    this.chat = chat;
    this.signer = signer;
  }

  /**
   * Create a token-gated DAO channel
   * Only wallets holding sufficient tokens can join
   */
  async createChannel(config) {
    const {
      name,
      description,
      tokenContract,
      tokenStandard, // 'ERC20' | 'ERC721'
      minBalance,
      chainId,
      admins,
    } = config;

    const groupId = `dao_${tokenContract.slice(2, 10)}_${Date.now()}`;

    const result = await this.chat.createGroup({
      type: TencentCloudChat.TYPES.GRP_AVCHATROOM,
      name: name,
      groupID: groupId,
      introduction: description,
      notification: `Token-gated: Hold ${minBalance}+ ${tokenStandard === 'ERC721' ? 'NFTs' : 'tokens'} at ${tokenContract}`,
      memberList: admins.map(addr => ({
        userID: addr.toLowerCase().slice(2),
      })),
      groupCustomField: [
        { key: 'tokenContract', value: tokenContract },
        { key: 'tokenStandard', value: tokenStandard },
        { key: 'minBalance', value: minBalance.toString() },
        { key: 'chainId', value: chainId.toString() },
        { key: 'createdBy', value: await this.signer.getAddress() },
      ],
    });

    return { groupId, ...result.data.group };
  }

  /**
   * Verify token balance and join a DAO channel
   * Fails if wallet doesn't hold enough tokens
   */
  async verifyAndJoin(groupId) {
    // 1. Fetch group token-gating requirements
    const { data } = await this.chat.getGroupProfile({
      groupID: groupId,
      groupCustomFieldFilter: ['tokenContract', 'tokenStandard', 'minBalance', 'chainId'],
    });

    const fields = data.group.groupCustomField;
    const getField = (key) => fields.find(f => f.key === key)?.value;

    const tokenContract = getField('tokenContract');
    const tokenStandard = getField('tokenStandard');
    const minBalance = BigInt(getField('minBalance') || '0');
    const requiredChainId = parseInt(getField('chainId') || '1');

    // 2. Check user is on correct chain
    const provider = new BrowserProvider(window.ethereum);
    const network = await provider.getNetwork();
    if (Number(network.chainId) !== requiredChainId) {
      // Request chain switch
      await window.ethereum.request({
        method: 'wallet_switchEthereumChain',
        params: [{ chainId: `0x${requiredChainId.toString(16)}` }],
      });
    }

    // 3. Verify on-chain token balance
    const walletAddress = await this.signer.getAddress();
    const abi = tokenStandard === 'ERC721' ? ERC721_ABI : ERC20_ABI;
    const contract = new Contract(tokenContract, abi, provider);
    const balance = await contract.balanceOf(walletAddress);

    if (balance < minBalance) {
      throw new Error(
        `Token gate: need ${minBalance.toString()} but have ${balance.toString()} ` +
        `of ${tokenContract} on chain ${requiredChainId}`
      );
    }

    // 4. Sign proof of balance for backend verification
    const proof = await this.signer.signMessage(
      JSON.stringify({
        action: 'join_dao_channel',
        groupId,
        tokenContract,
        balance: balance.toString(),
        timestamp: Date.now(),
      })
    );

    // 5. Submit proof to backend, then join
    await this._submitJoinProof(groupId, walletAddress, balance.toString(), proof);
    await this.chat.joinGroup({
      groupID: groupId,
      type: TencentCloudChat.TYPES.GRP_AVCHATROOM,
    });

    return { success: true, groupId, verifiedBalance: balance.toString() };
  }

  /**
   * Create sub-channels for different DAO roles
   * e.g., "core-devs" (NFT holders), "delegates" (high token balance), "general"
   */
  async createRoleChannel(parentGroupId, roleConfig) {
    const { roleName, nftContract, minNFTCount } = roleConfig;

    return await this.chat.createGroup({
      type: TencentCloudChat.TYPES.GRP_PUBLIC,
      name: `${parentGroupId} / ${roleName}`,
      groupID: `${parentGroupId}_${roleName}`,
      maxMemberNum: roleConfig.maxMembers || 500,
      joinOption: TencentCloudChat.TYPES.JOIN_OPTIONS_NEED_PERMISSION,
      groupCustomField: [
        { key: 'parentGroup', value: parentGroupId },
        { key: 'role', value: roleName },
        { key: 'nftGate', value: nftContract || '' },
        { key: 'minNFTCount', value: (minNFTCount || 1).toString() },
      ],
    });
  }

  async _submitJoinProof(groupId, walletAddress, balance, signature) {
    await fetch('/api/dao/verify-join', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ groupId, walletAddress, balance, signature }),
    });
  }
}

export { DAOChannelManager };

Step 5: Complete Application Assembly

Wire everything together into a cohesive web3 messaging app:

// src/app.js
import { connectWalletAndLogin } from './chat-init.js';
import { E2EEncryption } from './e2e-encryption.js';
import { EncryptedMessenger } from './encrypted-messenger.js';
import { Web3MessageFactory } from './web3-message-types.js';
import { DAOChannelManager } from './dao-channels.js';

async function initializeWeb3Chat() {
  // 1. Connect wallet → TRTC login
  const { chat, userId, walletAddress, signer } = await connectWalletAndLogin();

  // 2. Initialize E2E encryption from wallet signature
  const e2e = await E2EEncryption.fromWalletSignature(signer);

  // 3. Publish E2E public key in TRTC profile
  await chat.updateMyProfile({
    profileCustomField: [
      { key: 'Tag_Profile_Custom_E2EPubKey', value: e2e.getPublicKeyHex() },
    ],
  });

  // 4. Create messenger and message factory
  const messenger = new EncryptedMessenger(chat, e2e, userId);
  const messageFactory = new Web3MessageFactory(chat);
  const daoManager = new DAOChannelManager(chat, signer);

  // 5. Set up message display callback
  messenger.onMessage = (msg) => {
    console.log(`[${msg.from}] ${msg.type || 'text'}: `, msg.content);
    // Route to your UI rendering layer
  };

  return { messenger, messageFactory, daoManager, walletAddress };
}

// Usage
const app = await initializeWeb3Chat();

// Send encrypted P2P message
await app.messenger.sendEncrypted(
  '742d35cc6634c0532925a3b844bc9e7595f2bd28', // recipient userId (wallet sans 0x)
  'Did you see the new governance proposal?'
);

// Create a DAO channel
await app.daoManager.createChannel({
  name: 'Uniswap Governance',
  description: 'Discussion for UNI token holders',
  tokenContract: '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
  tokenStandard: 'ERC20',
  minBalance: '1000000000000000000000', // 1000 UNI
  chainId: 1,
  admins: ['0x742d35Cc6634C0532925a3b844Bc9e7595f2bD28'],
});

Security: E2E Encryption and Key Management Deep Dive

Key Hierarchy

A proper web3 messaging security model uses a layered key hierarchy:

Wallet Private Key (master identity — never shared, never leaves secure enclave)
  │
  ├── Identity Key (derived via deterministic signature)
  │     Used for: proving identity, signing key announcements
  │
  ├── Encryption Key (X25519, derived via HKDF from wallet signature)
  │     Used for: ECDH key agreement with peers
  │
  └── Per-Conversation Keys (derived from shared secret + conversation ID)
        Used for: actual message encryption
        └── Message Keys (optionally ratcheted for forward secrecy)

Key Management Best Practices

1. Deterministic key derivation. Derive encryption keys from a wallet signature of a fixed message. This ensures the same wallet recreates the same keys on any device—no key backup infrastructure needed.

2. Key rotation. Rotate the encryption key periodically (every 30 days or on security events). Publish the new public key to your TRTC profile. Previously encrypted messages remain readable because old shared secrets are cached client-side.

3. Forward secrecy. For high-security conversations, implement a Double Ratchet on top of the X25519 base. Each message uses a new ephemeral key, so compromising one message key doesn't expose past or future messages.

4. Multi-device support. When a user accesses their wallet from a new device, they re-derive the same encryption keys (from the same wallet signature). Message history encrypted with these keys becomes immediately accessible without any sync protocol.

5. Recovery. If a wallet is compromised, the user creates a new wallet and publishes a signed key rotation announcement to their contacts. TRTC's group broadcast mechanism ensures all peers receive the new key efficiently.

Threat Model

ThreatMitigation
TRTC server reads messagesE2E encryption: servers only see ciphertext + metadata
Man-in-the-middle attackWallet signatures authenticate all public key announcements
Wallet private key theftDerived keys limit blast radius; rotate immediately on detection
Metadata analysisTRTC relay architecture prevents IP correlation between sender/recipient
Replay attacksUnique nonce per message + timestamp validation
Spam from random walletsToken-gating + rate limiting + on-chain reputation
Quantum computing (future)Monitor PQC standards; X25519 replaceable when NIST-approved KEM is finalized

Use Cases: Where Web3 Messaging Creates Value

DeFi Notifications and Alerts

Real-time alerts when positions approach liquidation thresholds, yield farming rewards become claimable, or governance proposals need votes. TRTC's offline push ensures these critical messages reach users even when the app isn't open—unlike pure P2P protocols that require an active connection.

// Example: Monitor Aave health factor and alert user
async function monitorLiquidationRisk(messenger, factory, userWallet) {
  const healthFactor = await getAaveHealthFactor(userWallet);

  if (healthFactor < 1.2) {
    const recipientUserId = userWallet.toLowerCase().slice(2);
    const alert = factory.createDeFiAlert(recipientUserId, {
      type: 'liquidation',
      protocol: 'Aave V3',
      chainId: 1,
      position: { collateral: '10 ETH', debt: '25000 USDC' },
      currentValue: healthFactor.toFixed(4),
      threshold: '1.0',
      action: 'Add collateral or repay debt immediately',
      urgency: 'critical',
      actionUrl: 'https://app.aave.com/dashboard',
    });
    await messenger.chat.sendMessage(alert);
  }
}

DAO Governance Communication

Replace Discord (which has no on-chain verification) with token-gated channels where only verified token holders participate. Proposal notifications become interactive cards. Vote results are posted with on-chain proof. Discussion threads are scoped to people who actually have governance power.

NFT Marketplace Communication

Buyers and sellers negotiate directly through wallet-to-wallet chat. The identity is the wallet—you know you're talking to the actual asset owner. Offer messages embed smart contract interaction data. Counteroffers, provenance questions, and trade confirmations flow through authenticated, encrypted channels.

GameFi Real-Time Communication

In-game voice and text chat where player identity is their wallet address. Guild channels are NFT-gated (own the guild badge NFT to join). Tournament brackets and results are posted as verifiable on-chain messages. TRTC's low-latency voice infrastructure (via GVoice, the game-optimized voice solution) handles real-time team communication during competitive gameplay where P2P protocols would add unacceptable lag.

Cross-Chain Messaging Hub

Users on different blockchains (Ethereum, Solana, Polygon, Arbitrum) can message each other using TRTC as the chain-agnostic transport layer. The wallet address format differs across chains, but TRTC's Chat SDK treats all as userId strings. A unified inbox across all chains becomes possible.

Token-Gated Customer Support

Web3 projects create support channels where only verified token holders can submit tickets. This eliminates spam, prioritizes actual community members, and ensures support resources aren't wasted on bots or non-users.

Accelerating Development with TRTC MCP Server

For development teams building web3 messaging applications, TRTC offers an MCP (Model Context Protocol) server that dramatically speeds up integration. Connect the TRTC MCP server to your AI coding assistant (Claude, Cursor, Windsurf) to:

  • Generate integration code: Describe your web3 messaging requirements in natural language and get production-ready TRTC Chat SDK code with wallet authentication pre-configured
  • Scaffold custom message types: Generate typed message definitions for your specific blockchain events (DeFi alerts, NFT notifications, governance proposals)
  • Debug delivery issues: Query message delivery status, encryption handshake state, and group membership in real-time
  • Optimize architecture: Get recommendations for encryption patterns, scaling strategies, and identity mapping based on your chain and protocol choices

The MCP server understands TRTC's full API surface and generates code that follows best practices—cutting integration time from weeks to days for complex web3 messaging features.

Architecture Decision Guide

When to Choose Pure Decentralized (XMTP/Waku)

  • Maximum censorship resistance is a hard requirement
  • Your users accept higher latency (1-5 seconds)
  • Text messaging only (no voice, video, or large files)
  • Small community size (<1,000 active users)
  • You're building a protocol others will build on

When to Choose Hybrid (TRTC + Web3 Identity)

  • Production reliability is required (99.99% uptime)
  • Users expect sub-second message delivery
  • Rich media: voice, video, images, files, custom cards
  • Large communities: 10K to 100K+ members
  • Mobile push notifications are non-negotiable
  • You need group features: typing indicators, read receipts, message reactions
  • Compliance requirements exist (configurable content moderation)

When to Use TRTC as Protocol-Agnostic Transport

  • You're building a platform that integrates multiple protocols (XMTP + Push + custom)
  • You need a reliable delivery layer while focusing on identity and encryption innovation
  • Your application spans voice, video, and messaging in a unified experience
  • You want to start with working infrastructure and progressively decentralize

Performance Comparison

Pure P2P messaging protocols face hard physics and engineering constraints:

MetricP2P (Waku/libp2p)TRTC Transport
Message latency (p50)800ms–2s<150ms
Message latency (p99)3–10s<300ms
Offline deliveryBest-effort (node must be online)Guaranteed (offline push + queue)
Group broadcast (1K members)5–30s propagation<500ms
Mobile battery impactHigh (persistent P2P connections)Low (optimized keep-alive)
NAT traversal success60–70% (rest needs relay)100% (edge network relay)

For web3 messaging apps competing with WhatsApp's UX while maintaining decentralization values, TRTC's Chat API provides the performance foundation that P2P networks can't match today. Decentralization belongs at the identity and encryption layers; transport benefits from infrastructure investment.

FAQ

What is web3 messaging and how does it differ from regular messaging?

Web3 messaging is a communication system where user identity comes from blockchain wallet addresses instead of phone numbers or emails. Messages are encrypted with keys only the wallet holder controls, stored on decentralized networks, and resistant to censorship. The key difference from apps like WhatsApp: no corporation controls your identity, data, or ability to communicate.

Can I build a web3 chat app without deep blockchain expertise?

Yes. With TRTC's Chat SDK, the blockchain component is limited to wallet connection (a few lines of ethers.js). The wallet address maps directly to a TRTC userId—everything else (message routing, delivery, storage, push notifications) uses standard Chat SDK APIs. Add token-gating and on-chain verification incrementally as needed.

Which web3 messaging protocol should I choose?

It depends on priorities. XMTP for wallet-to-wallet DMs in existing dApps. Push Protocol for cross-chain notification systems. Waku for building protocol-level infrastructure. For production applications needing reliable delivery, rich media, large groups, and mobile push, use TRTC as the transport layer combined with any of these protocols for identity and encryption.

Is web3 messaging truly private?

When implemented correctly—more private than Web2 alternatives. E2E encryption protects content. Wallet-based identity eliminates personal data collection. No phone number, email, or name required. The caveat: on-chain transactions are public, so a wallet address could be linked to a real identity through blockchain analysis. For maximum privacy, use fresh wallets for sensitive conversations.

How do web3 messaging apps prevent spam?

Multiple mechanisms: token-gating (require minimum balance to message), on-chain reputation scoring, cryptoeconomic deterrents (stake tokens to send, slashed for spam), Rate-Limiting Nullifiers (RLN) at the protocol level, and TRTC's configurable rate limiting and content filtering. The combination of economic and technical barriers makes spam far harder than in open systems.

What happens to messages if I lose my wallet?

Messages encrypted with keys derived from the wallet are unrecoverable if the wallet is lost—the same tradeoff as losing your crypto. Best practices: use social recovery wallets (Safe, Argent), export encrypted message archives to IPFS with a separate recovery key, or implement multi-sig key backup where trusted contacts can help restore access.

Does web3 messaging support voice and video?

Yes, through TRTC. Voice and video calls use the same wallet-based identity and can be E2E encrypted with the same key exchange mechanism as text. This enables DAO governance meetings, GameFi team communication, NFT negotiation calls, and any scenario where real-time audio/video is needed between wallet-authenticated participants.

How does TRTC handle wallet-based identity?

TRTC's Chat SDK accepts any string as a userId. By mapping wallet addresses (minus the 0x prefix, lowercased) to userIds, you get wallet-based identity with zero custom infrastructure. The Chat SDK handles routing, storage, and delivery indexed by this wallet-derived userId. Users connect with MetaMask, WalletConnect, Coinbase Wallet, or any provider—the messaging layer doesn't care which wallet software they use.

Next Steps

  1. Get started free: Create a TRTC Chat API account — the free tier includes 100 DAUs with full SDK access, enough to build and test your web3 messaging prototype
  2. Explore the Web3 solution: See TRTC's complete Web3 communication infrastructure including identity mapping, token-gating patterns, and blockchain event integration
  3. Chat SDK documentation: Full API reference for message types, groups, encryption, and custom messages
  4. MCP integration: Connect the TRTC MCP server to your development environment for AI-accelerated integration

Web3 messaging is transitioning from experimental protocols to production systems. The applications that win adoption will deliver decentralization guarantees—wallet identity, E2E encryption, censorship resistance—without asking users to accept worse performance, fewer features, or broken UX. That's the gap TRTC fills: professional-grade real-time infrastructure that makes decentralized communication actually work at scale.