githubEdit

Wallet Permissions and Counterparty Trust

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-43arrow-up-right specification defines how these are applied and used within cryptographic operations.

  • Manifest: A JSON file served at https://{domain}/manifest.json providing 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 Manifestarrow-up-right 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:

  1. Application metadata - Standard W3C fields (name, short_name, description, icons) for display in permission prompts and the wallet dashboard.

  2. 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.

  3. 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-73arrow-up-right):

Field
Type
Description

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:

  1. Check whether a permission token already exists for this operation. If so, allow the operation. Otherwise, proceed with step 2.

  2. Fetch the originator's manifest.

  3. 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.

  4. Filter out permissions already granted (by looking up existing on-chain tokens). Remove them from the list before showing the user a prompt.

  5. Present all remaining permissions as a single grouped prompt for the user's approval.

  6. 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:

Field
Type
Description

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:

#
Type
Protocol Acronym
Scope Key
Counterparty-Aware

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, 1 or 2

    • protocolName - string identifier for the protocol

  • counterparty: 'self', 'anyone', or a compressed public key identifying the other party. Empty string is invalid.

    • For Level 2, counterparty is REQUIRED and permission scope is per-originator + per-counterparty.

    • For Level 1, any counterparty is 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.

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.counterpartyPermissions for a given counterparty in a single prompt, rather than prompting one-by-one as each protocol is used

  • Scope 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:

Aspect
Group Permissions
PACT

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 counterpartyPermissions covering 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 groupPermissions and 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:

  1. Grouped permission prompting is enabled

  2. Counterparty trust prompting is enabled by the wallet

  3. The request is for a protocol permission (not basket, certificate, or spending)

  4. The request is not privileged

  5. The protocol is Level 2

  6. The counterparty is a specific 66-character hex public key (not 'self' or 'anyone')

  7. No PACT is already established for this originator + counterparty

If all conditions pass, the wallet:

  1. Fetches the manifest's counterpartyPermissions for the originator.

  2. Computes the relevant declared Level 2 protocol set for this originator + counterparty context.

  3. Checks which of those protocols already have valid tokens.

  4. Presents only missing protocols to the user in a PACT prompt.

  5. On grant, creates on-chain protocol permission tokens for each approved protocol.

  6. 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:

  1. If counterparty is 'self' or 'anyone' -> always true.

  2. If the manifest has no counterpartyPermissions -> true (no PACT needed). This means applications that do not declare counterpartyPermissions will never trigger a PACT prompt - Level 2 protocol requests will fall through to individual or grouped permission prompts instead.

  3. Evaluate the full relevant protocol set against canonical permission state (Section 3.4). If all protocols in the set are valid -> true.

  4. 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 (admin prefix or p prefix).

  • 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:

  1. Grouped permission prompting is enabled in the wallet policy.

  2. The manifest is available and contains a metanet.groupPermissions object.

  3. The current request is included in the manifest's group permissions (see inclusion rules below).

  4. 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 protocolID must exactly match a manifest entry. For Level 2, manifest counterparty is REQUIRED and MUST exactly match the request counterparty. For Level 1, matching is by protocolID (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, and fields must 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:

  1. Grouped permission prompting is enabled.

  2. The current request is for a protocol permission.

  3. The protocol is Level 2.

  4. The manifest contains matching Level 2 protocol entries for the same counterparty.

  5. 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:

Type
Acronym
Admin Basket Name

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

#
Field
Description

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

#
Field
Description

0

originator

Normalized domain

1

expiry

UNIX epoch seconds (0 = never)

2

basketName

Basket identifier string

DCAP (Certificate): 6 fields

#
Field
Description

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

#
Field
Description

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 === 0 never expire.

  • Tokens with expiry > 0 are compared against the current UNIX epoch time in seconds. If expiry < 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 = 0 for 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:

  1. The wallet applies the approved permission grant scope.

  2. Unless this is an ephemeral request, the wallet creates or renews the corresponding on-chain permission token.

  3. 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:

  1. The wallet treats the request as a renewal of previously granted scope.

  2. On grant, the wallet invalidates the expired token and creates a replacement token with updated validity.

  3. 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.json at their domain root

  • Include a human-readable name and short_name field

  • Include an icons array with at least one icon for visual identification in wallet UI

  • Declare metanet.groupPermissions listing all permissions the application requires - this enables a single upfront prompt instead of repeated individual prompts

  • Declare metanet.counterpartyPermissions if the application involves Level 2 peer interactions

Applications MAY:

  • Include a metanet.trust object (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 name field


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.json resource 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 admin or p are reserved and MUST NOT be accessible to non-admin originators.

  • The basket name default is 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):

  • WalletPermissionsManager wraps the BRC-100 WalletInterface, intercepting every method call. It is the enforcement layer.

  • Permission tokens are created, renewed, and revoked using standard BRC-100 createAction / signAction flows.

  • Transaction descriptions for permission operations are optionally encrypted using [2, 'admin metadata encryption'] when encryptWalletMetadata is enabled.

  • The manager supports P-modules via the PermissionsModule interface (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:

  • moduleId is at least 1 character, no spaces

  • A single space separates moduleId from payload

  • payload is 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:

BRC
Title
Relevance

Publishing Trust Anchor Details at an Internet Domain

metanet.trust manifest structure for trusted certifiers

Group Permissions for App Access

GroupedPermissions interface for batched permission requests

P Protocols and P Baskets

Extensible permission modules for P-prefix schemes

Unified Wallet-to-Application Interface

Underlying wallet API that the permissions manager wraps

P Labels

Label format p <moduleId> <payload> for permission modules


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)

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:

  1. Rename the namespace: Change "babbage" to "metanet".

  2. Add schemaVersion: Add "schemaVersion": 1 inside the metanet object.

  3. Add W3C fields: Include standard manifest fields (short_name, description, start_url, display, icons) at the top level.

  4. Move trust data: If using babbage.trust, rename to metanet.trust (structure is unchanged).

Wallet Compatibility

Wallets MUST support both namespaces during the transition period:

  1. Check for metanet first. If present, use it.

  2. If metanet is absent, fall back to babbage and treat it as equivalent to metanet without a schemaVersion.

  3. Log a deprecation warning when a babbage namespace 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?