Design Guidelines

How apps should communicate trust, verification, and sovereignty when integrating Spaces. These guidelines help ensure users understand what they can trust and why.

Badges

Use fabric.badge(resolved) to determine how to display a handle. The badge communicates the trust level to users at a glance - like a verified checkmark on social media.

Orange badge() = "Orange"

Handle is sovereign (finalized on Bitcoin) and verified against a trusted anchor. The orange checkmark is the highest trust level.

22:22
A
alice@bitcoin
last seen 22m ago
Hey! do you know how to make pancakes?
Yes but only during a full moon
...what happens if you try on a normal day?
Seen 22 hours ago
Message
A messaging app showing the orange sovereign badge next to the handle in the chat header

Unverified badge() = "Unverified"

No trust anchor has been pinned (neither trusted nor semi-trusted). The handle was resolved against peer-observed state only. Apps should indicate that verification is not active.

None badge() = "None"

No badge. The handle was verified against a semi-trusted anchor but is not sovereign, or it is pending or dependent. Display it like a regular username - no checkmark, no warning. The absence of the badge is the signal.

Example: contact list with trusted anchor pinned
A
alice@bitcoin
Hey! Can you send me the...
B
bob@nostr
Sent you a zap ⚡
D
dave@bitcoin unverified
Check out this relay
C
carol@exchange
Your withdrawal is ready
alice badge() = "Orange" Sovereign + trusted anchor.
bob badge() = "None" Pending, no badge.
dave badge() = "Unverified" No trust anchor pinned, resolved against observed state only.
carol badge() = "None" Dependent, never gets a badge.

Quick reference

badge() Meaning Display
OrangeSovereign + verified against trusted anchor Orange checkmark
UnverifiedNo trust anchor pinned - resolved against peer-observed state onlyunverified
NoneVerified against semi-trusted anchor but not sovereign, or handle is pending/dependentNo badge - display as regular username

Trust Pools

Trust pools will become unnecessary as Fabric transitions to a stateless ZK light client that verifies the Bitcoin header chain and computes a trust ID permissionlessly on any device.

Introduction

A trust ID is the hash of a set of Merkle roots computed at multiple block intervals. It allows any client to verify protocol state from an inclusion proof anchored in any of those roots.

Spaces is purely Bitcoin-native PKI. Trust is derived from Bitcoin's proof of work, not from HTTPS certificates, DNS, or any external authority. A trust ID is computed locally by reading Bitcoin blocks. No server can grant or revoke trust.

Not every user will run their own verifier (such as Veritas). Fabric provides three trust pools so apps can offer a reasonable experience out of the box while preserving the path to full verification.

Observed - automatically populated from peers. This is always present and is used for best-effort resolution. On its own, badge() returns Unverified for all handles.

Semi-trusted - apps may pre-fill this from a source they control (e.g. an anchor served over HTTPS from your own backend). This is a pragmatic concession: HTTPS is not trustless, but it prevents every handle from showing as Unverified on first launch. Semi-trusted anchors will never produce Orange badges.

Trusted - reserved for anchors the user has explicitly verified, either by scanning a QR code or from an embedded verifier that permissionlessly computes the trust ID from Bitcoin blocks. This is the only pool that can produce Orange badges. Apps must not write to this pool on the user's behalf unless they embed a local verifier.

The Veritas app is the easiest way for users to obtain a permissionless trust ID without running a Bitcoin full node. It syncs from a checkpoint, verifies the Bitcoin header chain, and computes the trust ID locally.

22:22
Settings
Safety ID
No safety Trust ID set. Scan from Veritas or a local client to enable full verification.
Scan QR
Notifications
About
Settings - no trust anchor pinned
22:22
Settings
Verify Anchor
Scan a Trust ID from a Veritas client running locally on your machine (do NOT scan from remote sources)
Valid for up to 14 days after scan
QR verification screen
Do

Use fabric.badge() to determine what to show

Pre-fill the semi-trusted pool from your own endpoint for a good default

Make QR scan accessible so users can upgrade to full trust

Show anchor hash prominently so users can compare

Don't

Write to the trusted pool programmatically unless you embed a local verifier

Show orange checkmarks from a semi-trusted anchor

Hide or obscure the trust state

Auto-accept new anchors silently into the trusted pool

Handling staleness

Once a trusted anchor is pinned, badge() can return Orange for sovereign handles verified against it for up to 14 days. Handles committed after the pinned anchor (in a newer root) won't return Orange. If a semi-trusted anchor covers the newer root they may still return None rather than Unverified.

22:22
Messages
A
alice@bitcoin
Hey! Can you send me the...
B
bob@nostr
Sent you a zap ⚡
D
dave@bitcoin unverified
Check out this relay
C
carol@exchange
Your withdrawal is ready
Before QR scan
QR scan
22:22
Messages
Trusted anchor pinned · Expires in 14 days
A
alice@bitcoin
Hey! Can you send me the...
B
bob@nostr
Sent you a zap ⚡
D
dave@bitcoin
Check out this relay
C
carol@exchange
Your withdrawal is ready
After pinning trusted anchor

Notice that the orange checkmark only appears after the QR scan - and only for handles where .sovereignty is "final." Before the scan, no one gets a checkmark because the app can't prove anything from an unverified peer anchor. Dave showed "unverified" because his handle was committed in a newer root than the peer anchor; after re-syncing from a trusted source, his sovereignty is confirmed and the checkmark appears. Bob has no badge either way - he's pending, not sovereign.

Summary

1
Use fabric.badge() for all display logic. Don't check sovereignty or trust pools directly.
2
Only write to the trusted pool from a verified source. That means an explicit user action (QR scan) or an embedded verifier that computes the trust ID from Bitcoin blocks.
3
Pre-fill semi-trusted for a good default. Fetch an anchor from your own endpoint so users don't see Unverified on first launch.
4
Offer QR verification for full trust. Make it easy to scan from Veritas or a spaces client for the orange checkmark.