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.
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.
badge() = "Orange" Sovereign + trusted anchor.badge() = "None" Pending, no badge.badge() = "Unverified" No trust anchor pinned, resolved against observed state only.badge() = "None" Dependent, never gets a badge.Quick reference
| badge() | Meaning | Display |
|---|---|---|
| Orange | Sovereign + verified against trusted anchor | Orange checkmark |
| Unverified | No trust anchor pinned - resolved against peer-observed state only | unverified |
| None | Verified against semi-trusted anchor but not sovereign, or handle is pending/dependent | No 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.
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
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.
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
fabric.badge() for all display logic. Don't check sovereignty or trust pools directly.Unverified on first launch.