Docs
/
Protocol

Architecture

Two contracts, one browser client, no server. Why the split exists, what each component is allowed to know, and why messages live in logs rather than storage.

The system is deliberately small. There are two contracts, a static client, and nothing else. There is no backend, no database, no indexer that must stay online for the product to function, and no operator key that can move a user's name or read a user's mail.

Component responsibilities

ComponentOwnsCan never
MailRegistry Names, wallet bindings, published X25519 public keys, key versions, name epochs, reservations Read mail, or reassign a name without the owner acting
MailBox Message emission, stamp escrow, allow and block lists, credit balances Decrypt an envelope, or hold a message hostage to a payment
Browser client Key derivation, sealing, opening, log scanning, all plaintext Persist a private key anywhere
Chain Ordering, timestamps, permanence, public verifiability Learn message contents

Why identity and transport are separate contracts

Splitting them keeps each one auditable on its own terms and lets transport be replaced without touching identity. MailBox holds an immutable reference to the registry and calls exactly one method on it, isRegistered(address). That narrow interface is the entire coupling between the two.

interface IRegistry {
  function isRegistered(address wallet) external view returns (bool);
}

The practical consequence: a future MailBox with stealth addressing or a different escrow model can be deployed alongside the current one and share the same identities. Names and keys do not have to migrate.

The storage decision

This is the choice the whole cost model rests on. Contract storage costs 20,000 gas for each 32-byte slot written. Event log data costs roughly 8 gas per byte. For a payload measured in kilobytes the difference is three orders of magnitude.

Cost to write 1 KB contract storage ~640,000 gas · 32 slots at 20,000 event log data ~8,200 gas · 1024 bytes at 8 The bar above is drawn to scale. Logs are roughly 78x cheaper for the same payload. Logs are not readable by contracts, which is fine: no contract needs to read mail.
Figure 3. The trade is that logs cannot be read back by on-chain code. Since nothing in the protocol needs to inspect a message body, that costs nothing and saves almost everything.

What is stored on chain

Only the small, structural facts live in storage. Everything large lives in logs.

In contract storageWhy it must be
ownerOfName, accountsResolution must be callable by other contracts and by view calls
keyVersionOf, keyHistoryOld mail must stay decryptable after a rotation
nameEpochClients need to detect that a name changed hands
stamps, creditsEscrow is money; it needs authoritative state
stampPrice, allowedUntil, blockedSenderEnforced at send time, so the contract must read them
Message bodies are not in this list, and never will be.

Two chains, one identity

The design targets BSC and opBNB together. The mailbox key derivation deliberately omits chainId from its EIP-712 domain and uses a fixed salt for domain separation instead, so one signature yields the same mailbox identity on both networks.

Registry mirroring is an open decision

A name registered on BSC does not automatically exist on opBNB. Whether the registry is mirrored, bridged, or made canonical on one chain is one of the launch-gating decisions recorded on the roadmap.

Client architecture

The app is deliberately dependency-light and ships no build output. It is served under a strict Content Security Policy with connect-src 'none', which is possible because all chain communication goes through the injected wallet provider by message passing rather than through page-originated network requests.

crypto.js

Cryptography

HKDF derivation, sealing, opening, peer-key screening, payload validation. Holds the only plaintext in the system.

chain.js

Chain access

Provider setup, network switching, contract calls, and log filtering by topic.

app.js

Interface

Views, composition, list rendering. Never touches key material directly.

config.js

Deployment

Per-network chain IDs, contract addresses, explorers. The only file that changes between deployments.

Failure modes by design

If this failsWhat still works
The website goes offlineEverything. Any client that speaks the contract ABI can read and send.
The project is abandonedNames remain owned, mail remains readable, contracts keep running.
An RPC provider blocks youPoint the wallet at any other node.
A payout transfer revertsThe amount is credited for the owner to pull later; the message was already emitted.
You lose your walletYou lose the mailbox. This is stated plainly rather than papered over.

bMail is an independent, community-built project. It is not affiliated with, endorsed by, or operated by Binance or BNB Chain.