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.
Consent drives inbox routing¶
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
devnetwork. 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.