Build Testnet · v1.10 · all tests passing
Estimated read time: 9 minutes

Build a Messaging System

What we're building

A minimal on-chain messaging contract: any address can send a short message to any other address, the recipient can read their inbox, and both can prove the message existed at a given block. Not a chat app — a primitive that real chat apps could be built on top of. We'll touch the same patterns you'd use for any append-only public ledger: a per-recipient mapping, indexed events, and gas-conscious storage.

By the end you'll have a deployed contract on testnet and an SDK snippet that reads from it.

The contract

Open the Playground and paste this:

import { Address } from '@valdium/std'; export class Messages { // inbox[recipient] = list of message indices #inbox = new Map(); // messages[i] = { from, to, text, blockNumber } #messages = []; send(to, text) { if (typeof to !== 'string' || !Address.isValid(to)) { throw new Error('bad recipient'); } if (typeof text !== 'string' || text.length === 0 || text.length > 280) { throw new Error('text must be 1..280 chars'); } const idx = this.#messages.length; this.#messages.push({ from: ctx.sender, to, text, blockNumber: ctx.blockNumber, }); const inbox = this.#inbox.get(to) ?? []; inbox.push(idx); this.#inbox.set(to, inbox); emit('Sent', { from: ctx.sender, to, index: idx }); } inboxOf(addr) { const idxs = this.#inbox.get(addr) ?? []; return idxs.map(i => this.#messages[i]); } count() { return this.#messages.length; } }

Three things to point at in this code:

Compile and deploy

Click Compile. If you see OK · 0 errors, click Deploy. Sign in the wallet. Within 2 seconds you'll have an address like 0xA12cE…F92e. Copy it.

Send your first message

In the Interact tab, click send. Fill in:

Sign. The right panel will show the receipt with the Sent event decoded. Click inboxOf with the recipient's address and you'll see the message you just sent.

Reading from a frontend

Now let's pull this from a webpage. Install the SDK:

npm i @valdium/sdk

And drop in this code:

import { ValdiumClient, Contract } from '@valdium/sdk'; import abi from './messages.abi.json'; // generated by the compiler const client = new ValdiumClient('https://testnet.valdium.xyz/rpc'); const messages = new Contract(MY_DEPLOYED_ADDRESS, abi, client); // read const inbox = await messages.inboxOf(myAddress); inbox.forEach(m => console.log(\`from \${m.from}: \${m.text}\`)); // subscribe to new messages messages.on('Sent', (event) => { if (event.to === myAddress) { console.log('new message from', event.from); } });

Reads are free and return immediately. Subscriptions hold a WebSocket open and fire as new messages land. Combined, this is the entire stack for a minimal mailbox UI.

Gas considerations

The send function does three storage writes: append to #messages, lookup-and-update #inbox.get(to), and emit an event. On testnet that's around 65,000 gas — roughly 0.0001 VLD at current base fee. Worth knowing because a chat app that sends a tx per keystroke would burn a real wallet quickly; batching sends is the obvious optimization.

A batched variant that takes an array of { to, text } in one call would amortize the per-tx overhead and drop the per-message cost by about 40%. Left as an exercise.

What's missing

This contract is a primitive, not a product. Real messaging adds: