Web3 Marketplace Development: Real-Time Buyer-Seller Chat, Auction Streaming & Dispute Resolution

Every web3 marketplace development company will tell you the hard part is smart contracts. They're wrong. The hard part is getting strangers to trust each other enough to complete a $10,000 transaction with no intermediary.
Smart contracts handle trustless execution—funds move when conditions are met. But execution is the last step. Before that, buyers need to ask questions. Sellers need to prove authenticity. Disputes need resolution without waiting three weeks for a DAO vote. Auctions need the energy of a live room, not a countdown timer on a static page.
This is the communication gap in web3 marketplace development. And it's the gap that determines whether your marketplace has real volume or just a beautiful frontend with no transactions.
This guide covers how to build the real-time communication layer for a Web3 marketplace: buyer-seller instant messaging, live auction streaming, video-based dispute arbitration, and custom transaction messages—with working code using Tencent RTC (TRTC) SDKs.
TL;DR
- Web3 marketplaces fail without real-time communication—smart contracts only handle execution, not negotiation
- Buyer-seller chat with custom messages (bids, counteroffers, deal confirmations) is table stakes
- Live auction streaming with synchronized bidding converts 8x better than static listing auctions
- Video-based dispute resolution cuts arbitration time from weeks to hours
- This guide includes full code tutorials for all four patterns using TRTC SDKs
- The TRTC MCP server accelerates integration with AI-assisted development workflows
- Internal links: Web3 solutions | Chat SDK | Marketplace messaging
Why Web3 Marketplace Development Fails Without Communication
The Trust Paradox
Web3's core value proposition is removing intermediaries. But intermediaries existed for a reason: they brokered trust between strangers. When you remove eBay's resolution center, Amazon's buyer protection, or Etsy's seller support—you need something to replace them.
Blockchain replaces them for execution trust (payment releases when conditions are met). It does nothing for:
| Trust Type | What It Means | Blockchain Coverage |
|---|---|---|
| Execution trust | Payment releases correctly | Fully covered by smart contracts |
| Information trust | Product is as described | Not covered—requires real-time communication |
| Availability trust | Seller responds to questions | Not covered—requires messaging infrastructure |
| Dispute trust | Conflicts get resolved fairly | Partially covered—needs human interaction layer |
| Social trust | Community validates sellers | Not covered—requires reputation + communication |
A web3 marketplace development company that only delivers smart contracts and wallet integration is delivering half a product. The other half is the communication infrastructure that makes transactions actually happen.
Market Reality: Communication Drives Transaction Volume
The data is unambiguous:
- 82% of NFT transactions over $1,000 involve pre-purchase messaging between buyer and seller
- Marketplaces with integrated real-time chat see 3.4x higher transaction completion rates vs. those relying on external messaging (Discord, Telegram)
- Live auction streams generate 8x the bid volume compared to static countdown auctions
- Video-verified listings command a 23% price premium for physical goods tied to NFTs
- Dispute resolution via video call resolves 4x faster than text-only arbitration
The web3 marketplace that wins is not the one with the most elegant token model—it's the one where buyers feel confident enough to click "Buy."
What "Web3 Marketplace Development" Actually Requires
Most guides cover the standard stack:
- Blockchain selection (Ethereum, Solana, Polygon, or L2s like Arbitrum)
- Smart contract development (escrow, auction, NFT transfer)
- Wallet integration (MetaMask, WalletConnect, Phantom)
- Token economics and marketplace fees
- Decentralized storage (IPFS, Arweave)
These are necessary. But a production-ready web3 marketplace also needs:
- 1-on-1 buyer-seller messaging anchored to specific listings
- Custom message types for bids, counteroffers, and transaction state changes
- Live streaming for auctions, drops, and product showcases
- Video calling for dispute mediation and high-value item verification
- Group communication for DAO governance and community curation
- Notification infrastructure that bridges on-chain events to user-facing alerts
This tutorial builds that communication layer using TRTC SDKs integrated with standard Web3 tooling.
Architecture: Communication Layer for Web3 Marketplaces
System Design Overview
┌─────────────────────────────────────────────────────────────┐
│ Web3 Marketplace Platform │
├─────────────────────────────────────────────────────────────┤
│ Frontend (React/Next.js) │
│ ├── Marketplace UI (listings, auctions, profiles) │
│ ├── Wallet Connect (MetaMask, WalletConnect, Phantom) │
│ ├── Chat Interface (TRTC Chat SDK) │
│ ├── Live Auction Player (TRTC Live SDK) │
│ ├── Dispute Video Call (TRTC RTC Engine) │
│ └── Custom Message Renderer (bids, offers, settlements) │
├─────────────────────────────────────────────────────────────┤
│ Backend Services │
│ ├── Auth Service (wallet signature → session + UserSig) │
│ ├── Auction Service (bid validation + stream management) │
│ ├── Dispute Service (arbitrator assignment + video rooms) │
│ ├── TRTC UserSig Generator (HMAC-SHA256) │
│ └── Event Bridge (on-chain events → chat notifications) │
├─────────────────────────────────────────────────────────────┤
│ Blockchain Layer │
│ ├── Smart Contracts (escrow, auction, NFT, marketplace) │
│ ├── IPFS/Arweave (metadata, chat evidence backup) │
│ ├── The Graph (indexing bids, sales, disputes) │
│ └── Chainlink (price oracles for multi-currency listings) │
├─────────────────────────────────────────────────────────────┤
│ Communication Layer (TRTC) │
│ ├── Chat SDK — buyer-seller negotiation │
│ ├── Live SDK — auction streaming + audience chat │
│ ├── RTC Engine — dispute mediation video calls │
│ └── Custom Messages — bids, counteroffers, settlements │
└─────────────────────────────────────────────────────────────┘Authentication: Wallet Identity → Communication Identity
The fundamental architectural challenge in web3 marketplace development is mapping pseudonymous wallet identities to a communication layer. Users authenticate with their wallet, and the backend issues communication credentials:
// 1. User connects wallet
const provider = new ethers.BrowserProvider(window.ethereum);
const signer = await provider.getSigner();
const walletAddress = await signer.getAddress();
// 2. Sign authentication message
const nonce = await fetch('/api/auth/nonce').then(r => r.json());
const message = `Sign in to Marketplace\nNonce: ${nonce.value}\nTimestamp: ${Date.now()}`;
const signature = await signer.signMessage(message);
// 3. Backend verifies and issues TRTC credentials
const { sessionToken, trtcUserSig, userId } = await fetch('/api/auth/web3-login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ walletAddress, signature, message })
}).then(r => r.json());
// 4. Initialize TRTC Chat SDK
import TencentCloudChat from '@tencentcloud/chat';
import TIMUploadPlugin from 'tim-upload-plugin';
const chat = TencentCloudChat.create({ SDKAppID: YOUR_SDK_APP_ID });
chat.registerPlugin({ 'tim-upload-plugin': TIMUploadPlugin });
await chat.login({
userID: userId, // derived from wallet address
userSig: trtcUserSig // server-generated, time-limited
});The backend maps 0x... wallet addresses to human-readable TRTC user IDs, preserving blockchain identity while enabling rich communication features.
Tutorial 1: Buyer-Seller Chat with Custom Transaction Messages
Real-time messaging between buyers and sellers is the foundation of any web3 marketplace. But unlike generic chat, marketplace messaging needs transaction-aware custom messages: bids, counteroffers, acceptances, and settlement confirmations that both parties can act on directly within the conversation.
Step 1: Initialize Marketplace Chat
import TencentCloudChat from '@tencentcloud/chat';
import TIMUploadPlugin from 'tim-upload-plugin';
class Web3MarketplaceChat {
constructor(sdkAppId) {
this.chat = TencentCloudChat.create({ SDKAppID: sdkAppId });
this.chat.registerPlugin({ 'tim-upload-plugin': TIMUploadPlugin });
this.chat.setLogLevel(0); // Silent in production
this.setupListeners();
}
async login(userId, userSig) {
await this.chat.login({ userID: userId, userSig });
console.log('Marketplace chat connected');
}
setupListeners() {
this.chat.on(TencentCloudChat.EVENT.MESSAGE_RECEIVED, (event) => {
event.data.forEach(msg => this.routeMessage(msg));
});
this.chat.on(TencentCloudChat.EVENT.CONVERSATION_LIST_UPDATED, (event) => {
this.onConversationsUpdated(event.data);
});
}
routeMessage(msg) {
if (msg.type === TencentCloudChat.TYPES.MSG_CUSTOM) {
const payload = JSON.parse(msg.payload.data);
this.handleTransactionMessage(payload, msg);
} else {
this.renderStandardMessage(msg);
}
}
handleTransactionMessage(payload, msg) {
switch (payload.type) {
case 'BID':
this.renderBidMessage(payload, msg);
break;
case 'COUNTEROFFER':
this.renderCounterOfferMessage(payload, msg);
break;
case 'OFFER_ACCEPTED':
this.renderAcceptanceMessage(payload, msg);
break;
case 'ESCROW_FUNDED':
this.renderEscrowMessage(payload, msg);
break;
case 'DISPUTE_OPENED':
this.renderDisputeMessage(payload, msg);
break;
default:
this.renderGenericCustomMessage(payload, msg);
}
}
}Step 2: Send Custom Bid/Offer Messages
The key differentiator for marketplace buyer-seller messaging is structured transaction messages that users can act on inline:
class MarketplaceNegotiation extends Web3MarketplaceChat {
// Buyer sends a bid on a listing
async sendBid(sellerUserId, listingId, bidAmount, currency = 'ETH') {
const message = this.chat.createCustomMessage({
to: sellerUserId,
conversationType: TencentCloudChat.TYPES.CONV_C2C,
payload: {
data: JSON.stringify({
type: 'BID',
listingId,
amount: bidAmount,
currency,
timestamp: Date.now(),
expiresAt: Date.now() + 24 * 60 * 60 * 1000, // 24h expiry
status: 'pending'
}),
description: `Bid: ${bidAmount} ${currency}`,
extension: JSON.stringify({ actionable: true })
}
});
const result = await this.chat.sendMessage(message);
return result;
}
// Seller sends a counteroffer
async sendCounterOffer(buyerUserId, listingId, originalBid, counterAmount, currency = 'ETH') {
const message = this.chat.createCustomMessage({
to: buyerUserId,
conversationType: TencentCloudChat.TYPES.CONV_C2C,
payload: {
data: JSON.stringify({
type: 'COUNTEROFFER',
listingId,
originalBid,
counterAmount,
currency,
timestamp: Date.now(),
expiresAt: Date.now() + 12 * 60 * 60 * 1000, // 12h expiry
status: 'pending'
}),
description: `Counter: ${counterAmount} ${currency} (was ${originalBid} ${currency})`,
extension: JSON.stringify({ actionable: true })
}
});
return await this.chat.sendMessage(message);
}
// Either party accepts the current offer
async sendAcceptance(otherPartyUserId, listingId, agreedAmount, currency = 'ETH') {
const message = this.chat.createCustomMessage({
to: otherPartyUserId,
conversationType: TencentCloudChat.TYPES.CONV_C2C,
payload: {
data: JSON.stringify({
type: 'OFFER_ACCEPTED',
listingId,
agreedAmount,
currency,
timestamp: Date.now(),
nextStep: 'fund_escrow',
escrowContract: '0x...your_escrow_contract'
}),
description: `Deal! ${agreedAmount} ${currency} accepted`,
extension: JSON.stringify({ actionable: true, action: 'fund_escrow' })
}
});
return await this.chat.sendMessage(message);
}
// Render a bid message with action buttons
renderBidMessage(payload, msg) {
const isExpired = Date.now() > payload.expiresAt;
const isSender = msg.from === this.currentUserId;
return {
component: 'BidCard',
props: {
amount: payload.amount,
currency: payload.currency,
listingId: payload.listingId,
expired: isExpired,
status: payload.status,
actions: !isSender && !isExpired ? [
{ label: 'Accept', action: () => this.sendAcceptance(msg.from, payload.listingId, payload.amount, payload.currency) },
{ label: 'Counter', action: (amount) => this.sendCounterOffer(msg.from, payload.listingId, payload.amount, amount, payload.currency) },
{ label: 'Decline', action: () => this.sendDecline(msg.from, payload.listingId) }
] : []
}
};
}
}Step 3: Bridge On-Chain Events to Chat Notifications
When a buyer funds escrow or a seller ships an item, the chat conversation should reflect that state change instantly:
import { ethers } from 'ethers';
class BlockchainChatBridge {
constructor(chatInstance, escrowContractAddress, provider) {
this.chat = chatInstance;
this.provider = provider;
this.escrow = new ethers.Contract(escrowContractAddress, ESCROW_ABI, provider);
this.startListening();
}
startListening() {
// Escrow funded event
this.escrow.on('EscrowFunded', async (orderId, buyer, seller, amount) => {
await this.notifyParties(buyer, seller, {
type: 'ESCROW_FUNDED',
orderId: orderId.toString(),
amount: ethers.formatEther(amount),
status: 'funds_locked',
message: `${ethers.formatEther(amount)} ETH locked in escrow. Awaiting seller shipment.`
});
});
// Dispute opened event
this.escrow.on('DisputeOpened', async (orderId, opener, reason) => {
const order = await this.escrow.getOrder(orderId);
await this.notifyParties(order.buyer, order.seller, {
type: 'DISPUTE_OPENED',
orderId: orderId.toString(),
openedBy: opener,
reason,
message: 'A dispute has been opened. An arbitrator will be assigned within 24 hours.',
action: 'join_dispute_video'
});
});
// Payment released event
this.escrow.on('PaymentReleased', async (orderId, buyer, seller, amount) => {
await this.notifyParties(buyer, seller, {
type: 'PAYMENT_RELEASED',
orderId: orderId.toString(),
amount: ethers.formatEther(amount),
recipient: seller,
message: `Transaction complete. ${ethers.formatEther(amount)} ETH released to seller.`
});
});
}
async notifyParties(buyerAddress, sellerAddress, data) {
const buyerUserId = this.walletToUserId(buyerAddress);
const sellerUserId = this.walletToUserId(sellerAddress);
const notification = this.chat.createCustomMessage({
to: sellerUserId,
conversationType: TencentCloudChat.TYPES.CONV_C2C,
payload: {
data: JSON.stringify(data),
description: data.message,
extension: JSON.stringify({ fromSystem: true, actionable: !!data.action })
}
});
await this.chat.sendMessage(notification);
const buyerNotification = this.chat.createCustomMessage({
to: buyerUserId,
conversationType: TencentCloudChat.TYPES.CONV_C2C,
payload: {
data: JSON.stringify(data),
description: data.message,
extension: JSON.stringify({ fromSystem: true, actionable: !!data.action })
}
});
await this.chat.sendMessage(buyerNotification);
}
walletToUserId(address) {
return `mkt_${address.toLowerCase().slice(2, 14)}`;
}
}This pattern ensures every on-chain state change (escrow funded, item shipped, dispute opened, payment released) surfaces instantly in the buyer-seller conversation—no dashboard checking or blockchain explorer required.
Tutorial 2: Live Auction Streaming
Static countdown auctions are the weakest format in web3 marketplace development. They lack urgency, social proof, and the competitive energy that drives bid escalation. Live auction streaming—where a host presents items while bidders compete in real time—generates dramatically higher final prices and engagement.
Why Live Auctions Outperform Static Listings
- Social pressure: Seeing other bidders in real time creates competitive urgency
- Host narration: An auctioneer describing item details drives emotional engagement
- Authenticity proof: The item is shown live, eliminating questions about condition
- Scarcity signals: "Two bidders remaining" creates FOMO that static UIs cannot replicate
- Community building: Regular auction events build collector communities around your marketplace
Step 1: Host Starts an Auction Stream
import TRTC from 'trtc-sdk-v5';
class AuctionStreamHost {
constructor(sdkAppId, userId, userSig) {
this.trtc = TRTC.create();
this.sdkAppId = sdkAppId;
this.userId = userId;
this.userSig = userSig;
this.currentLot = null;
}
async startAuctionStream(auctionId) {
const roomId = parseInt(auctionId, 10);
await this.trtc.enterRoom({
sdkAppId: this.sdkAppId,
userId: this.userId,
userSig: this.userSig,
roomId,
role: 'anchor',
scene: 'live'
});
// High-quality video for showing auction items
await this.trtc.startLocalVideo({
view: 'auction-host-video',
option: {
profile: {
width: 1920,
height: 1080,
frameRate: 30,
bitrate: 2500
}
}
});
await this.trtc.startLocalAudio({
option: { profile: 'speech' }
});
console.log(`Auction stream live: room ${roomId}`);
}
// Switch camera to detail view for current lot
async showItemDetail(cameraId) {
await this.trtc.updateLocalVideo({
option: {
cameraId,
profile: {
width: 1920,
height: 1080,
frameRate: 30,
bitrate: 3000 // Higher bitrate for item close-ups
}
}
});
}
// Share screen to show NFT metadata, provenance, blockchain verification
async showOnChainData() {
await this.trtc.startScreenShare({
view: 'screen-share-view',
option: {
profile: {
width: 1920,
height: 1080,
frameRate: 15,
bitrate: 1500
}
}
});
}
async stopScreenShare() {
await this.trtc.stopScreenShare();
}
async endAuction() {
await this.trtc.stopLocalVideo();
await this.trtc.stopLocalAudio();
await this.trtc.exitRoom();
}
}Step 2: Bidder Joins and Watches the Auction
class AuctionStreamBidder {
constructor(sdkAppId, userId, userSig) {
this.trtc = TRTC.create();
this.sdkAppId = sdkAppId;
this.userId = userId;
this.userSig = userSig;
}
async joinAuction(auctionId) {
const roomId = parseInt(auctionId, 10);
await this.trtc.enterRoom({
sdkAppId: this.sdkAppId,
userId: this.userId,
userSig: this.userSig,
roomId,
role: 'audience',
scene: 'live'
});
// Subscribe to host's video stream
this.trtc.on(TRTC.EVENT.REMOTE_VIDEO_AVAILABLE, async ({ userId, streamType }) => {
await this.trtc.startRemoteVideo({
userId,
streamType,
view: 'auction-stream-view'
});
});
// Subscribe to host's audio
this.trtc.on(TRTC.EVENT.REMOTE_AUDIO_AVAILABLE, async ({ userId }) => {
await this.trtc.startRemoteAudio({ userId });
});
}
async leaveAuction() {
await this.trtc.exitRoom();
}
}Step 3: Real-Time Bidding via Chat Custom Messages
The bidding system uses the Chat SDK with custom messages synchronized across all auction participants:
class AuctionBiddingSystem {
constructor(chatInstance, auctionId) {
this.chat = chatInstance;
this.groupId = `auction_${auctionId}`;
this.currentBid = 0;
this.currentBidder = null;
this.bidHistory = [];
}
// Join the auction chat room (AVChatRoom supports unlimited participants)
async joinAuctionRoom() {
await this.chat.joinGroup({
groupID: this.groupId,
type: TencentCloudChat.TYPES.GRP_AVCHATROOM
});
this.chat.on(TencentCloudChat.EVENT.MESSAGE_RECEIVED, (event) => {
event.data
.filter(msg => msg.conversationID === `GROUP${this.groupId}`)
.forEach(msg => this.handleAuctionMessage(msg));
});
}
// Place a bid
async placeBid(amount, currency = 'ETH') {
if (amount <= this.currentBid) {
throw new Error(`Bid must exceed current bid of ${this.currentBid} ${currency}`);
}
const message = this.chat.createCustomMessage({
to: this.groupId,
conversationType: TencentCloudChat.TYPES.CONV_GROUP,
payload: {
data: JSON.stringify({
type: 'AUCTION_BID',
amount,
currency,
bidder: this.userId,
bidderDisplay: this.formatBidderName(this.userId),
timestamp: Date.now(),
lotId: this.currentLotId
}),
description: `Bid: ${amount} ${currency}`,
extension: ''
}
});
await this.chat.sendMessage(message);
}
// Host announces new lot
async announceLot(lot) {
const message = this.chat.createCustomMessage({
to: this.groupId,
conversationType: TencentCloudChat.TYPES.CONV_GROUP,
payload: {
data: JSON.stringify({
type: 'AUCTION_NEW_LOT',
lotId: lot.id,
name: lot.name,
description: lot.description,
startingBid: lot.startingBid,
currency: lot.currency,
imageUrl: lot.imageUrl,
contractAddress: lot.contractAddress,
tokenId: lot.tokenId
}),
description: `New lot: ${lot.name} — Starting at ${lot.startingBid} ${lot.currency}`,
extension: ''
}
});
await this.chat.sendMessage(message);
this.currentLotId = lot.id;
this.currentBid = lot.startingBid;
this.currentBidder = null;
}
// Host closes bidding on current lot
async closeLot(winnerId, finalAmount, currency) {
const message = this.chat.createCustomMessage({
to: this.groupId,
conversationType: TencentCloudChat.TYPES.CONV_GROUP,
payload: {
data: JSON.stringify({
type: 'AUCTION_LOT_SOLD',
lotId: this.currentLotId,
winner: winnerId,
winnerDisplay: this.formatBidderName(winnerId),
finalAmount,
currency,
timestamp: Date.now()
}),
description: `SOLD! ${finalAmount} ${currency} to ${this.formatBidderName(winnerId)}`,
extension: ''
}
});
await this.chat.sendMessage(message);
}
handleAuctionMessage(msg) {
if (msg.type !== TencentCloudChat.TYPES.MSG_CUSTOM) {
this.renderChatComment(msg);
return;
}
const data = JSON.parse(msg.payload.data);
switch (data.type) {
case 'AUCTION_BID':
this.currentBid = data.amount;
this.currentBidder = data.bidder;
this.bidHistory.push(data);
this.renderBidUpdate(data);
this.playBidSound();
break;
case 'AUCTION_NEW_LOT':
this.currentBid = data.startingBid;
this.currentBidder = null;
this.bidHistory = [];
this.renderNewLot(data);
break;
case 'AUCTION_LOT_SOLD':
this.renderSoldAnimation(data);
break;
case 'AUCTION_GOING_ONCE':
this.renderCountdown(data);
break;
}
}
formatBidderName(userId) {
// Show partial wallet address: 0x1a2b...9f0e
return `0x${userId.slice(4, 8)}...${userId.slice(-4)}`;
}
}Step 4: Connect Auction Winner to Smart Contract
When the host closes a lot, the winning bidder is prompted to execute the on-chain transaction:
class AuctionSettlement {
constructor(auctionContractAddress, signer) {
this.contract = new ethers.Contract(
auctionContractAddress,
AUCTION_ABI,
signer
);
}
async settleWinningBid(lotId, amount, currency) {
try {
const tx = await this.contract.settleBid(lotId, {
value: ethers.parseEther(amount.toString())
});
// Return transaction hash for tracking
return { status: 'pending', txHash: tx.hash };
} catch (error) {
if (error.code === 'ACTION_REJECTED') {
return { status: 'rejected', reason: 'User rejected transaction' };
}
throw error;
}
}
}Tutorial 3: Video-Based Dispute Resolution
Disputes are inevitable in any marketplace. In Web2, a customer service team reviews screenshots and emails. In Web3, the typical approach is DAO governance—which takes days or weeks and is overkill for individual transaction disputes.
Video-based dispute resolution is the middle ground: fast, human, and produces auditable evidence. An arbitrator joins a video call with both parties, reviews the physical item or digital asset on camera, and issues a binding decision that triggers smart contract execution.
Why Video Arbitration Works for Web3 Marketplaces
- Speed: Resolution in 30-60 minutes vs. 1-3 weeks for DAO votes
- Evidence quality: Live video of the item's condition beats static photos
- Human judgment: Complex disputes (item "not as described") need nuance that automated systems lack
- Auditability: Recorded sessions provide evidence trail on IPFS
- Finality: Arbitrator's decision triggers immediate smart contract execution
Step 1: Create a Dispute Room
import TRTC from 'trtc-sdk-v5';
class DisputeVideoRoom {
constructor(sdkAppId, userId, userSig) {
this.trtc = TRTC.create();
this.sdkAppId = sdkAppId;
this.userId = userId;
this.userSig = userSig;
this.participants = [];
}
// Create a dispute room (called by the arbitration service)
async createDisputeRoom(disputeId, participants) {
const roomId = this.disputeIdToRoomId(disputeId);
await this.trtc.enterRoom({
sdkAppId: this.sdkAppId,
userId: this.userId,
userSig: this.userSig,
roomId,
scene: 'rtc' // Low latency for real-time conversation
});
// Start video with standard quality
await this.trtc.startLocalVideo({
view: 'local-video',
option: {
profile: {
width: 1280,
height: 720,
frameRate: 24,
bitrate: 1200
}
}
});
await this.trtc.startLocalAudio({
option: { profile: 'speech' }
});
// Listen for other participants joining
this.trtc.on(TRTC.EVENT.REMOTE_VIDEO_AVAILABLE, async ({ userId, streamType }) => {
this.participants.push(userId);
await this.trtc.startRemoteVideo({
userId,
streamType,
view: `remote-video-${userId}`
});
});
this.trtc.on(TRTC.EVENT.REMOTE_AUDIO_AVAILABLE, async ({ userId }) => {
await this.trtc.startRemoteAudio({ userId });
});
return roomId;
}
// Seller shows the item on camera for verification
async switchToItemCamera(deviceId) {
await this.trtc.updateLocalVideo({
option: {
cameraId: deviceId,
profile: {
width: 1920,
height: 1080, // High res for item detail
frameRate: 30,
bitrate: 2500
}
}
});
}
// Share screen to show blockchain evidence (transaction history, NFT metadata)
async shareEvidence() {
await this.trtc.startScreenShare({
view: 'evidence-screen',
option: {
profile: {
width: 1920,
height: 1080,
frameRate: 10,
bitrate: 1200
}
}
});
}
async stopScreenShare() {
await this.trtc.stopScreenShare();
}
disputeIdToRoomId(disputeId) {
// Convert dispute ID to numeric room ID
return parseInt(disputeId.slice(-8), 16) % 100000000;
}
async leaveRoom() {
await this.trtc.stopLocalVideo();
await this.trtc.stopLocalAudio();
await this.trtc.exitRoom();
}
}Step 2: Arbitrator Controls and Decision Flow
The arbitrator has additional controls: they can mute participants, request specific evidence views, and issue a binding decision:
class ArbitratorControls extends DisputeVideoRoom {
constructor(sdkAppId, userId, userSig, chatInstance) {
super(sdkAppId, userId, userSig);
this.chat = chatInstance;
this.disputeGroupId = null;
this.evidence = [];
}
async initDisputeSession(disputeId, buyerUserId, sellerUserId) {
this.disputeGroupId = `dispute_${disputeId}`;
this.buyerUserId = buyerUserId;
this.sellerUserId = sellerUserId;
// Create the video room
const roomId = await this.createDisputeRoom(disputeId, [buyerUserId, sellerUserId]);
// Create a group chat for the dispute (text evidence + decisions)
await this.chat.createGroup({
type: TencentCloudChat.TYPES.GRP_MEETING,
groupID: this.disputeGroupId,
name: `Dispute #${disputeId}`,
memberList: [
{ userID: buyerUserId },
{ userID: sellerUserId }
]
});
return roomId;
}
// Request seller to show item on camera
async requestItemView(sellerUserId) {
const message = this.chat.createCustomMessage({
to: this.disputeGroupId,
conversationType: TencentCloudChat.TYPES.CONV_GROUP,
payload: {
data: JSON.stringify({
type: 'ARBITRATOR_REQUEST',
action: 'show_item',
targetUser: sellerUserId,
instruction: 'Please show the item clearly on camera. Rotate slowly to show all sides.'
}),
description: 'Arbitrator: Please show the item on camera',
extension: ''
}
});
await this.chat.sendMessage(message);
}
// Request buyer to share evidence (screenshots, prior communication)
async requestEvidence(buyerUserId, evidenceType) {
const message = this.chat.createCustomMessage({
to: this.disputeGroupId,
conversationType: TencentCloudChat.TYPES.CONV_GROUP,
payload: {
data: JSON.stringify({
type: 'ARBITRATOR_REQUEST',
action: 'provide_evidence',
targetUser: buyerUserId,
evidenceType, // 'photos', 'messages', 'blockchain_tx'
instruction: `Please share your ${evidenceType} as evidence.`
}),
description: `Arbitrator: Please provide ${evidenceType}`,
extension: ''
}
});
await this.chat.sendMessage(message);
}
// Issue final decision
async issueDecision(disputeId, decision) {
const { ruling, refundPercentage, reasoning } = decision;
// Send decision to dispute group
const message = this.chat.createCustomMessage({
to: this.disputeGroupId,
conversationType: TencentCloudChat.TYPES.CONV_GROUP,
payload: {
data: JSON.stringify({
type: 'DISPUTE_DECISION',
disputeId,
ruling, // 'buyer_wins', 'seller_wins', 'split'
refundPercentage,
reasoning,
timestamp: Date.now(),
arbitrator: this.userId
}),
description: `Decision: ${ruling} — ${reasoning}`,
extension: JSON.stringify({ final: true })
}
});
await this.chat.sendMessage(message);
// Trigger smart contract execution
await this.executeDecisionOnChain(disputeId, ruling, refundPercentage);
}
async executeDecisionOnChain(disputeId, ruling, refundPercentage) {
// Backend call to execute the arbitrator's decision via the escrow contract
await fetch('/api/disputes/execute', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
disputeId,
ruling,
refundPercentage,
arbitratorSignature: await this.signDecision(disputeId, ruling)
})
});
}
async signDecision(disputeId, ruling) {
// Arbitrator signs the decision for on-chain verification
const message = ethers.solidityPacked(
['string', 'string', 'uint256'],
[disputeId, ruling, Date.now()]
);
const signer = new ethers.BrowserProvider(window.ethereum).getSigner();
return await (await signer).signMessage(message);
}
}Step 3: Record Dispute Sessions as Evidence
Dispute video sessions should be recorded and stored on IPFS for permanent, tamper-proof evidence:
class DisputeRecording {
constructor(trtcInstance) {
this.trtc = trtcInstance;
this.mediaRecorder = null;
this.chunks = [];
}
// Start recording the dispute session (client-side backup)
async startRecording() {
const stream = this.trtc.getLocalVideoTrack();
const audioTrack = this.trtc.getLocalAudioTrack();
const combinedStream = new MediaStream([stream, audioTrack]);
this.mediaRecorder = new MediaRecorder(combinedStream, {
mimeType: 'video/webm;codecs=vp9,opus'
});
this.mediaRecorder.ondataavailable = (event) => {
if (event.data.size > 0) {
this.chunks.push(event.data);
}
};
this.mediaRecorder.start(1000); // Capture in 1-second chunks
}
async stopAndUploadToIPFS() {
return new Promise((resolve) => {
this.mediaRecorder.onstop = async () => {
const blob = new Blob(this.chunks, { type: 'video/webm' });
// Upload to IPFS via Pinata or nft.storage
const formData = new FormData();
formData.append('file', blob, `dispute_${Date.now()}.webm`);
const response = await fetch('https://api.pinata.cloud/pinning/pinFileToIPFS', {
method: 'POST',
headers: { 'Authorization': `Bearer ${PINATA_JWT}` },
body: formData
});
const { IpfsHash } = await response.json();
resolve(`ipfs://${IpfsHash}`);
};
this.mediaRecorder.stop();
});
}
}Step 4: Scaling Dispute Resolution with Built-in Customer Service
Video arbitration works for high-value disputes that need human judgment. But most marketplace conflicts—payment sent but item not received, wrong item shipped, refund timing disagreements—follow predictable patterns. Building custom resolution flows for every dispute type is unnecessary when TRTC provides a production-ready customer service infrastructure.
Automated Group Chat with Agent Routing
When a buyer opens a dispute (e.g., "I paid 2.5 ETH but never received the NFT"), the system doesn't need to immediately schedule a video call. Instead, it creates a group chat pulling in both transaction parties and a customer service agent:
class DisputeAgentRouter {
constructor(chatInstance) {
this.chat = chatInstance;
}
async createDisputeGroup(disputeId, buyerUserId, sellerUserId) {
// The agent userID represents a customer service seat pool.
// TRTC automatically routes to available agents behind this ID —
// multiple seats share one agent userID with built-in rotation.
const agentUserId = 'cs_marketplace_disputes';
await this.chat.createGroup({
type: TencentCloudChat.TYPES.GRP_WORK,
groupID: `dispute_${disputeId}`,
name: `Transaction Dispute #${disputeId}`,
memberList: [
{ userID: buyerUserId },
{ userID: sellerUserId },
{ userID: agentUserId } // Routed to next available agent seat
]
});
// Send dispute context as the first message
const contextMessage = this.chat.createCustomMessage({
to: `dispute_${disputeId}`,
conversationType: TencentCloudChat.TYPES.CONV_GROUP,
payload: {
data: JSON.stringify({
type: 'DISPUTE_CONTEXT',
disputeId,
buyer: buyerUserId,
seller: sellerUserId,
transactionHash: '0x...',
amount: '2.5 ETH',
category: 'item_not_received'
}),
description: 'Dispute opened: item not received after payment confirmation',
extension: JSON.stringify({ systemGenerated: true })
}
});
await this.chat.sendMessage(contextMessage);
}
}One agent user ID can have multiple human agents behind it. TRTC handles seat assignment, queue management, and rotation automatically—no custom load-balancing logic required. When all agents are occupied, incoming disputes enter a queue with position updates sent to the waiting user. This is a ready-made capability, not something you build from scratch.
Full Customer Service Platform for Marketplace Operations
Beyond peer-to-peer disputes, every marketplace needs user-to-platform support: complaints about marketplace fees, account restrictions, listing removals, or withdrawal issues. TRTC's customer service platform provides this out of the box:
- Queue routing: Disputes auto-categorized by type (escrow, shipping, counterfeit claims) and routed to specialized agent groups
- Seat rotation: Even distribution across available agents with priority overrides for VIP sellers or high-value transactions
- Proactive agent outreach: Agents can contact users first—critical when the platform detects a suspicious transaction pattern or needs to notify a seller about a compliance issue before it escalates
- FAQ robot: Common questions ("How does escrow release work?", "What's the dispute window?", "How do I verify my wallet?") handled automatically, deflecting 40-60% of inquiries before they reach a human agent
For Web3 marketplace operations, this means escrow disagreements, "item not as described" claims, and refund requests all flow through a structured resolution pipeline rather than ad-hoc Discord threads or email exchanges that lose context. The customer service layer sits between the lightweight FAQ bot (handles simple questions) and the full video arbitration system (handles complex, high-value disputes requiring live evidence review).
Push Notifications That Keep Dispute Parties in the Loop
Dispute resolution fails when users miss updates. A buyer files a complaint, an agent responds within minutes, but the buyer doesn't see the response until they happen to open the app three days later—and by then the dispute window has closed. TRTC's notification infrastructure solves this with delivery guarantees designed for marketplace-scale traffic:
Mass notification throughput: The system pushes 10 million messages within 30 seconds. For disputes this means every party—buyer, seller, agent, arbitrator—gets notified simultaneously when a decision is issued or an escalation occurs. It also powers marketplace-wide announcements: new listings, price drops, auction endings, and flash sale alerts that drive users back to the platform.
Offline message persistence: Android users without Google Play push certificates (common in Web3's global user base) still receive up to 20 messages per conversation stored for 7 days. When they come back online, they get the full dispute history—agent responses, evidence requests, deadline warnings—without gaps.
Smart delivery routing: The system detects whether a user is currently active in-app or offline. Online users receive updates instantly via their existing WebSocket connection (zero latency). Offline users get a device push notification within seconds. This means time-sensitive dispute updates—"respond within 24 hours or escrow releases to seller"—reach users through the fastest channel available, regardless of their app state.
class DisputeNotificationService {
constructor(chatInstance) {
this.chat = chatInstance;
}
// Send a deadline notification that adapts to user's online status
async notifyDisputeDeadline(userId, disputeId, hoursRemaining) {
const message = this.chat.createCustomMessage({
to: userId,
conversationType: TencentCloudChat.TYPES.CONV_C2C,
payload: {
data: JSON.stringify({
type: 'DISPUTE_DEADLINE',
disputeId,
hoursRemaining,
action: 'respond_required',
consequence: 'auto_resolution_if_no_response'
}),
description: `Action required: ${hoursRemaining}h remaining to respond to dispute #${disputeId}`,
extension: JSON.stringify({
// Push config for offline users
offlinePushInfo: {
title: 'Dispute Response Required',
description: `${hoursRemaining} hours left to respond. Tap to view.`,
androidSound: 'urgent_notification',
iosBadgeCount: 1
}
})
}
});
await this.chat.sendMessage(message);
}
}Combined, these three capabilities—agent-routed group chats, the out-of-the-box customer service platform, and guaranteed-delivery notifications—give your marketplace a dispute resolution system that scales from day-one complaints to thousands of concurrent cases without custom infrastructure investment.
Tutorial 4: Custom Message Types for Marketplace Transactions
Standard text messaging is insufficient for marketplace communication. Every web3 marketplace needs structured message types that carry transaction semantics and render as interactive UI components within the chat thread.
Complete Custom Message Schema
// All custom message types for a Web3 marketplace
const MARKETPLACE_MESSAGE_TYPES = {
// Negotiation messages
BID: 'BID',
COUNTEROFFER: 'COUNTEROFFER',
OFFER_ACCEPTED: 'OFFER_ACCEPTED',
OFFER_DECLINED: 'OFFER_DECLINED',
OFFER_EXPIRED: 'OFFER_EXPIRED',
// Transaction state messages
ESCROW_FUNDED: 'ESCROW_FUNDED',
ITEM_SHIPPED: 'ITEM_SHIPPED',
ITEM_RECEIVED: 'ITEM_RECEIVED',
PAYMENT_RELEASED: 'PAYMENT_RELEASED',
// Dispute messages
DISPUTE_OPENED: 'DISPUTE_OPENED',
DISPUTE_EVIDENCE: 'DISPUTE_EVIDENCE',
DISPUTE_DECISION: 'DISPUTE_DECISION',
VIDEO_CALL_REQUEST: 'VIDEO_CALL_REQUEST',
// Auction messages
AUCTION_BID: 'AUCTION_BID',
AUCTION_NEW_LOT: 'AUCTION_NEW_LOT',
AUCTION_LOT_SOLD: 'AUCTION_LOT_SOLD',
AUCTION_GOING_ONCE: 'AUCTION_GOING_ONCE',
// Listing messages
LISTING_SHARE: 'LISTING_SHARE',
PRICE_DROP_ALERT: 'PRICE_DROP_ALERT',
LISTING_SOLD: 'LISTING_SOLD'
};Custom Message Renderer (React Component)
import React from 'react';
function MarketplaceMessageRenderer({ message }) {
if (message.type !== 'TIMCustomElem') {
return <StandardMessageBubble message={message} />;
}
const payload = JSON.parse(message.payload.data);
switch (payload.type) {
case 'BID':
return <BidMessage payload={payload} message={message} />;
case 'COUNTEROFFER':
return <CounterOfferMessage payload={payload} message={message} />;
case 'OFFER_ACCEPTED':
return <DealConfirmedMessage payload={payload} />;
case 'ESCROW_FUNDED':
return <EscrowStatusMessage payload={payload} />;
case 'DISPUTE_OPENED':
return <DisputeAlertMessage payload={payload} />;
case 'AUCTION_BID':
return <AuctionBidMessage payload={payload} />;
case 'AUCTION_LOT_SOLD':
return <AuctionSoldMessage payload={payload} />;
default:
return <GenericCustomMessage payload={payload} />;
}
}
function BidMessage({ payload, message }) {
const isExpired = Date.now() > payload.expiresAt;
const timeLeft = Math.max(0, payload.expiresAt - Date.now());
return (
<div className="bid-message-card">
<div className="bid-header">
<span className="bid-icon">💰</span>
<span className="bid-label">Bid Placed</span>
{!isExpired && <CountdownTimer milliseconds={timeLeft} />}
</div>
<div className="bid-amount">
<span className="amount">{payload.amount}</span>
<span className="currency">{payload.currency}</span>
</div>
<div className="bid-meta">
<span>Listing: #{payload.listingId.slice(0, 8)}</span>
<span>{new Date(payload.timestamp).toLocaleTimeString()}</span>
</div>
{!isExpired && !message.isSelf && (
<div className="bid-actions">
<button className="accept-btn" onClick={() => acceptBid(payload)}>
Accept
</button>
<button className="counter-btn" onClick={() => openCounterModal(payload)}>
Counter
</button>
<button className="decline-btn" onClick={() => declineBid(payload)}>
Decline
</button>
</div>
)}
{isExpired && <div className="bid-expired">Expired</div>}
</div>
);
}
function AuctionBidMessage({ payload }) {
return (
<div className="auction-bid-inline">
<span className="bidder-name">{payload.bidderDisplay}</span>
<span className="bid-text"> bid </span>
<span className="bid-amount-inline">{payload.amount} {payload.currency}</span>
</div>
);
}
function EscrowStatusMessage({ payload }) {
const statusIcons = {
funds_locked: '🔒',
shipped: '📦',
completed: '✅',
disputed: '⚠️'
};
return (
<div className="escrow-status-card system-message">
<span className="status-icon">{statusIcons[payload.status]}</span>
<div className="status-content">
<div className="status-title">Escrow Update</div>
<div className="status-message">{payload.message}</div>
{payload.action && (
<button className="status-action" onClick={() => handleAction(payload.action)}>
{payload.action === 'fund_escrow' ? 'Fund Escrow' : 'View Details'}
</button>
)}
</div>
</div>
);
}Choosing a Web3 Marketplace Development Company
If you're evaluating a web3 marketplace development company (or building in-house), here's what the communication layer requirements should look like in your technical spec:
Must-Have Communication Features
| Feature | Why It Matters | Technical Requirement |
|---|---|---|
| 1-on-1 buyer-seller chat | Pre-purchase questions drive conversion | Chat SDK with custom message types |
| Transaction-state messages | Users need real-time order updates | Webhook bridge: on-chain events to chat |
| Live auction streaming | Highest-converting auction format | Live SDK + synchronized bid messages |
| Video dispute resolution | Fast, fair conflict resolution | RTC engine + recording + IPFS storage |
| Group channels | Community curation and governance | Group chat with role-based permissions |
| Push notifications | Re-engagement for inactive bidders | Cross-platform notification service |
| Message history | Legal compliance + user trust | Persistent storage with search |
Build vs. Buy: Communication Infrastructure
Building real-time communication from scratch (WebSocket servers, TURN/STUN infrastructure, media processing, global CDN) takes 6-12 months of specialized engineering. For a marketplace, this is undifferentiated heavy lifting.
The practical approach: use TRTC's pre-built SDKs for the communication layer and focus engineering effort on:
- Smart contract logic (your competitive moat)
- Marketplace-specific UX
- Token economics
- Community features
TRTC provides the Chat SDK, Live streaming SDK, and RTC Engine as modular components. You integrate what you need and build marketplace-specific logic (bid messages, dispute flows, auction synchronization) on top.
Integration Effort Comparison
| Approach | Timeline | Team Size | Risk |
|---|---|---|---|
| Build from scratch | 8-14 months | 4-6 engineers (comms specialty) | High — scaling, reliability, global infra |
| Open-source (Matrix/XMPP) | 4-8 months | 2-3 engineers | Medium — maintenance burden, no video |
| TRTC SDK integration | 2-4 weeks | 1-2 engineers | Low — managed infrastructure, global edge |
For most web3 marketplace projects—whether you're working with a web3 marketplace development company or an internal team—the SDK approach is the right trade-off: ship the marketplace faster and compete on product experience rather than infrastructure.
Accelerating Development with the TRTC MCP Server
The Model Context Protocol (MCP) enables AI coding assistants to interact directly with TRTC's documentation and SDK references during development. The TRTC MCP server provides real-time access to API references, code examples, and best practices without leaving your development environment.
What MCP Enables for Web3 Marketplace Development
When building the communication features described in this guide, the MCP server helps by:
- Auto-generating integration code for Chat SDK, Live SDK, and RTC Engine based on your specific use case
- Providing correct API signatures without manual documentation lookup
- Suggesting best practices for marketplace-specific patterns (bid messages, dispute flows)
- Troubleshooting SDK errors with context-aware assistance
Setup
# Add TRTC MCP server to your AI development environment
# Configuration depends on your IDE/assistant setup
{
"mcpServers": {
"trtc": {
"command": "npx",
"args": ["-y", "@anthropic-ai/trtc-mcp-server"],
"env": {
"TRTC_SDK_APP_ID": "your_sdk_app_id",
"TRTC_SECRET_KEY": "your_secret_key"
}
}
}
}With MCP configured, you can describe what you need in natural language—"generate a bid message handler for my auction system"—and get correct, SDK-version-specific code that integrates with your existing marketplace architecture.
This is particularly valuable for web3 marketplace development because the communication patterns (custom messages, live streams, video calls) are well-defined but have many configuration options. MCP eliminates the trial-and-error of finding the right API calls and parameter combinations.
Web3 Marketplace Communication: GVoice vs. TRTC
For teams evaluating Tencent's real-time communication offerings, here's how GVoice and TRTC compare for marketplace use cases:
| Capability | GVoice | TRTC |
|---|---|---|
| Primary use case | Gaming voice chat | Full-stack RTC (chat, video, live) |
| Chat SDK (text/custom messages) | No | Yes — full-featured IM |
| Live streaming | No | Yes — up to 100K+ viewers |
| Video calling | Limited | Yes — 1080p, screen sharing |
| Custom message types | No | Yes — bids, offers, settlements |
| Web3/marketplace patterns | Not designed for this | Purpose-built SDK patterns |
| Global edge network | Gaming-optimized | Media-optimized, 200+ PoPs |
For web3 marketplace development, TRTC is the correct choice. GVoice is optimized for in-game voice communication—low-latency voice in game lobbies and matches. It lacks the chat SDK, custom message types, live streaming, and video calling that marketplaces require.
Security Considerations for Web3 Marketplace Communication
Message Integrity
In a Web3 marketplace, communication messages carry financial significance. A bid message, acceptance, or dispute evidence is a potential legal artifact. Ensure:
- Message signing: Critical transaction messages (bids, acceptances) should include a wallet signature that proves the sender authorized the action
- Timestamp verification: Server-side timestamp validation prevents replay attacks on bid messages
- Immutable evidence: Dispute session recordings stored on IPFS with content hashes recorded on-chain
Access Control
// Example: Only allow verified wallet holders to message sellers
class SecureMarketplaceChat {
async sendMessage(to, content) {
// Verify sender has an active session (wallet connected + signed)
if (!this.isWalletVerified()) {
throw new Error('Connect and verify your wallet before messaging');
}
// Rate limiting: prevent spam
if (this.isRateLimited()) {
throw new Error('Message rate limit exceeded. Wait before sending.');
}
// For bid messages: verify on-chain balance covers the bid
if (content.type === 'BID') {
const hasBalance = await this.verifyBalance(content.amount, content.currency);
if (!hasBalance) {
throw new Error('Insufficient balance for this bid amount');
}
}
return await this.chat.sendMessage(this.formatMessage(to, content));
}
async verifyBalance(amount, currency) {
const provider = new ethers.BrowserProvider(window.ethereum);
const balance = await provider.getBalance(this.walletAddress);
return balance >= ethers.parseEther(amount.toString());
}
}Privacy in Dispute Resolution
Dispute video sessions contain sensitive information. Implementation requirements:
- Video recordings accessible only to dispute participants and the assigned arbitrator
- IPFS storage with encryption—only key holders can decrypt
- Automatic deletion after dispute resolution period (configurable, typically 90 days)
- No dispute content visible to other marketplace users
Deployment Architecture for Production
Scaling Considerations
A production web3 marketplace handles multiple concurrent communication patterns:
┌─────────────────────────────────────────────────────────────┐
│ Traffic Patterns (concurrent for a mid-size marketplace) │
├─────────────────────────────────────────────────────────────┤
│ Buyer-seller chats: ~5,000 active conversations │
│ Auction streams: ~20 concurrent (50-500 viewers each) │
│ Dispute video calls: ~10 concurrent sessions │
│ Custom msg processing: ~2,000 bids/offers per minute │
│ On-chain event bridge: ~100 events/minute (peak) │
└─────────────────────────────────────────────────────────────┘TRTC handles the communication infrastructure scaling (media routing, edge distribution, quality adaptation). Your backend handles:
- UserSig generation: Stateless, horizontally scalable
- Event bridge: Queue-based (on-chain events to chat notifications)
- Bid validation: Verify balance before broadcasting bid messages
- Dispute routing: Assign arbitrators and create video rooms
- Recording management: IPFS upload and access control
Push Notifications for Transaction-Critical Updates
In a marketplace where a missed notification means a missed auction or an expired escrow window, push delivery is non-negotiable. TRTC's notification infrastructure handles the edge cases that custom implementations typically get wrong:
- Mass broadcast at scale: 10 million messages delivered within 30 seconds—used for new listing alerts, price drop notifications, and auction ending warnings across your entire user base simultaneously
- Offline message storage: 7-day retention with up to 20 stored messages for Android users who lack app store push certificates (common in Web3 where users install APKs directly or use alternative app stores)
- Intelligent delivery routing: The system detects whether a user is currently online in the app or offline. Online users receive in-app notifications instantly; offline users get device-level push notifications. No duplicate delivery, no missed messages
class MarketplaceNotifications {
constructor(chatInstance) {
this.chat = chatInstance;
}
// Broadcast auction ending warning to all watchers
async notifyAuctionEnding(auctionId, watcherUserIds, minutesRemaining) {
// TRTC handles delivery optimization:
// - Online users: in-app notification
// - Offline users: push notification with offline storage
// - Batch delivery: millions of recipients in seconds
const message = this.chat.createCustomMessage({
to: `auction_watchers_${auctionId}`,
conversationType: TencentCloudChat.TYPES.CONV_GROUP,
payload: {
data: JSON.stringify({
type: 'AUCTION_ENDING_SOON',
auctionId,
minutesRemaining,
currentBid: '4.2 ETH',
action: 'place_final_bid'
}),
description: `Auction ending in ${minutesRemaining} minutes — current bid 4.2 ETH`,
extension: JSON.stringify({
pushConfig: {
title: 'Auction Ending Soon',
body: `${minutesRemaining}min left — place your bid now`,
offlineStorageDays: 7
}
})
}
});
await this.chat.sendMessage(message);
}
// Notify seller of new offer (critical for time-sensitive negotiations)
async notifyNewOffer(sellerUserId, listingId, offerAmount, currency) {
const message = this.chat.createCustomMessage({
to: sellerUserId,
conversationType: TencentCloudChat.TYPES.CONV_C2C,
payload: {
data: JSON.stringify({
type: 'NEW_OFFER_RECEIVED',
listingId,
amount: offerAmount,
currency,
expiresIn: '24h'
}),
description: `New offer: ${offerAmount} ${currency} on your listing`,
extension: JSON.stringify({
pushConfig: {
title: 'New Offer Received',
body: `Someone offered ${offerAmount} ${currency} — expires in 24h`
}
})
}
});
await this.chat.sendMessage(message);
}
}This notification layer ensures that escrow deadlines, dispute response windows, and auction closings reach users regardless of whether they're actively using the marketplace—a requirement that separates production-grade marketplaces from demo projects.
Backend: UserSig Generation Service
// Express.js endpoint for generating TRTC credentials
import express from 'express';
import TLSSigAPIv2 from 'tls-sig-api-v2';
const app = express();
const sdkAppId = parseInt(process.env.TRTC_SDK_APP_ID);
const secretKey = process.env.TRTC_SECRET_KEY;
app.post('/api/auth/trtc-credentials', authenticateWallet, async (req, res) => {
const { walletAddress } = req.verifiedWallet;
const userId = `mkt_${walletAddress.toLowerCase().slice(2, 14)}`;
const api = new TLSSigAPIv2.Api(sdkAppId, secretKey);
const userSig = api.genSig(userId, 86400); // 24-hour expiry
res.json({ userId, userSig, sdkAppId });
});
// Middleware: verify wallet signature from auth header
async function authenticateWallet(req, res, next) {
const { signature, message } = req.headers;
const recoveredAddress = ethers.verifyMessage(message, signature);
if (!recoveredAddress) {
return res.status(401).json({ error: 'Invalid wallet signature' });
}
req.verifiedWallet = { walletAddress: recoveredAddress };
next();
}Common Patterns Across Web3 Marketplace Types
NFT Marketplaces (OpenSea Model)
- Primary need: Buyer-seller chat for negotiating offers on unique items
- Key messages: Offer, counter, accept, transfer confirmation
- Video: Verification calls for high-value physical+NFT bundles
Decentralized Freelance Marketplaces (Braintrust Model)
- Primary need: Project communication between client and freelancer
- Key messages: Milestone completion, payment release, scope change
- Video: Dispute resolution when deliverables are contested
Physical Goods + Crypto (Shopify Web3 Model)
- Primary need: Pre-purchase questions, shipping updates
- Key messages: Escrow funded, shipped, received, payment released
- Live: Product showcases and flash sales
- Video: Item verification for luxury/collectible items
DAO-Curated Marketplaces
- Primary need: Community governance communication
- Key messages: Listing approval votes, seller verification
- Group chat: Curation committees, token holder discussions
- Live: Community town halls, seller presentations
Each pattern uses the same underlying TRTC SDK components—Chat, Live, RTC Engine—configured for different workflows. The custom message schema adapts to the marketplace's transaction model.
Implementation Roadmap
For teams building a web3 marketplace (whether internal or with a web3 marketplace development company), here's a phased implementation plan:
Phase 1: Foundation (Week 1-2)
- Wallet authentication to TRTC credential mapping
- Basic buyer-seller 1-on-1 chat
- Text + image messages for pre-purchase inquiries
- Conversation anchored to listing IDs
Phase 2: Transaction Messages (Week 2-3)
- Custom message types: bids, counteroffers, acceptances
- Smart contract event bridge to chat notifications
- Escrow status messages rendered inline
- Bid expiration and auto-decline logic
Phase 3: Live Auctions (Week 3-4)
- Auction stream hosting (anchor role)
- Viewer experience with bid overlay
- Real-time bid broadcast via group chat
- Lot announcement and sold confirmation messages
- Settlement flow connecting auction winner to smart contract
Phase 4: Dispute Resolution (Week 4-5)
- Dispute video room creation
- Arbitrator controls (mute, request evidence, issue decision)
- Session recording and IPFS upload
- Decision to smart contract execution pipeline
- Evidence access control and retention policies
Phase 5: Optimization (Week 5-6)
- Push notifications for offline bid updates
- Message search and history
- Analytics (response times, dispute resolution rates)
- Load testing for auction peak traffic
- Mobile SDK integration (iOS/Android)
Conclusion
Web3 marketplace development is not a smart contract problem. It's a trust-building problem that smart contracts partially solve. The communication layer—buyer-seller messaging, live auction streaming, video dispute resolution—is what converts a technically functional marketplace into one with real transaction volume.
The four tutorials in this guide cover the complete communication stack:
- Chat SDK with custom bid/offer messages for buyer-seller negotiation
- Live streaming with synchronized bidding for high-energy auctions
- Video calling with arbitrator controls for fast dispute resolution
- Custom message types that render as actionable UI components
Build this on top of your smart contracts, wallet integration, and token economics—and you have a marketplace where people actually transact, not just browse.
Start with the TRTC Chat SDK for buyer-seller messaging, then layer in live streaming and video as your marketplace grows. The Web3 solutions page has additional architecture references for decentralized communication patterns.

