Wallet Permissions and Counterparty Trust
Stephen Thomson ([email protected])
Brayden Langley ([email protected])
Jackie Lu ([email protected])
Abstract
This document fully specifies how BRC-100 wallets handle application permission requests.
Its goals are to:
Define, without ambiguity, how permissions are declared, requested, and evaluated
Specify full permission lifecycle behavior: grant, deny, renew, revoke, and persistence
Distinguish group permissions from counterparty trust (PACT) and define how both are enforced together
Provide an authoritative, implementation-agnostic reference for compliant wallet behavior
This document is normative: it defines complete expected behavior for compliant wallets and applications, independent of UI or implementation language.
Motivation
Wallets act as security boundaries between users and applications. Applications must not be able to:
Spend funds
Access private data
Interact with protocols
Access certificates
...without explicit user authorization.
The permissions system ensures:
Least-privilege access
Clear user consent
Deterministic enforcement
Predictable developer behavior
Normative Language
The key words MUST, MUST NOT, SHOULD, and MAY in this document are to be interpreted as described in RFC 2119 and RFC 8174 when, and only when, they appear in all capitals.
Specification
3.0 Terminology
The following terms are used consistently throughout this document:
Wallet: Software responsible for enforcing permissions and managing user assets and data.
Application (Originator): Software acting as an originator that requests permissions from the wallet. Identified by its domain (e.g.
example.com).User: The human owner of the wallet who grants or denies permissions.
Counterparty: An external entity (identified by a compressed public key) interacting with the user through an application via Level 2 protocols.
Permission Grant: A persisted authorization allowing an originator to perform a specific action within defined scope.
Permission Token: An on-chain PushDrop output stored in an admin basket that represents a persisted permission grant. Tokens are encrypted so only the wallet owner can read them. See Section 7.
Security Level: An integer (0, 1, or 2) that classifies protocol access. Level 0 is open (no permission required). Level 1 protocols are application-scoped. Level 2 protocols are counterparty-specific (a distinct external entity is involved). The BRC-43 specification defines how these are applied and used within cryptographic operations.
Manifest: A JSON file served at
https://{domain}/manifest.jsonproviding application metadata. See Section 3.2.Trusted Certifier: An entity the user has chosen to trust, identified by a compressed public key and assigned a trust score (1-10). Used for registry resolution and identity verification.
For the purposes of this document, group permissions refer to permissions granted by a user to an application originator via the wallet's standard permission system. These are distinct from PACT (counterparty trust), which governs trust relationships with external entities (see Section 5).
3.1 Originator
An originator represents the identity of an application requesting permissions, derived from the application's domain.
Permissions are always scoped to an originator. Granting permission to one originator does not imply permission for another.
The wallet resolves originator identity from the request context (typically HTTP headers such as Origin or Originator) when evaluating permission scope.
Security Note: The user's wallet, web browser and computer system are expected to coordinate and confirm the authenticity of originators, thereby preventing request forgery. The steps taken to confirm originator authenticity vary depending on wallet deployment context, but this specification assumes wallets will prevent forged requests via IPC, local kernel/substrate-specific authentication, or other means.
3.2 Manifest
Each application SHOULD serve a manifest file at https://{domain}/manifest.json.
The manifest follows the W3C Web App Manifest standard for application metadata (name, icons, display preferences), and extends it with a metanet namespace for wallet permission declarations. This keeps the manifest interoperable with standard web tooling while adding Metanet-specific capabilities.
The manifest serves three purposes:
Application metadata - Standard W3C fields (
name,short_name,description,icons) for display in permission prompts and the wallet dashboard.Group permission declarations - Under
metanet.groupPermissions, applications declare the permissions they need. When grouped permission seeking is enabled, the wallet fetches these declarations and presents them to the user as a single grouped prompt on first interaction, rather than prompting one-by-one as each capability is used.Counterparty permission declarations - Under
metanet.counterpartyPermissions, applications declare the Level 2 protocols they require for peer interaction in that app. When a new (untrusted) counterparty is encountered, the wallet uses this declaration to request trust for those protocols before allowing that peer interaction to proceed.
Manifest Structure
All fields under metanet are optional except schemaVersion. An application that declares no metanet.groupPermissions will simply be prompted individually at runtime for each capability it uses. An application that declares no metanet.counterpartyPermissions will never trigger a PACT prompt - Level 2 protocol requests will fall through to individual or grouped permission prompts instead.
Schema Version
The metanet.schemaVersion field is a required integer that identifies the version of the Metanet permission schema. Wallets MUST check this field and handle unknown versions gracefully (e.g. by ignoring the metanet block or warning the user). The current version is 1.
Group Permissions Declaration
Grouped permissions let an application declare multiple permissions required up front so the wallet can request them in one consolidated prompt. Without grouped declarations, permissions are requested individually at runtime when each protected operation is first attempted.
The metanet.groupPermissions object follows the GroupedPermissions interface (BRC-73):
description
string?
Human-readable description of the permission group
spendingAuthorization
{ amount, description }
Monthly spending limit in satoshis
protocolPermissions
array
Protocol access requests (Level 1 or 2). Level 2 entries MUST include a specific counterparty; Level 1 entries MAY omit counterparty.
basketAccess
array
Basket access requests, each with a basket name
certificateAccess
array
Certificate access requests with type, verifierPublicKey, fields
When the wallet encounters a permission-requiring operation, it MUST:
Check whether a permission token already exists for this operation. If so, allow the operation. Otherwise, proceed with step 2.
Fetch the originator's manifest.
Check whether the current operation is covered by the manifest's
groupPermissions. If it is not, trigger a one-off prompt for the operation. If it is, proceed with step 4.Filter out permissions already granted (by looking up existing on-chain tokens). Remove them from the list before showing the user a prompt.
Present all remaining permissions as a single grouped prompt for the user's approval.
After the user responds, check whether the specific triggering operation is now satisfied. If yes, allow it to proceed with the operation. If no, trigger an individual one-off prompt.
Counterparty Permissions Declaration
Counterparty permissions let an app declare all the Level 2 protocols it needs when interacting with an untrusted counterparty. The wallet can then request trust for that person in one bundled prompt, instead of showing repeated one-off Level 2 prompts as each operation is attempted.
The metanet.counterpartyPermissions object declares PACT requirements - the Level 2 protocol set the application requires to interact with a peer in that app:
description
string?
Human-readable description
protocols
array
Array of { protocolName, description } - required peer-interaction protocols, interpreted as Level 2 only
The wallet MUST enforce that all declared counterpartyPermissions.protocols entries include a non-empty protocolName. These entries are interpreted as Level 2 protocol declarations. These declarations define required Level 2 protocols, while the concrete counterparty is supplied at request time. Effective permission scope remains per-originator + per-counterparty.
Including counterpartyPermissions tells the wallet: "when this app encounters a new (untrusted) counterparty, request trust for this declared protocol set before allowing peer interaction to proceed." Without it, each Level 2 protocol permission is requested individually as it's used.
Manifest Examples
Example 1: Simple app with spending only
An application that only needs to spend satoshis (e.g. a tipping service):
Example 2: Data storage app (baskets, no counterparties)
An application that stores encrypted notes in a basket using a Level 1 protocol:
Example 3: Peer-to-peer messaging (with counterparty permissions)
An application where users exchange messages with specific counterparties. This declares counterpartyPermissions so the wallet will prompt the user to establish trust (PACT) when a new counterparty is encountered:
Example 4: Identity verification app (certificates)
An application that verifies user identity by requesting specific certificate fields:
Example 5: Full-featured application (all permission types + PACT)
Example 6: Minimal manifest (no Metanet permissions)
An application that does not declare any permissions upfront. The wallet will prompt individually as each capability is used:
3.3 Permission Categories
The system defines four primary permission types:
1
Protocol Permissions
DPACP (Domain Protocol Access Control Protocol)
protocolID (security level + name)
Yes (Level 2)
2
Spending Permissions
DSAP (Domain Spending Authorization Protocol)
Satoshi amount
No
3
Basket Permissions
DBAP (Domain Basket Access Protocol)
Basket name
No
4
Certificate Permissions
DCAP (Domain Certificate Access Protocol)
Certificate type + verifier + fields
No
Each permission type has distinct scope, grant options, and prompting behavior. All four types support a renewal flag indicating the permission is being re-requested after a previous grant has expired (when finite expiry is used) or been revoked.
3.4 Deterministic Permission Evaluation
To remove ambiguity across implementations:
Wallets MUST make allow/deny decisions from canonical permission state: valid, unspent, unexpired on-chain permission tokens plus explicit one-time ephemeral grants for the current request.
In-memory caches are performance optimizations only and MUST NOT be treated as authoritative permission state.
If cache state and on-chain token state disagree, the wallet MUST use on-chain token state.
Revocation, renewal, or token spend events that change permission state MUST invalidate affected cache entries immediately.
Internal function names, callback names, and class names in this document are illustrative unless explicitly defined as protocol surface.
4. Permission Types
4.1 Protocol Permissions
Protocol permissions control an application's ability to interact with named protocols for cryptographic and data operations.
Scope
A protocol permission is scoped by two fields:
protocolID: A tuple of[securityLevel, protocolName]securityLevel- integer,1or2protocolName- string identifier for the protocol
counterparty:'self','anyone', or a compressed public key identifying the other party. Empty string is invalid.For Level 2,
counterpartyis REQUIRED and permission scope is per-originator + per-counterparty.For Level 1, any
counterpartyis allowed, and it SHOULD be omitted in manifest declarations.
Security Levels
Level 0: Open usage - no permission check is required.
Level 1: Any counterparty is allowed. No counterparty prompt is required. Once granted for an originator, the permission applies across all counterparties for that originator. The wallet SHOULD present the scope as application-only (e.g. "only with this app").
Level 2: Only this specific external counterparty is permitted. The wallet MUST identify the counterparty to the user by displaying the counterparty's compressed public key, and a PACT relationship may be created (see Section 5). The wallet SHOULD present the scope as application-and-counterparty (e.g. "only with this app and counterparty").
Grant Options
Protocol permission grants are binary (grant or deny). There are no amount limits or ephemeral flags. Wallets SHOULD issue protocol grants as non-expiring (expiry = 0). If an implementation supports finite expiry values, it SHOULD require explicit user or administrator intent, because routine expiry creates repeated renewal prompts and degraded UX.
4.2 Spending Permissions
Spending permissions control an application's ability to spend funds (satoshis) on behalf of the user. These are high risk and require explicit, informed user consent.
Scope
A spending permission is scoped by:
satoshis: The amount being requested for the current transaction.lineItems(optional): An itemized breakdown, where each item has{ satoshis, description }.
Grant Options
Spending permissions support three grant outcomes:
One-time grant: Approves only the current spend request and does not create a standing authorization.
Standing authorization: Approves spending up to an authorized monthly limit for the originator.
Denial: Rejects the request.
Prompt Presentation
The spending prompt SHOULD display an itemized breakdown when lineItems are provided. Each line item shows a description and satoshi amount. The wallet SHOULD include the network fee as a separate line item and display a total. This allows the user to understand exactly what they are paying for before approving.
Wallets MUST treat application-provided description text as untrusted. Wallets MUST compute and display authoritative satoshi amounts from structured numeric fields (such as spending.satoshis, lineItems[].satoshis, and spendingAuthorization.amount) rather than relying on free-form description text.
Monthly Tracking
Spending authorization tokens do not have a time-based expiry (expiry is always 0). Instead, spending is tracked on a calendar month basis. The wallet calculates how much the originator has spent in the current month and compares it against the authorized limit.
0). Instead, spending is tracked on a calendar month basis. The wallet calculates how much the originator has spent in the current month and compares it against the authorized limit.4.3 Basket Permissions
Basket permissions control an application's ability to interact with named data baskets - structured, wallet-managed collections of spendable outputs.
Scope
A basket permission is scoped by:
basket: The basket identifier string.
Basket permissions are granted per basket name. Granting access to one basket does not imply access to any other.
Basket operations commonly include insertion, listing, and removal. Wallets MAY apply distinct prompt policy per operation type.
Grant Options
Basket permission grants are binary (grant or deny). There are no amount limits or ephemeral flags.
4.4 Certificate Permissions
Certificate permissions control an application's ability to interact with identity certificates - structured attestations issued by certifiers.
Scope
A certificate permission is scoped by:
type(certType): The certificate type identifier string.verifierPublicKey(verifier): The compressed public key of the entity that will verify/receive the certificate data.fields: A record of specific certificate fields being requested. This enables selective disclosure - the application requests only the fields it needs, not the entire certificate.
Operation Scope
Certificate operations use two permission mechanisms:
Disclosure operations use certificate-access checks (DCAP) with
certType, verifier, and field-level scope.Acquisition, listing, and relinquishment use protocol-level checks (DPACP), where certificate type is represented in protocol scope.
Field Matching
When looking up an existing DCAP token for a disclosure request, field matching uses subset semantics: the requested fields MUST be a subset of the token's granted fields. A token granting ["name", "email"] satisfies a request for ["name"], but a token granting ["name"] does NOT satisfy a request for ["name", "email"].
Both certType and verifierPublicKey MUST match exactly (strict equality). There is no partial or fuzzy matching.
Note: The grouped manifest inclusion check (Section 6.3) uses exact set equality for fields - both the manifest entry and the request must have the same field set. This is stricter than the token lookup's subset semantics.
Grant Options
Certificate permission grants are binary (grant or deny), but the wallet presents field-level granularity - the user sees which specific fields are being requested.
5. Counterparty Trust (PACT)
5.1 Definition
PACT (Protocol Agnostic Counterparty Trust) is a trust mechanism that governs whether the wallet user trusts a specific external counterparty for Level 2 protocol interactions within a specific application.
When an application involves peer-to-peer interactions - encrypted messaging, collaborative editing, trading, or any protocol where the user's cryptographic keys are used in relation to another person - the wallet needs to answer a question that group permissions alone cannot: "Do I trust this particular person, through this particular app?"
PACT provides that answer. It is a separate trust layer from group permissions, and it is established per originator + counterparty pair. A PACT granted for a counterparty through one application does not carry over to another application.
Applications declare their PACT requirements in the manifest's metanet.counterpartyPermissions section. This tells the wallet which Level 2 protocols are part of the peer interaction, so they can all be presented in a single trust prompt when a new counterparty is encountered - rather than prompting one protocol at a time.
5.2 Purpose
PACT addresses a fundamental gap in the permission model: group permissions answer "do I trust this app?", but they do not answer "do I trust this person through this app?" For applications with peer interactions, both questions must be answered.
PACT exists to:
Gate counterparty-specific interactions - ensure a counterparty is explicitly trusted before Level 2 protocol permissions are granted for them
Separate app trust from peer trust - a user may trust the app but not every counterparty the app introduces
Bundle peer protocol grants - present all Level 2 protocols declared in
metanet.counterpartyPermissionsfor a given counterparty in a single prompt, rather than prompting one-by-one as each protocol is usedScope trust narrowly - PACT is scoped to the originator + counterparty pair, preventing one app's trust decisions from affecting another app
5.3 Scope: Level 2 Only
PACT applies exclusively to Level 2 protocols. Level 1 protocols do not involve an external counterparty and therefore do not require PACT. Wallets SHOULD present this to the user as: "This person (counterparty) wants to interact with you through this app (originator) using these protocols."
5.4 Separation from Group Permissions
Key distinctions:
Governs
Application -> Wallet
Counterparty -> User (via application)
Scoped to
Originator domain
Originator domain + Counterparty public key
Applies to
All four permission types
Level 2 protocols only
Prompted when
App attempts a wallet operation
App involves an untrusted counterparty
Declared in manifest
metanet.groupPermissions
metanet.counterpartyPermissions
An operation MAY require both:
A granted group permission (e.g. protocol access for the originator)
An established PACT (trust for the specific counterparty)
5.5 Manifest-Driven Routing
When a permission request arrives, the wallet checks the originator's manifest and routes the request based on its type. The reference flow in Section 6.2 uses a deterministic strategy order (PACT -> peer-grouped -> grouped -> individual):
If the request is a Level 2 protocol with a specific counterparty, and the manifest declares
counterpartyPermissionscovering that protocol -> the wallet checks for an established PACT and, if missing, triggers a PACT prompt.If the request is covered by the manifest's
groupPermissions(any of the four permission types) -> the wallet filters out already-granted permissions and triggers a grouped permission prompt.If the request is a Level 2 protocol that appears in
groupPermissionsand shares a counterparty with other Level 2 protocols -> the wallet MAY present a peer-grouped prompt (all Level 2 protocols for that peer at once).If the request is not covered by any manifest declaration, or if the manifest is unavailable -> the wallet triggers an individual permission prompt for the specific permission type.
The routing is determined by the nature of each incoming request and what the manifest declares.
5.6 PACT Lifecycle
Compliant wallets MUST implement the following PACT lifecycle behavior:
PACT is not an in-memory-only permission. A granted PACT is represented by on-chain Level 2 protocol permission tokens scoped to the originator + counterparty pair.
Establishing a PACT
A PACT request flow triggers only when all of the following conditions are met:
Grouped permission prompting is enabled
Counterparty trust prompting is enabled by the wallet
The request is for a protocol permission (not basket, certificate, or spending)
The request is not privileged
The protocol is Level 2
The counterparty is a specific 66-character hex public key (not
'self'or'anyone')No PACT is already established for this originator + counterparty
If all conditions pass, the wallet:
Fetches the manifest's
counterpartyPermissionsfor the originator.Computes the relevant declared Level 2 protocol set for this originator + counterparty context.
Checks which of those protocols already have valid tokens.
Presents only missing protocols to the user in a PACT prompt.
On grant, creates on-chain protocol permission tokens for each approved protocol.
Ensures subsequent checks observe the newly granted state consistently.
Checking if a PACT Exists
For PACT evaluation, define the relevant protocol set as the Level 2 protocols from counterpartyPermissions that apply to the current originator + counterparty context. PACT is established only when every protocol in this set has a valid, unexpired token.
The wallet checks in this order:
If counterparty is
'self'or'anyone'-> alwaystrue.If the manifest has no
counterpartyPermissions->true(no PACT needed). This means applications that do not declarecounterpartyPermissionswill never trigger a PACT prompt - Level 2 protocol requests will fall through to individual or grouped permission prompts instead.Evaluate the full relevant protocol set against canonical permission state (Section 3.4). If all protocols in the set are valid ->
true.Otherwise ->
false.
Concurrent PACT Requests
If multiple requests simultaneously require establishing a PACT for the same originator + counterparty pair, wallets MUST apply one consistent permission decision to all affected requests.
5.7 Whitelisted Counterparties
Wallets MAY define a policy-managed set of trusted infrastructure counterparties that can bypass prompts for specific Level 2 protocols.
If a wallet supports this mechanism:
It MUST scope each entry to explicit counterparty public key + protocol combinations.
It MUST NOT treat
'self'or'anyone'as whitelist targets.It SHOULD use this only for pre-vetted infrastructure entities.
5.8 Trust Settings
Wallets MAY expose user trust settings for registry and certifier selection. The trust data model and validation rules are defined by BRC-68 and are out of scope for this specification.
6. Permission Request Flow
This section defines the normative decision model wallets MUST apply when evaluating application permission requests. It specifies required outcomes and precedence constraints, without prescribing internal implementation details.
6.1 Permission Triggering
Permissions are checked at runtime when an application calls a wallet API method. Wallets MUST enforce permission checks before executing protected operations.
Applications MAY additionally declare their permissions upfront in the manifest's metanet.groupPermissions (see Section 3.2). When present, the wallet uses these declarations to batch permission prompts into a single grouped request on first interaction, reducing the number of individual prompts.
6.2 High-Level Flow
For each wallet API call from an originator, compliant wallets MUST enforce the following:
Originator handling: The wallet MUST normalize the originator domain before permission evaluation.
Admin originator: Requests from the admin originator are allowed.
Reserved namespaces: Non-admin originators MUST be denied access to admin-reserved protocol, basket, or label names (
adminprefix orpprefix).Policy gating: If wallet policy disables permission seeking for an operation category, the wallet MAY allow that operation without prompting.
Whitelist handling: If a request matches a policy-approved counterparty+protocol whitelist entry, the wallet MAY allow without prompting.
Canonical evaluation: Otherwise, permission decisions MUST be based on canonical permission state (Section 3.4).
Token validity: A valid unexpired token allows the request. An expired token triggers renewal handling.
Missing permission: If no valid permission exists, the wallet MUST obtain an explicit user decision through applicable permission flows (PACT, grouped/peer-grouped, or individual).
Decision outcome: A grant may create or renew an on-chain token (unless ephemeral). A denial MUST fail the operation with
ERR_PERMISSION_DENIED.
6.3 Grouped Permission Requests
When an application declares metanet.groupPermissions in its manifest, the wallet uses this to present a grouped permission request - a single prompt containing all the permissions the application needs.
Trigger Conditions
The grouped prompt fires only when all of the following conditions are met:
Grouped permission prompting is enabled in the wallet policy.
The manifest is available and contains a
metanet.groupPermissionsobject.The current request is included in the manifest's group permissions (see inclusion rules below).
At least one declared permission is not yet granted. The wallet checks each manifest-declared permission against existing on-chain tokens. If every permission is already granted, the grouped prompt is skipped.
If any condition fails, the wallet MUST use a non-grouped permission path for the triggering request.
Inclusion Rules
The wallet determines whether a request is "included" in the manifest's group permissions based on the request type:
Protocol: The request's
protocolIDmust exactly match a manifest entry. For Level 2, manifestcounterpartyis REQUIRED and MUST exactly match the request counterparty. For Level 1, matching is byprotocolID(counterparty is application-scoped). Empty-string counterparty values are invalid. Privileged protocol requests are never included in grouped flows.Basket: The request's basket name must exactly match a manifest entry.
Certificate: The request's
certType,verifierPublicKey, andfieldsmust all match a manifest entry. Field matching uses exact set equality (both sets must have the same elements). Privileged certificate requests are never included.Spending: Any spending request matches if the manifest declares a
spendingAuthorization(regardless of amount).
Grouped Request Contents
A grouped request bundles:
Protocol permissions (zero or more)
Basket access (zero or more)
Certificate access (zero or more)
Spending authorization (zero or one)
The user can selectively approve individual items within the group. The triggering operation MUST be allowed only if its specific required permission is approved.
Serialization
Wallets MUST ensure grouped prompting is deterministic under concurrency. A request arriving during an active grouped flow for the same originator MUST observe that flow's final decision before any additional prompt is shown.
6.4 Peer-Grouped Requests
Peer-grouped requests are a specialized form of grouped request for Level 2 protocol permissions with a specific counterparty.
Trigger Conditions
A peer-grouped flow fires when all of:
Grouped permission prompting is enabled.
The current request is for a protocol permission.
The protocol is Level 2.
The manifest contains matching Level 2 protocol entries for the same counterparty.
At least one matching permission is not yet granted.
Characteristics
A peer-grouped prompt contains only Level 2 protocol permissions for a single counterparty - no basket, certificate, or spending items. This represents a focused trust decision: "grant this counterparty access to these protocols through this app."
If peer-grouped prompting does not apply, the wallet uses another applicable permission path.
6.5 Permission-Seeking Policy
Wallets MAY expose policy controls that enable or disable prompting for specific operation categories. These controls are implementation-defined, but they MUST NOT broaden permission scope, bypass explicit denial handling, or weaken user-driven consent guarantees defined in this specification.
7. Permission Tokens (On-Chain Persistence)
Wallets MAY persist granted permissions as on-chain PushDrop outputs stored in admin baskets within the user's wallet. This provides a tamper-evident, user-owned record of all granted permissions.
7.1 Token Storage
Each permission type has a dedicated admin basket:
Protocol
DPACP
admin protocol-permission
Basket
DBAP
admin basket-access
Certificate
DCAP
admin certificate-access
Spending
DSAP
admin spending-authorization
Tokens are encoded as encrypted PushDrop outputs. All fields within the token are encrypted, so an observer inspecting the blockchain cannot determine which protocols are authorized, the expiry times, or the originator domains.
7.2 Token Fields
Each token type stores encrypted fields for permission scope and validity:
DPACP (Protocol): 6 fields
0
originator
Normalized domain
1
expiry
UNIX epoch seconds (0 = never)
2
privileged
'true' or 'false'
3
securityLevel
1 or 2
4
protocolName
Protocol identifier string
5
counterparty
'self', 'anyone', or public key
DPACP tokens MUST NOT be created for Level 0 protocols, because Level 0 is open usage and does not require permission.
DBAP (Basket): 3 fields
0
originator
Normalized domain
1
expiry
UNIX epoch seconds (0 = never)
2
basketName
Basket identifier string
DCAP (Certificate): 6 fields
0
originator
Normalized domain
1
expiry
UNIX epoch seconds (0 = never)
2
privileged
'true' or 'false'
3
certType
Certificate type identifier
4
fields
JSON-encoded array of authorized field names
5
verifier
Verifier public key
DSAP (Spending): 2 fields
0
originator
Normalized domain
1
authorizedAmount
Monthly satoshi limit
Note: DSAP tokens do not have an expiry field. Spending is tracked by calendar month.
7.3 Expiry
Tokens with
expiry === 0never expire.Tokens with
expiry > 0are compared against the current UNIX epoch time in seconds. Ifexpiry < now, the token is expired.When an expired token is found, the wallet triggers a renewal flow rather than a new permission request.
Wallets SHOULD set
expiry = 0for user-granted permissions, because users can revoke permissions at any time and non-zero expiry creates avoidable renewal friction.If a finite expiry is supported, it SHOULD be an explicit opt-in policy choice rather than the default.
DSAP (spending) tokens always have
expiry = 0- spending limits are enforced on a calendar month basis instead.
7.4 Granting
When the user approves a permission request:
The wallet applies the approved permission grant scope.
Unless this is an ephemeral request, the wallet creates or renews the corresponding on-chain permission token.
Subsequent permission checks MUST reflect the new state.
Grant variants:
Individual grant - applies to one permission request.
Grouped grant - applies to the approved subset from a grouped permission request.
PACT grant - creates protocol tokens for each approved Level 2 protocol in the PACT prompt.
Ephemeral grants: When ephemeral: true, no on-chain token is created. The permission is a one-time in-memory authorization. The caller's request is allowed, but no persistent record is kept. Used primarily for one-off spending approvals.
7.5 Denial
When the user denies a permission:
The triggering operation is rejected with error code
ERR_PERMISSION_DENIED.The wallet MUST enforce denial deterministically - it MUST NOT silently fall back or degrade.
Applications SHOULD treat denied permissions as expected states, not errors.
7.6 Revocation
Granted permissions MUST be revokable by the user at any time through the wallet's user interface. Revocation MUST spend the on-chain token output with no replacement - the transaction consumes the token UTXO and produces no new permission output, leaving nothing in the respective admin basket.
The wallet supports:
Individual revocation - spends and invalidates a single token.
Batch revocation - revokes multiple tokens.
Full originator revocation - revokes all tokens for an application, optionally filtered by type.
Revoked permissions MUST be enforced immediately. The user will be prompted again the next time the application tries to trigger the operation.
7.7 Renewal
When the wallet finds an expired token during a permission check, it triggers a renewal flow:
The wallet treats the request as a renewal of previously granted scope.
On grant, the wallet invalidates the expired token and creates a replacement token with updated validity.
On denial, the operation is rejected.
7.8 Caching
Wallets MAY use caching as an optimization, but cache contents are never authoritative. Any allow/deny decision MUST be equivalent to canonical permission-state evaluation (Section 3.4).
7.9 Request Deduplication
Wallets SHOULD avoid duplicate prompts for concurrent requests that require the same permission scope. Deduplication strategy is implementation-defined, but MUST preserve correct scope and decision consistency.
8. Manifest Guidance
Applications SHOULD:
Serve a valid W3C-compliant
manifest.jsonat their domain rootInclude a human-readable
nameandshort_namefieldInclude an
iconsarray with at least one icon for visual identification in wallet UIDeclare
metanet.groupPermissionslisting all permissions the application requires - this enables a single upfront prompt instead of repeated individual promptsDeclare
metanet.counterpartyPermissionsif the application involves Level 2 peer interactions
Applications MAY:
Include a
metanet.trustobject (BRC-68) if the application operator is also a trust entity
Applications MUST NOT:
Declare Level 1 protocols in
counterpartyPermissions(they will be silently dropped)Include misleading or inconsistent numeric claims in permission descriptions (for example, description text that states an amount different from the actual requested satoshi amount or authorization limit)
Wallets SHOULD:
Display the manifest name in all permission prompts and dashboard views
Fall back gracefully to the domain name if the manifest is unavailable or lacks a
namefield
9. Security Considerations
9.1 Security Scan
Wallets SHOULD scan manifest-sourced permission descriptions for malicious, deceptive, or internally inconsistent wording. At minimum, wallets SHOULD detect and flag description text that conflicts with structured permission data (for example, satoshi amounts in description text that differ from requested or authorized numeric values). When suspicious wording is detected, wallets SHOULD warn users clearly before approval.
9.2 General
Permissions MUST never be implicitly granted.
All permission decisions MUST be user-driven.
Revoked permissions MUST be enforced immediately.
Permission data MUST be isolated per originator.
Manifest fetches MUST be restricted to HTTPS (except localhost for development).
Manifest retrieval MUST be constrained to the origin's
manifest.jsonresource and MUST reject unrelated paths.Whitelisted counterparties SHOULD be limited to infrastructure entities pre-vetted by the wallet vendor. The whitelist MUST NOT be user-extensible without explicit configuration.
Trusted certifier public keys MUST conform to compressed public key format (
/^(02|03)[a-fA-F0-9]{64}$/).On-chain permission token fields MUST be encrypted so that only the wallet owner can read them.
Protocol and basket names starting with
adminorpare reserved and MUST NOT be accessible to non-admin originators.The basket name
defaultis reserved for internal wallet operations.PACT counterparties MUST be validated as 66-character hexadecimal strings (compressed public key format).
Implementations (Informative)
This section captures implementation-specific details. These are informative, not normative.
Wallet-Toolbox (@bsv/wallet-toolbox-client):
WalletPermissionsManagerwraps the BRC-100WalletInterface, intercepting every method call. It is the enforcement layer.Permission tokens are created, renewed, and revoked using standard BRC-100
createAction/signActionflows.Transaction descriptions for permission operations are optionally encrypted using
[2, 'admin metadata encryption']whenencryptWalletMetadatais enabled.The manager supports P-modules via the
PermissionsModuleinterface (see below).
P-Modules (PermissionsModule - BRC-98/99/111):
P-modules are an extensibility mechanism that allows wallet consumers to add custom permission handling for P-prefix baskets, protocols, and labels.
Interface: A module MUST implement two methods:
onRequest({ method, args, originator })->{ args }- transforms the request before it reaches the underlying wallet. May enforce permissions, throw errors, or modify arguments.onResponse(result, { method, originator })->result- transforms the wallet response before returning to the caller.
Registration: Modules are registered via the permissionModules config field - a Record<string, PermissionsModule> keyed by scheme ID (e.g. { "btms": new BtmsModule() }). If a P-prefix name references an unregistered scheme ID, the wallet throws immediately.
Detection and routing: Any basket name, protocol name, or label starting with "p " triggers the P-module system. The scheme ID is extracted as the second token (e.g. "p btms token123" -> scheme ID "btms").
P-label format (BRC-111): Labels MUST follow the format p <moduleId> <payload> where:
moduleIdis at least 1 character, no spacesA single space separates
moduleIdfrompayloadpayloadis at least 1 character and MUST NOT start with a space
Scope: P-modules handle baskets (listOutputs, relinquishOutput, createAction, internalizeAction), protocols (encrypt, decrypt, createHmac, verifyHmac, createSignature, verifySignature, getPublicKey), and labels (createAction, listActions, internalizeAction).
Bypass behavior: P-module paths completely bypass the standard on-chain permission token system (DPACP/DBAP/DCAP/DSAP). The module's onRequest method is solely responsible for any authorization logic. Non-P baskets, protocols, and labels in the same operation still go through the standard permission checks.
Chaining: When multiple P-modules are involved in a single operation (e.g. createAction with baskets from one scheme and labels from another), onRequest calls are chained forward through all modules, and onResponse calls are chained in reverse order.
References
This specification references or builds upon:
Publishing Trust Anchor Details at an Internet Domain
metanet.trust manifest structure for trusted certifiers
Unified Wallet-to-Application Interface
Underlying wallet API that the permissions manager wraps
Non-Goals
This document does not define:
UI layout or copy
Wallet-specific storage implementations
Application UX strategies beyond enforcement expectations
The full internal API surface of
WalletPermissionsManager
Backwards Compatibility (babbage -> metanet)
babbage -> metanet)Prior versions of the wallet permissions system used a babbage namespace in the manifest instead of metanet. The previous format was:
Compared to the metanet format defined by this specification, the legacy babbage format did not standardize:
A versioned permissions namespace (
schemaVersion)A W3C Web App Manifest-aligned structure for application metadata
A normative compatibility model for consuming both legacy and current namespaces during migration
Migration Path
Applications SHOULD update their manifests to use the metanet namespace and W3C-compliant structure. The migration involves:
Rename the namespace: Change
"babbage"to"metanet".Add
schemaVersion: Add"schemaVersion": 1inside themetanetobject.Add W3C fields: Include standard manifest fields (
short_name,description,start_url,display,icons) at the top level.Move trust data: If using
babbage.trust, rename tometanet.trust(structure is unchanged).
Wallet Compatibility
Wallets MUST support both namespaces during the transition period:
Check for
metanetfirst. If present, use it.If
metanetis absent, fall back tobabbageand treat it as equivalent tometanetwithout aschemaVersion.Log a deprecation warning when a
babbagenamespace is encountered to encourage migration.
Timeline
The babbage namespace is deprecated as of this specification. Wallets SHOULD continue to support it for backwards compatibility until a future version removes support. Applications SHOULD migrate to metanet at their earliest convenience.
Last updated
Was this helpful?

