Skip to content

How Umul Uses XMTP

Where the previous three documents explained the protocol, this one records which parts Umul actually uses.

Item Value
SDK @xmtp/browser-sdk 7
Network environment dev (the SDK 7 default)
Local DB Persistent browser storage (OPFS), encrypted with a key derived from a wallet signature
Adapters XmtpMessagingAdapter, XmtpSignerAdapter

Because it is the dev environment, it does not interoperate with XMTP apps on production. The environment split is exactly as listed in the conditions table in the Overview.

The encryption key for the local MLS DB is derived from a wallet signature and re-derived every session, so it is never stored anywhere.

One wallet signature produces two things at sign-in

The wallet signature is the root of both the backend session and the XMTP identity.

connect wallet
  → SIWE-style signature → backend session (umul_session cookie)
  → XmtpSignerAdapter signature → XMTP Inbox / installation

XmtpSignerAdapter handles the identity registration described in the Identity document. Signing in from empty browser storage creates a new installation every time, which consumes the limit of 10.

Evidence control messages: a prefix instead of a custom codec

Umul does not define a custom content type (codec). It uses a versioned text envelope.

gasok:evidence:v1:<base64url-json>

The ordinary chat UI hides messages carrying this prefix and renders them as evidence cards instead. A single constant, EVIDENCE_CONTROL_PREFIX, is the whole detection rule.

The price of this choice is the fourth condition in the Overview. Open this conversation in another XMTP app and you see the raw gasok:evidence:v1:... text rather than an evidence card. That is a limit accepted for P0.

The payload contains no plaintext. It carries only a list of XMTP message IDs (messageRefs), and the recipient uses those IDs to find their own local messages and recompute the content hash. Each party recomputing rather than trusting a server-supplied value is the heart of evidence.

Umul also leans on the fact that control messages are end-to-end encrypted by MLS. Sending the evidence archive's content key (archiveKey) down this path delivers it to participants only, without passing through the server.

Umul takes XMTP's three consent states as they are.

XMTP ConsentState Umul
Allowed allowed
Denied denied → spam inbox
Unknown unknown

Umul's inbox routing is not decided by consent alone, though. A manual spam marking takes precedence over Dojang verification status, and otherwise a conversation reaches the main inbox only when every participant other than yourself is verified.

Condition Routing
User marked it spam (consent denied) Spam inbox
Every participant other than yourself is verified Main inbox
Anything else (including unverified or unknown) Request inbox

An unknown that could not be confirmed because of an RPC error is routed to the request inbox alongside unverified. It is safer not to promote an unconfirmed peer into the main inbox as though they were verified.

Consent is stored encrypted on the network, so the same routing follows the user across every XMTP app they use. The Dojang-based routing exists only in Umul.

The path for reading past messages

XmtpMessagingAdapter.listMessages
  conversation.sync()      fetch queued network messages, decrypt, store locally
  conversation.messages()  read from the local MLS DB

sync() has to come first. The stream (streamAllMessages) only yields messages from the point of subscription onward, so this path is mandatory for putting past messages on screen.

Details and the recovery limits for a new installation are in Message History.

Limits

  • It is the dev network. No interop with production apps, and XMTP does not guarantee data retention on dev.
  • The 10-installation limit. Every clear-storage-and-sign-in-again burns a slot. Once the limit is reached, sign-in hangs.
  • A new installation cannot read the past. This is a consequence of end-to-end encryption, not a bug. Only conversations used for evidence get around it, via the ciphertext archive.
  • Evidence control messages appear as raw text in other apps. That is the price of choosing a prefix over a custom codec.
  • The cost of generational change. v2 went read-only after four years and v2 conversations were not migrated. Building an app on XMTP means accepting the operational cost of following such changes.