Beersy
BRC-185

Wallet Substrate Discovery, Selection, and Encrypted Mobile Connections

Right now if you run two wallet-enabled apps on the same computer, or try to use your phone's wallet from a desktop browser, things break: one app grabs the only expected port, or whichever wallet loads last wins and silently takes over. There was no agreed way for an app to see what wallets are available, let the user pick one, and keep that choice stable without accidentally scrambling which wallet which connection.

Ty Everett, Darren Kellenschwilerchanged 13 Oct 202662 min read

Reference for an AI

Everything an assistant needs to answer questions about BRC-185 accurately, including what it depends on.

Summary

Why
Multiple wallets and devices can be present at once, and without a shared discovery and selection contract, apps and users have no reliable way to control which wallet actually handles a given connection.
What
BRC-185 is a specification for how applications discover multiple wallets, let a user or app pick one, and carry the full BRC-100 over local, embedded, browser-extension, and encrypted mobile connections.
How
A developer wires their app to a connection that probes ascending localhost ports and other for wallets, presents a neutral chooser (or runs automatic/explicit selection the app configures), binds each connection to one wallet for its lifetime, and, for mobile, pairs over an encrypted relay using…

What this lets you do

  • Discover several wallets available on a device without triggering any permission prompt
  • Let an app or user pick one wallet per connection via a neutral chooser UI
  • Keep separate connections pointed at separate wallets with no ambient 'active wallet' takeover
  • Pair a desktop app with a phone wallet over an encrypted relay using a QR/URI scheme
  • Wrap the resulting connection as an ordinary WalletInterface for onboarding or fee layers

Written by claude-sonnet-5 from the specification text. Where the two differ, the original is correct.

wallets foundselection polibound wallet

The specification

Abstract

This proposal specifies how applications discover multiple wallets, select one wallet per connection, and carry the complete BRC-100 core interface over local, embedded, extension, and encrypted mobile . Separately specified additive getVersion metadata is permitted under section 1 without negotiating away any core method or guarantee. It defines ascending localhost port allocation, a neutral chooser contract, origin-scoped selection memory, safe switching, a vendor-neutral pairing URI, and a mandatory encrypted mobile relay profile. Discovery, connection management, and user interfaces are composable components outside the core SDK. Applications can use automatic selection or supply their own user experience, including commercial onboarding wrappers.

Status: proposed specification; draft for review. The wire profiles and requirements below are proposed interoperability contracts, not claims that existing wallets implement them. No implementation or security-audit result is asserted.

Motivation

An application should be able to use one wallet while another application uses another wallet on the same computer. A user should also be able to use a phone wallet from a desktop browser or move from a phone browser into a wallet and back. A single occupied localhost port, a last-writer-wins injected global, or a race between transports must not determine the user's wallet.

The September 23–24, 2026 discussion between Deggen, Ty Everett, and wallet users identified these problems and proposed ascending ports, selection among substrates, embedded-wallet defaults, and mobile QR pairing. The relevant public discussion and implementation inputs are linked in the references. This proposal turns those inputs into one connection contract while preserving the existing .

Specification

1. Scope, terminology, and conformance

The capitalized requirements use BCP 14. Sections 1–12 are normative. Subsequent sections are non-normative.

The complete BRC-100 core contract is mandatory; core-method negotiation is prohibited. This proposal MUST NOT add BRC-100 methods, parameters or result fields, or permit negotiation of a subset of its methods. getVersion({}) retains its { version: string } result shape. A legacy adapter MAY use the call as a noninteractive readiness probe; a successful probe or metadata advertisement is not proof of conformance, security or authorization.

Additive attributes within that string MAY use BRC-205, which extends the complete string to at most 2048 ASCII bytes while retaining a 7–30-byte base. Wallets MAY emit that format without a separate opt-in, coordinated configuration, or support-discovery exchange. Consumers adopting BRC-205 MUST accept its bounds and parsing rules, including base-only results and ignoring unknown attributes. This is a narrow exception to this document's unchanged-result wording: only the syntax and length of getVersion's string contents are extended, not its result shape, call code, transport framing, or the core behavior of any other method. Older strict validators may reject extended strings and need updates; BRC-185 support alone does not prove that an implementation's validator has been updated. No metadata-profile negotiation mechanism is introduced here.

Future BRC-defined attributes MAY advertise support for additive permission schemes at the existing reserved dispatch points in BRC-98 (), BRC-99 (), and BRC-111 (action ). These use p followed by a space-free scheme/ and the scheme-defined remainder; the governing BRC's syntax and constraints still apply. Advertisements MUST distinguish the namespace and exact scheme/module ID and identify the defining specification. They MUST NOT replace mandatory methods, change ordinary non-scheme operation semantics, or themselves grant permission or bypass security checks. The wallet still decides whether it supports the explicitly requested scheme and applies that scheme's defined permission and operation rules; unsupported reserved identifiers remain rejected. A missing advertisement is not proof of non-support, and a present advertisement is not proof that an operation will be authorized or succeed. This BRC allocates no scheme-advertisement key or list format; future specifications define those details and their freshness/privacy behavior.

Every wallet claiming conformance MUST fully implement all 28 BRC-100 methods, and every conforming connection MUST carry all of them, with their complete arguments, results, errors, and originator semantics. An application is entitled to assume the full interface. An omitted implementation, method whitelist in a relay, or unsupported-method stub is nonconforming. A genuine permission denial, lock state, invalid argument, insufficient balance, or documented resource failure is not a missing method. Blanket denial used to conceal an absent implementation is nonconforming. BRC-116 permission decisions continue to apply; neither discovery nor selection grants permission. A restricted tool-server facade is not a substitute for a conforming wallet connection.

TermDefinition
ProviderOne independently selectable wallet instance. A product name, account, transport, and provider are different things.
Substrate / adapterA communications mechanism / its implementation behind the discovery and connection interfaces.
CandidateOne provider and its reachable transport aliases, with provenance and availability state.
ConnectionAn immutable binding to one provider, one application security context, and one connection .
EpochA fresh, random UUIDv4 created when a binding is established. Wallet-specific references belong to that binding.
Framework-neutral discovery, selection, memory, and connection orchestration outside the core SDK.
ChooserA replaceable presentation adapter; graphical, terminal, native, or application-defined.
WrapperApplication-selected functionality around a bound WalletInterface, such as onboarding, funding, or monetization.
Application For a web application, the serialized URL origin: scheme, ASCII , and nondefault port. It is not merely a domain.
OriginatorThe BRC-100 application domain supplied to wallet methods. For web callers this is the origin's ASCII hostname, without scheme or port.

A controller MUST distinguish the full application origin for connection/memory isolation from BRC-100's domain-shaped originator. It MUST NOT change existing BRC-100 permission semantics by treating its full origin as a new method argument. Non-web hosts MUST supply an explicit, stable application context and originator; a headless caller cannot acquire browser-origin authenticity by supplying a string.

Web URL parsing and origin serialization use the WHATWG URL Standard. A supplied origin MUST equal its parsed URL's serialized origin, with no credentials, path, query, or fragment; reject opaque null origins. For this profile loopback means localhost, a canonical dotted-decimal IPv4 address in 127.0.0.0/8, or [::1]. HTTP descriptor addresses MUST be serialized origins with one of those hosts and their actual port; no arbitrary DNS hostname, user information, path, query, redirect, or fragment is a loopback-discovery address. Display the complete ASCII origin when asking for approval; a friendly application name does not replace it.

Conformance is role-specific: a wallet implements the full interface and its advertised substrates; an adapter implements its advertised substrate; a controller and chooser implement their respective requirements; a mobile implementation implements sections 8–10. A wallet need not implement every physical substrate. Controllers MUST accept additional adapters through the same interfaces and MUST NOT exclude an otherwise usable provider because of vendor, commercial affiliation, or substrate family.

“Runtime support” means facts such as DOM availability, permitted loopback access, native bridge presence, or an interactive terminal. It MUST NOT mean a subset of BRC-100 methods. Wire version 1 below versions this connection protocol, not BRC-100.

2. Component boundaries and automatic WalletClient integration

The dependency direction MUST be:

application / commercial wrapper
             |
       WalletInterface
             |
   bound connection / WalletClient
             |
 external connection controller ---- external chooser / onboarding hooks
             |
 interchangeable discovery and transport adapters
             |
           wallet

Discovery algorithms, scanners, provider registries, pairing, relay clients, storage, chooser state, and framework-specific UI MUST live outside the core SDK source and runtime dependencies. The SDK MAY define a small interface and delegate to an externally supplied controller. A controller MUST return a complete WalletInterface; it MUST NOT require applications to know which substrate supplied it.

The following behavioral interface is normative; equivalent language bindings are permitted:

interface ConnectionRequest {
  applicationContext: string
  originator: string
  mode: 'automatic' | 'ask' | 'explicit'
  explicitProvider?: string
  signal?: AbortSignal
}
interface ConnectionProvider {
  resolve(request: ConnectionRequest): Promise<BoundConnection>
}
interface BoundConnection {
  wallet: WalletInterface
  providerId: string
  epoch: string
  close(): Promise<void>
}

providerId is controller-local and MUST NOT be a wallet account . The controller MUST expose state changes, a current candidate snapshot, explicit refresh, choose/change-wallet, forget-preference, and disconnect operations to presentation adapters. Subscription MUST return an unsubscribe function. Snapshot entries MUST include provider ID, display name, provenance, availability, transport aliases, and whether continuity was verified. Discovery completion MUST include per-adapter complete, partial, blocked, or unsupported coverage; an empty partial snapshot is not “no wallet installed.”

For JavaScript auto integration, an external package MAY register a ConnectionProvider under globalThis[Symbol.for('brc185.connection-provider.v1')]. Registration MUST be explicit application initialization, MUST reject replacement of a different registered provider unless the application explicitly requests replacement, and MUST finish before the first automatic connection. An explicitly supplied provider takes precedence over the registration. The registered provider MUST implement resolve; malformed registrations fail with CONFIGURATION_ERROR. This is a same-realm integration hook, not an authenticated extension discovery channel.

An upgraded WalletClient in automatic mode MUST delegate to the supplied or registered provider on its first connection attempt. The controller automatically invokes the chooser only when section 5 requires it. Concurrent first calls MUST share one pending resolution; they MUST NOT produce multiple chooser windows or multiple bindings. After resolution, calls use that binding until explicitly closed. Resolution cancellation MUST reject every waiting call without sending it to any wallet.

An explicitly supplied WalletInterface or explicitly selected legacy substrate MUST retain its existing behavior and MUST NOT unexpectedly open a chooser. In the absence of a supplied/registered provider, the SDK MUST preserve the existing automatic connection path and errors; upgrading the SDK alone MUST NOT add a DOM dependency, scan additional ports, open a popup, or install a global controller. A host with no chooser can use an explicit provider or unambiguous automatic selection; otherwise it receives SELECTION_REQUIRED with the snapshot. It MUST NOT hang awaiting nonexistent UI.

SDK documentation MUST show separately distributed browser/React, plain HTML/JavaScript, Node/terminal, and headless integrations, plus custom adapter, UI, storage, and wrapper composition. Example package names and APIs MUST be labeled as examples until those packages exist. Importing the core SDK in Node, a worker, React Native, or server rendering MUST not access DOM globals.

3. Discovery records, continuity, and privacy

Discovery MUST be noninteractive at the wallet-permission layer. It MUST NOT sign transactions, retrieve balances or account identity keys, unlock a wallet, or request BRC-100 permissions. Browser or OS permission to access a substrate is a separate state that the controller MUST explain and respect.

New providers expose the following descriptor. JSON in this BRC is UTF-8, rejects duplicate property names, invalid Unicode, nonfinite numbers, and unknown fields unless an explicit extension field is defined. Cryptographic canonicalization uses RFC 8785 JCS. Base64url uses the unpadded RFC 4648 alphabet; decoders MUST reject noncanonical encodings.

FieldType and requirement
versionInteger, exactly 1.
instanceIdLowercase UUIDv4, stable for this provider and application context. Different independently selectable profiles use different IDs.
namePlain text, 1–80 UTF-8 bytes; product name, never executable markup.
instanceLabelPlain text, 0–80 UTF-8 bytes; optional user-understandable distinction between instances, no secret/account identity.
iconOptional image reference under section 3.1; the same decoded value used by brc185-icon.
vendorLowercase reverse-domain string, 1–253 ASCII bytes; self-asserted, not proof of domain ownership.
continuityKeyBase64url of a 32-byte Ed25519 public key generated independently of wallet spending/identity keys.
endpoints1–32 records {id, type, address}. IDs are unique 1–64-character ASCII tokens matching [A-Za-z0-9._-]+.

type is http-wire, http-json, https-json, window, xdm, react-native, or an absolute HTTPS URI naming an extension's public specification. address is a URL for HTTP types and an opaque adapter-local for other types, at most 2048 UTF-8 bytes. An endpoint record never authorizes connecting to a different host. Adapter and application policy still apply.

The provider MUST maintain a distinct random continuity key and instance ID per application context; transports for the same instance/context share them. They MUST persist across ordinary restarts, MUST rotate on reset, and MUST NOT be derived by exposing an account identity key. Reset/rotation means a new provider for remembered-choice purposes. Private continuity material MUST be protected as a secret, using secure OS storage where available. It MUST never be returned to an application or relay.

A provider exposing only one active internal account at a time MAY retain its (instanceId, continuityKey) when that account changes. This proves provider continuity, not account continuity; section 6 still requires invalidation of the old account's binding. Independently selectable provider instances/profiles remain distinct.

describe(origin, challenge) returns {descriptor, challenge, signature}. challenge is a fresh 32-byte random base64url value supplied by the controller. The Ed25519 signature is over:

UTF8("BRC-185 descriptor v1\n") || JCS({origin, challenge, descriptor})

Use pure Ed25519 from RFC 8032, not Ed25519ph or a wallet signing key. The controller MUST verify the signature, exact challenge and context before using continuity or merging aliases. The response is bounded to 16,384 bytes. origin is supplied by the trusted adapter context; a web request body's origin claim MUST NOT override the browser-supplied origin.

Two candidates MUST be merged only when they prove the same (instanceId, continuityKey) in the same context, or a trusted application-supplied adapter explicitly attests that they are aliases of the same object. Name, vendor, icon, port, wallet version, response speed, and account public key are insufficient. A single product can legitimately occupy several rows. Conflicting metadata for a proven identity is shown as a conflict and requires explicit selection; it MUST NOT silently overwrite an existing row.

Legacy providers lacking this record MUST remain usable through adapters. They are marked legacy/unverified; a controller MUST NOT invent verified continuity or silently persist a transport address as wallet identity. On a later resolution, a remembered legacy choice requires confirmation. Within a live, unmodified explicit object binding it remains usable normally.

Names and textual metadata MUST be rendered as inert text. The descriptor has no analytics URL, balance, , account key, or method list. Optional UI artwork may be application-supplied or use section 3.1's provider-supplied image reference. It is treated as untrusted and MUST NOT trigger network requests merely because a wallet announced itself. Discovery records and the candidate inventory MUST remain local to the application unless the user explicitly requests sharing them. Storage denial MUST degrade to in-memory preferences, not block connection.

Continuity proves possession of a previously seen transport key. It is not product certification, account authentication, code integrity, or protection against a compromised application, wallet, browser extension, or operating system.

3.1 Optional wallet icon reference

This BRC defines the optional BRC-205 attribute brc185-icon: one image reference for the responding wallet product. Its value uses BRC-205's percent-encoded UTF-8, single-occurrence, generic bounds and unknown-key rules. The decoded reference MUST be 1–256 UTF-8 bytes and one of:

  • An absolute HTTPS URL serialized under the WHATWG URL Standard, with no credentials or fragment.
  • uhrp://<identifier>, where the case-sensitive identifier is Base58Check of prefix bytes ce00 and a 32-byte SHA-256 hash, as described in BRC-26.
  • A canonical chirp://<rootIdentifier> URI under BRC-167.

Scheme names MUST be lowercase; identifiers and HTTPS paths/queries retain their case-sensitive meaning. Consumers MUST validate the reference without trimming, case-folding its identifier, or applying another percent-decoding pass to the BRC-205 value. Inline data URIs and other schemes are outside this attribute. Omission means no icon is supplied; invalid or unsupported icon metadata is treated as absent and MUST NOT prevent wallet use or change selection.

A provider MAY include the same decoded reference in its optional descriptor icon field, covered by the existing descriptor signature, so a chooser can obtain artwork without invoking wallet methods. The attribute supplies artwork through the existing getVersion({}) result on the selected connection or an otherwise permitted noninteractive legacy readiness probe. Neither path authorizes retrieving account labels from every discovered wallet or causing a permission/unlock prompt. An icon, its URL, or a signature over that URL is not vendor certification, provider identity, account continuity, or an authentication result.

Controllers/UIs MAY resolve supported references when presenting a chooser or a connected-wallet surface. Resolution MUST remain outside the core SDK dependencies, honor application fetch policy, and apply bounded retrieval and image-decoding limits. and CHIRP resolution MUST verify content under their respective protocols. Retrieved artwork MUST be displayed only in a non-executable image context, never inserted as document markup. Missing resolver support, blocked access, failed retrieval or an invalid image MUST fall back to generic artwork without delaying or rejecting connection. Wallets MUST supply public product artwork, not account-specific identifiers, secrets, tracking tokens or application histories in the reference.

Consumers MAY cache images by the exact reference using HTTPS cache semantics or the applicable content-addressed integrity rules. They SHOULD refresh the reference from a new descriptor snapshot or selected-wallet metadata result when presenting a new connection surface, and MUST discard the previous provider's displayed reference on provider switch. Icon changes MUST NOT invalidate an epoch, merge providers, or alter chooser ordering. Remote retrieval can reveal the user's IP address and connection timing; applications MAY disable it or use application-supplied artwork.

ExampleInterpretation
example-1.2.3; brc185-icon=https%3A%2F%2Fwallet.example%2Ficon.png;Valid BRC-205 attribute; decoded reference is https://wallet.example/icon.png.
Descriptor icon: "https://wallet.example/icon.png"The same reference, without BRC-205 value encoding, inside the signed descriptor.
Decoded uhrp://<identifier> or chirp://<rootIdentifier>Valid only with the corresponding valid identifier; angle-bracket text is schematic, not a literal value.
Decoded http://wallet.example/icon.png, data:image/png;base64,..., or an invalid content identifierIcon absent.
A decoded reference over 256 UTF-8 bytes, or duplicate brc185-icon keysBRC-205 generic validation makes all attributes unavailable; wallet use remains unaffected.

4. Local and embedded substrate profiles

4.1 Ascending localhost allocation

The starting ports are fixed:

SubstrateFirst candidateSubsequent candidates
BRC-100 binary HTTP / BRC-5http://localhost:33013302, 3303, …, 65535
BRC-100 JSON HTTP / BRC-5http://localhost:33213322, 3323, …, 65535
BRC-100 JSON HTTPS / BRC-5https://localhost:21212122, 2123, …, 65535

A wallet starting a listener MUST attempt an exclusive bind at the first port and increment by one after address-in-use failures until a bind succeeds or its configured allocation ceiling is reached. The bind itself is the allocation operation; a separate availability test is insufficient. Unrelated services also occupy ports. Other bind errors MUST be surfaced, not treated as of an available wallet. No vendor owns a port. A wallet with several listeners allocates them independently and advertises all successfully bound endpoints; offsets need not match.

The sequences intentionally overlap. Port number does not identify wire format. For example, a binary listener may bind 3321 after earlier ports are occupied. New endpoints MUST serve GET /.well-known/brc-wallet for discovery independently of their RPC format. The controller reads the signed endpoint type before sending RPC. It MUST deduplicate identical scheme/host/port probes across scan sequences. A wallet MAY serve both formats on one listener if it dispatches by the BRC-5 content type and advertises both.

Listeners MUST bind only loopback interfaces, never wildcard/LAN interfaces for this profile, and MUST validate Host and web Origin. Implementations using both IPv4 and IPv6 MUST ensure both addresses route to the same instance for an advertised localhost endpoint; otherwise they MUST advertise the actual loopback literal as a distinct address and avoid ambiguous localhost binding. A failure to reserve one address family MUST NOT cause requests to be routed to a different wallet in that family.

The default discovery round examines offsets 0–63 inclusive from each base, in ascending offset order, with at most 16 simultaneous requests and a 1000 ms deadline per discovery request. It MUST NOT stop at the first gap or first successful wallet. http://localhost overlap is probed once; HTTP and HTTPS remain separate addresses. These are bounded default discovery windows, not reserved port ranges or allocation ceilings. A larger explicit window or explicit loopback endpoint MAY be configured. The snapshot MUST disclose the covered intervals and any blocked/timed-out probes. UI MUST offer refresh and a way to extend/configure the window when coverage is incomplete. It MUST NOT claim to have searched all ports or all installed wallets.

Discovery SHOULD begin only on an application wallet-use or connect action. A controller MUST NOT repeatedly rescan in the background, around browser local-network permissions, disable certificate validation, or follow discovery redirects. A policy-blocked scan is blocked, not evidence of absence. A browser UI can ask for local-network access through normal browser controls or offer another substrate. The HTTPS profile requires a normally trusted certificate; the HTTP profile retains the local BRC-5 trust boundary and MUST NOT be presented as encrypted remote pairing.

The discovery request has query parameter challenge containing the 32-byte challenge. Web servers derive origin from the browser Origin header. Non-web adapters send an explicit Originator plus a configured application-context header BRC-185-Context; these are host assertions, not browser authentication. Responses are 200 application/json with the section 3 proof, Cache-Control: no-store, and Vary: Origin. CORS MUST return the exact permitted origin, never reflect null, and MUST NOT enable credentials or authorize wallet operations through discovery. Preflight is noninteractive. Descriptors MUST advertise the responding endpoint with a matching scheme, host, port, and format.

To bind a new-profile HTTP connection, the controller sends POST /.well-known/brc-wallet/connect with JSON {epoch, challenge, instanceId, continuityKey} and the same origin rules as discovery. epoch is a new UUIDv4 and challenge is a new random 32-byte base64url value. A matching provider returns {epoch, descriptor, challenge, signature}, where signature covers UTF8("BRC-185 connect v1\n") || JCS({origin, epoch, challenge, descriptor}). A mismatch returns HTTP 412 without binding. The controller verifies this fresh proof and the selected identity before accepting the connection. The wallet binds epoch to this application context and its current account context; existing epochs cannot be rebound.

New-profile HTTP RPC uses POST /brc185/v1/rpc/<BRC-100 methodName> with headers BRC-185-Epoch, BRC-185-Instance, and BRC-185-Key carrying the bound epoch, instance ID, and continuity public key. Before dispatch, the wallet MUST validate all three against a live binding and the request origin. An unknown, retired, or mismatched binding returns HTTP 412 with a section 6 PROVIDER_CHANGED error and not-sent delivery. The distinct path is necessary: if the port is reused by a legacy wallet that ignores new headers, it must not accidentally execute a legacy method route. HTTP 404, missing binding validation support, and changed proofs MUST NOT trigger fallback/replay of that call onto legacy routes.

The new route carries the existing BRC-5 binary or JSON method payload/result representation, content type, and origin semantics, subject only to section 1's separately adopted getVersion string-content profile; it adds no BRC-100 argument. Success uses HTTP 200 and the original method result/error representation. DELETE /.well-known/brc-wallet/connect/<epoch> with the three binding headers closes the binding and returns HTTP 204; the same origin checks apply. CORS preflight MUST support those named headers and DELETE for permitted origins. Responses MUST be noncacheable. An actual listener/account restart invalidates in-memory bindings even if its continuity key persists.

Existing POST /<methodName> routes remain unchanged for legacy clients. No /v1 prefix is added to those existing routes. Adapters MAY probe legacy default endpoints using unchanged getVersion({}) when the well-known path is absent. Only the known legacy default format is assumed in that fallback; nondefault ambiguous endpoints require an explicit format configuration. An arbitrary HTTP response is not a wallet. A legacy HTTP binding lacks this new protection against port reuse and MUST be identified accordingly; probing a version string cannot supply it.

4.2 Browser extensions and window.CWI

Multiple new providers MUST announce independently; they MUST NOT compete to overwrite window.CWI. The JavaScript event names are brc185:request-provider and brc185:announce-provider. A controller first installs its announcement listener, then dispatches a CustomEvent on window with detail {version:1, requestId} where requestId is a UUIDv4. Each provider responds with detail {version:1, requestId, handle}. handle exposes describe(origin, challenge) and connect(origin, epoch, onClose): Promise<WalletInterface>, where onClose(reason) reports account-changed, revoked, or lost. The returned interface is bound to that epoch and cannot be retargeted. Providers MUST answer each fresh request once and MUST NOT trigger wallet prompts when announcing or describing. The initial event collection window is 200 ms; late announcements MUST update the snapshot and never replace an already bound connection.

The controller MUST call describe with the actual window.location.origin and a fresh challenge, with a 1000 ms noninteractive discovery deadline. A timeout is partial coverage. connect MUST bind that origin. The handle also exposes disconnect(epoch): Promise<void> to close its binding without modifying any BRC-100 method. Browser extensions MUST derive the web origin at their trusted extension boundary and MUST NOT accept an override from page-supplied arguments. A controller MUST snapshot and validate metadata, limit unsolicited announcements, and retain a dismissal option for abusive providers. Page events and provider labels themselves are not authenticated extension installation evidence.

An adapter MUST also support an existing window.CWI object as a legacy provider. If a trusted host identifies it as the same object as an announced provider, it is one candidate. Otherwise a potentially duplicate entry remains distinguishable by provenance; guessing a merge is prohibited. A legacy overwrite cannot reveal providers that were already overwritten; controllers MUST not claim that it can.

4.3 XDM and React Native

A top-level browser document whose parent is itself has no parent-XDM provider; that adapter reports unsupported without sending a message. A missing native bridge similarly reports unsupported, rather than waiting on a nonexistent channel.

New XDM discovery sends {type:"brc185:request-provider",version:1,requestId,challenge} and returns {type:"brc185:announce-provider",version:1,requestId,proof} through JSON postMessage data. The request ID is a UUIDv4 and challenge is a fresh random 32-byte base64url value. The discovery response deadline is 1000 ms; a timeout is partial coverage. Initial discovery MAY send only those nonsensitive values to the direct parent with target * when its origin is not yet known. The receiver derives the application origin from MessageEvent.origin. The controller MUST accept an announcement only from that expected parent window, reject opaque/null origins, verify the proof, and pin event.source and event.origin for every subsequent message. It MUST NOT broadcast wallet calls to all windows. Application-registered wallet frames require an explicit expected source and origin.

XDM connection establishment sends {type:"brc185:connect", version:1, instanceId, continuityKey, epoch, challenge} to the pinned source/origin. The wallet validates identity and returns {type:"brc185:connected", version:1, proof} with the same signed connect proof as HTTP. The controller verifies it before invoking methods. A new epoch binds the current account context; mismatches return {type:"brc185:connection-error", version:1, epoch, error} carrying a section 6 error. Wallets MUST reject invocation before establishment.

New XDM invocation envelopes are {type:"brc185:invoke", version:1, instanceId, epoch, id, method, params}. epoch and id are UUIDv4 values. Responses are {type:"brc185:result", version:1, instanceId, epoch, id, result} or the same routing fields with type:"brc185:error" and error:{code, message, data?}. code and optional data preserve the underlying wallet error; stack traces SHOULD be withheld. The wallet derives originator from the pinned caller's hostname. All routing fields MUST match the active binding; unknown methods and mismatched fields are rejected before dispatch. Every BRC-100 method and its existing JSON representation is carried. Result and error are mutually exclusive. New envelopes coexist with, and MUST NOT reinterpret, legacy BRC-6 CWI envelopes. Either side closes with {type:"brc185:closed", version:1, instanceId, epoch, reason}, with reason account-changed, revoked, or lost; it MUST come from the pinned peer. Closed epochs are never reused.

React Native adapters use those JSON envelopes over the host's registered WebView bridge, with origin derived from the native host's verified current document URL. The host MUST invalidate a binding on navigation to another origin and MUST distinguish independent WebViews/providers. A page-provided origin string, isTrusted alone, or the presence of a postMessage function is not origin validation. Legacy XDM and React Native bridges remain eligible through their existing adapters. A trusted host-provided WalletInterface can be supplied directly with no discovery at all.

5. Selection policy and neutral presentation

Applications select automatic, ask, or explicit behavior when instantiating the controller/chooser integration. The default is automatic. An application MAY declare an embedded provider as its preferred/default provider; it MUST do so explicitly through configuration or a trusted host binding, never by recognizing a favored vendor name.

Apply the following order after adapter coverage is known:

ConditionRequired behavior
Explicit object/provider suppliedBind that provider or return its error; do not substitute another.
askPresent the chooser even for one candidate; await selection.
Account-change invalidation satisfying section 6's same-provider rebinding conditionsMAY establish a fresh binding to that provider without a chooser, subject to application selection policy.
Valid remembered choice, verified as the same providerReuse it without a chooser. Other newly discovered wallets do not disrupt it.
Remembered choice missing, changed, conflicting, or unverifiableAsk again; do not fall through to a different single candidate. Preserve the old preference until replaced or forgotten.
App-configured embedded default availableSelect it without a chooser; other adapters may be deferred until Change wallet.
Exactly one candidate and discovery coverage complete within the configured scopeSelect it without a chooser.
Multiple candidates, or unresolved coverage that could change an automatic choicePresent the chooser with the coverage state.
No candidates, discovery completeOffer phone pairing and application-provided onboarding/install paths.
No candidates and blocked/partial discoveryExplain access/coverage, offer retry, phone pairing and onboarding; do not assert no wallet is installed.
Choice required but no UI adapterReturn SELECTION_REQUIRED; include candidates and coverage.

“Complete” is completion of the configured discovery scope, not proof that no other wallet exists anywhere. An explicitly configured embedded-only initial scope is valid and normally gives a single automatic default. An expanded chooser MUST include all usable candidates returned by all configured adapters, including extensions, localhost, window.CWI, XDM, React Native, and established mobile connections. It MUST expose unavailable/blocked adapters as coverage information. Application/runtime restrictions must be disclosed, not disguised as vendor incompatibility.

Phone pairing and wallet installation are actions, not fictitious discovered wallets. Their availability alone MUST NOT force a chooser popup when an existing desktop or embedded wallet can be automatically selected. Applications MUST have an API to request the chooser/phone path explicitly. A graphical integration MUST provide a reachable Change wallet/Use phone control, even when initial selection is automatic; an app-defined settings or connection menu satisfies this requirement.

When automatic selection chooses the sole discovered provider and it is not the application's configured embedded default, the graphical integration MUST immediately identify the selected provider and show a Change wallet control in the connection surface. No additional confirmation or chooser is required merely to show this state.

Chooser rows MUST use equal typography, icon area, interaction affordances, and information density. No sponsorship, vendor relationship, port number, response speed, or adapter priority may influence wallet ordering, default selection, prominence, or omission. The default order is ascending Unicode code-point order of name, then instanceLabel, then providerId; UI MAY instead preserve a user-set order. A label identifying the user's current/remembered choice or the application's embedded default is permitted factual state, not an endorsement. Different transport aliases of one proven provider are one row with inspectable connection details.

Lock state is not absence. The controller MUST not silently move to another wallet because the selected wallet is locked, denied a permission, needs funding, or is temporarily offline. Wallet authentication and method permission remain wallet-controlled steps after selection. The chooser MUST support keyboard access, visible focus, accessible names, screen-reader status updates, cancel/back, and an equivalent non-QR path for users who cannot scan a code. Modal styling is not prescribed.

Selection memory is scoped to the full application context and provider proof. The default is to remember a successful selection for future visits when local storage is available; the UI MUST expose the remember setting and a forget action. Store only the provider identifier, continuity key, instance ID, selected mode, and necessary adapter-local locator. Do not store wallet keys, permissions, balances, account identity, pairing secrets, or a full discovered-wallet inventory. A changed account/identity notification invalidates the binding. A fresh explicit selection is required unless section 6's same-provider rebinding exception applies; remembered choice, an embedded default, or a lone candidate MUST NOT bypass that requirement. When a legacy substrate cannot signal account changes, the integration MUST disclose that limitation; it MUST never claim to verify account continuity through getVersion.

5.1 User-visible connection states

Graphical integrations MUST expose understandable status and reachable actions for the applicable states below. These are presentation obligations, not new wire states or BRC-100 methods. An application MAY use a modal, panel, page, or native surface and its own wording and branding. Equivalent presentations MUST preserve section 5's equal treatment, accessibility, cancellation, and selection policy.

StateRequired presentation and actions
DiscoveringIdentify that discovery is in progress and offer cancel. An empty pending snapshot MUST NOT be described as a completed search with no wallet installed.
Discovery blocked or partialExplain the access/coverage limitation, distinguish it from wallet absence, and offer retry, phone pairing and application-supplied onboarding/install paths. Expose the coverage controls required by section 4.
Discovery complete, no candidatesShow an explicit empty state with phone pairing and application-supplied onboarding/install paths. Installation and pairing actions MUST remain distinguishable from discovered-provider rows.
Choice requiredShow eligible providers and coverage, with cancel/back and the remember/forget controls required by section 5. A selected/default label MUST NOT become preferential styling or commercial ordering.
ConnectedIdentify the bound provider and make Change wallet/Use phone and disconnect reachable. Connection MUST NOT be presented as account authentication or approval of a wallet operation.
Locked/authentication required or waiting for wallet approvalExplain the wallet-controlled next step and distinguish it from discovery or installation. Preserve the and section 6's cancellation/delivery rules; no fixed permission-response deadline is introduced.
Suspended, disconnected or closedDistinguish recoverable transport suspension with retained session state from terminal closure requiring a new binding/pairing. Offer the applicable reconnect, fresh connection, or disconnect action without replaying wallet operations or promising that prior effects were undone.

5.2 Selected-account labels and bundled wallets

When adopting BRC-205, an application SHOULD obtain account-label from getVersion({}) on the selected connection when presenting account-continuation UI. It MUST NOT query all candidates for labels, use a label to select/rank a provider, replace descriptor name or instanceLabel with it, merge providers on it, or use it as a remembered-choice key. The label MUST remain distinguishable from provider provenance and application-owned action text. It MUST follow BRC-205's inert rendering, freshness, privacy and generic-fallback rules; missing/invalid metadata MUST NOT block wallet use. A label rename alone MUST NOT invalidate a binding or imply account replacement; an unchanged label MUST NOT conceal a real account change under section 6.

A wallet bundled with an application MAY be that application's explicitly configured embedded default. Other applications can discover it only if it exposes a supported, reachable substrate; an in-process embedded object alone is not an externally discoverable wallet service. The bundled application's default status MUST NOT transfer to unrelated applications. Those applications apply their own section 5 selection policy, show the provider's product name during discovery, and obtain any account label only in the selected context. Provider continuity and selection memories remain application-scoped; neither a shared display label nor installation by another application grants authentication or permission.

6. Binding, switching, and error semantics

An application MAY hold several explicitly created connections concurrently. Each has its own immutable provider and epoch. No ambient “active wallet” may retarget existing connections. Transport aliases MAY be selected before binding using app-configured runtime suitability; once requests have been dispatched, changing an alias requires a new epoch and the switching rules below. Selection does not migrate keys, funds, baskets, certificates, account sessions, or action references.

A new-profile wallet MUST bind an epoch to its selected internal account context without exposing an account identity key in discovery. Changing that context invalidates the epoch before another call can execute. In-flight work either completes against the original context or fails with its true delivery certainty; it MUST NOT move to the new context. Object/XDM/native adapters notify closure as specified above; HTTP invalidates the binding so the next request returns 412; mobile sends an encrypted PROVIDER_CHANGED session FAILURE with cause: 'account-changed' and closes. Legacy substrates without such notifications/bindings cannot claim this guarantee.

After a binding closes with reason account-changed, or an authenticated mobile session FAILURE reports PROVIDER_CHANGED with cause: 'account-changed', the controller MAY establish a new binding to the same provider without a chooser. This exception requires that the provider is still present, re-proves the exact same (instanceId, continuityKey) with a fresh section 3 challenge for the same application context, and has no conflicting identity metadata. It remains subject to application selection policy: ask or an application requirement for explicit re-selection still requires selection. A product name, cached descriptor, remembered preference, unchanged continuity key alone, generic HTTP 412, or PROVIDER_CHANGED without a verified account-change cause is insufficient. If the provider is missing, changed, conflicting, or cannot re-prove continuity, the controller MUST require fresh explicit selection and MUST NOT substitute another provider or fall through to an automatic default.

On account-change invalidation, the controller MUST notify the application that the account changed and the old epoch closed, whether or not a replacement is established or a chooser is shown. This notification MUST precede admitting calls on any replacement binding. The replacement MUST have a fresh epoch and be exposed to the application as a new BoundConnection; existing connection objects MUST remain bound to their closed epoch. Old queued or in-flight calls MUST NOT be transferred, retried, or replayed onto the new binding, including calls known to be not-sent. New calls may begin only against the separately established binding, with action references and wrapper state isolated as specified below. Provider continuity does not establish account authentication or grant wallet permissions. For mobile, omitting the provider chooser does not omit pairing: sections 8–10 still require a fresh invitation, cryptographic session, continuity proof and explicit endpoint approvals; the closed session cannot be reopened.

On Change wallet, the controller MUST stop admitting new calls to the old connection and show pending work. The user/application can either wait for all calls to settle or explicitly abandon the connection. Waiting MUST NOT impose a permission deadline. Abandoning rejects unresolved application promises with delivery certainty as below, closes the old connection, and MUST NOT claim that wallet-side effects were undone. Where a BRC-100 action reference is known, an application MAY explicitly call abortAction on the old wallet; there is no invented universal cancellation method. New work begins only on a separately resolved binding.

signAction/abortAction references, in-progress funding flows, and wrapper state MUST stay with the wallet/epoch that produced them. A wrapper MUST be recreated or explicitly reset on switching; changing the base of a live wrapper with pending work is prohibited. An adapter MUST NOT retry a wallet method automatically after uncertain delivery, reconnect it to another wallet, or replay a transaction because the chooser reopened. Re-delivery of identical encrypted transport records within one live mobile session is not re-invocation; section 10 prevents duplicate dispatch.

Connection-layer errors are distinct from wallet errors and MUST retain the following fields:

type ConnectionError = {
  domain: 'brc185'
  code: string
  message: string
  delivery: 'not-sent' | 'unknown'
  epoch?: string
  cause?: string
}

Codes are CONFIGURATION_ERROR, SELECTION_REQUIRED, USER_CANCELLED, DISCOVERY_BLOCKED, PROVIDER_CHANGED, PAIRING_EXPIRED, PAIRING_REJECTED, PROTOCOL_ERROR, RESOURCE_LIMIT, DISCONNECTED, and CONNECTION_CLOSED. Before wallet dispatch, delivery is not-sent. If dispatch may have occurred and no authoritative wallet result exists, it is unknown. A network timeout cannot prove no transaction was created. An explicit BRC-100 error remains the original wallet error, not a connection error. Libraries MAY add a cause without replacing these fields. For a mobile account-context change, the wallet MUST include the exact string cause: 'account-changed' in its encrypted session FAILURE; the controller MUST NOT infer this cause from a relay event, error message, or permission/authentication failure. Other causes do not authorize the same-provider rebinding exception.

Applications MUST be able to distinguish discovery, choosing, pairing, connected, locked/authentication required, waiting for wallet approval, suspended, disconnected, and closed. isAuthenticated:false MUST NOT be converted into “install a wallet.” Permission requests follow BRC-219: the wallet presents/maintains the prompt or returns an error; connected pending prompts have no fixed permission timeout. Cancellation of a chooser or unapproved pairing sends no queued BRC-100 call.

7. Wrappers, onboarding, and commercial composition

The controller's output MUST be wrappable as an ordinary WalletInterface. Applications MAY place onboarding, funding, branded presentation, developer/platform fees, or other commercial policies above it. This BRC neither mandates nor removes those policies. A transport/controller MUST NOT edit transaction outputs, suppress a wrapper's fee, insert its own fee, or reroute funding. Wallet permissions remain authoritative for any resulting action.

The application MUST be able to replace the default no-wallet/onboarding UI, supply install and same-device handoff routes, and receive typed connection errors. One component must own a given connection/onboarding interaction: a controller and wrapper MUST NOT each open a competing missing-wallet modal for the same resolution. Integration documentation MUST explain how to disable or delegate overlapping UI while retaining funding and commercial behavior. A user-cancelled chooser MUST not be transformed into an indefinite missing-wallet wait.

Discovery and chooser entry MUST NOT require a vendor account, paid directory listing, referral agreement, sponsored auction, or approval by a central catalog. A separate onboarding catalog MAY contain disclosed commercial offers. It MUST be distinguishable from the neutral list of discovered wallets and MUST NOT hide, rank, or preselect those wallets because of payment. A wrapper cannot claim a conforming chooser while using commercial ranking inside it.

For example, a connection can be resolved once and supplied to new BabbageGo(connection.wallet, options). Funding retries after a definite insufficient-funds result remain wrapper/application decisions against that same binding. They MUST NOT become retries after ambiguous transport failure or against a replacement wallet. This preserves the composition boundary without standardizing a vendor's fee amounts, business model, or UI.

8. Mobile pairing and trust boundaries

8.1 Endpoints and relay

Mobile connections MUST use authenticated end-to-end encryption. HTTPS/WSS to a relay alone does not satisfy this requirement. The mandatory profile is Noise_NNpsk0_25519_ChaChaPoly_SHA256, using Noise revision 34, X25519, ChaCha20-Poly1305, and SHA-256. Implementations MUST use maintained implementations of these primitives/protocol processing, cryptographically secure randomness, and reject an all-zero X25519 shared secret. Implementations MUST NOT invent alternate , key derivation, or cipher constructions under version 1. A future cryptographic profile requires a separately specified version; unsupported versions fail closed, with no plaintext fallback.

The application is the Noise initiator; the wallet is the responder. The invitation carries a random 256-bit preshared key (PSK), never a , spending key, account identity key, or password. Both sides generate fresh ephemeral X25519 keys for every invitation. The PSK and ephemeral private keys MUST be erased after a successful handshake or abandonment; only the resulting in-memory session state remains. In managed runtimes implementations MUST remove references and overwrite mutable buffers where supported, without claiming guaranteed erasure of garbage-collected copies. The displayed QR/link MUST be removed at that point. Sessions MUST NOT resume after a process loses its cryptographic state.

Two deployment modes use the same wire protocol:

modeEncryption endpoint and disclosure
clientDefault. The actual application client, including a browser, encrypts/decrypts. A separately deployed application backend and relay receive no BRC-100 plaintext or session keys.
backendExplicit configuration. The application's backend is the encryption endpoint and therefore can read the calls/results. Both connection UIs MUST say that the named application backend has this access before pairing is approved. Client-to-backend traffic MUST use authenticated HTTPS and application-session isolation.

backend MUST NOT be selected automatically as a workaround for unavailable client cryptography. The mode is authenticated in the handshake. A relay is a replaceable ciphertext router and MUST NOT receive the PSK, ephemeral private keys, transport keys, decrypted payloads, or account keys in either mode. A backend that terminates the wallet encryption is an application endpoint, not a ciphertext-only relay. Self-hosted and vendor-operated relays use the same contract; no relay operator is privileged by this BRC.

This profile protects payloads from the relay and network intermediaries and provides forward secrecy through fresh ephemeral DH. It does not hide connection timing, approximate traffic volume, IP addresses, or the relay topic. It cannot protect plaintext from either authorized endpoint or compromised code running there. The QR/link is a short-lived bearer invitation: anyone who obtains it can attempt pairing, so explicit endpoint confirmation remains required.

8.2 Invitation encoding

The vendor-neutral URI is exactly:

brc-wallet://pair#<base64url(JCS(offer))>

There is no query, username, port, trailing path, or alternate host in this URI. Fragment transport avoids placing the invitation in an HTTP request or Referer. An offer has exactly the following fields, all required:

FieldValue
versionInteger 1.
topic16 independently random bytes, base64url. Single invitation/session identifier.
relayAbsolute wss: URL of the relay WebSocket endpoint; no credentials, query, or fragment; at most 2048 UTF-8 bytes.
walletToken32 random bytes, base64url; authorizes relay attachment as wallet, not wallet operations. Distinct from PSK and client token.
psk32 independently random bytes, base64url, produced by the application encryption endpoint.
challenge32 independently random bytes, base64url, for the wallet descriptor proof.
originCanonical HTTPS application origin, or a canonical HTTP loopback origin for explicit local development.
originatorASCII hostname of origin, 1–250 bytes.
modeExactly client or backend.
expiresInteger Unix seconds, no later than 300 seconds after invitation creation.
returnUrlnull, or an absolute HTTPS URL with the same origin as origin, no credentials or fragment, at most 2048 UTF-8 bytes.
returnState16 independently random bytes, base64url; for same-device return correlation, even when returnUrl is null.

The decoded offer MUST be at most 4096 bytes. Encoders MUST ensure the complete encoded URI fits their selected QR capacity and MUST reject oversize offers rather than truncate fields or lower cryptographic entropy. Wallets MUST accept the full 4096-byte decoded limit through paste/link intake even when a particular camera/QR renderer cannot carry it. A QR renderer MUST expose the equivalent link/intake alternative.

Version 1 mobile origins are web origins. A native or terminal application can use an application-controlled HTTPS origin and MUST explain its non-browser context; this does not prove the native program owns the domain. It MUST NOT impersonate another application's origin. Non-web origin authentication mechanisms can be specified separately without changing BRC-100.

Parsers MUST reject offers whose expiry is at or before their current time and MUST reject offers more than 330 seconds in the future (the extra 30 seconds accommodates a slightly slow receiving clock). The originating endpoint's 300-second deadline remains authoritative; clock disagreement can fail pairing but cannot extend it. The application MUST destroy an unused invitation at its expiry and generate new topic, PSK, tokens, challenge, and state for Refresh. Expiry applies to establishing a new session, not to an approved session's pending permission requests. The application MUST permit only one completed wallet pairing per invitation and MUST invalidate the invitation after approval or cancellation.

Offers MUST be produced from the actual application context, not a URL supplied by an untrusted frame or the relay. QR data, tokens, and fragments MUST NOT enter logs, analytics, crash reports, public clipboard history services, or third-party URL shorteners. A pairing page MUST not load third-party code with access to the fragment and MUST remove the fragment from visible browser history as soon as it has safely handed the offer to the local pairing component. Only explicitly user-initiated copying is permitted.

8.3 Origin and user confirmation

Before any wallet method can be sent, both endpoints MUST complete this sequence:

  1. Validate the offer, establish the Noise handshake, and compute the confirmation code below. Handshake message payloads are empty. No BRC-100 request is a handshake payload.
  2. The wallet sends encrypted HELLO, containing the section 3 proof for offer.origin and offer.challenge. The application verifies it and displays the wallet name, instance label, and provenance, including whether it matches a remembered continuity key.
  3. Both surfaces display the application origin and the same confirmation code. In backend mode both also display the backend-access disclosure. The wallet MUST identify the origin as a pairing claim, not an independently verified browser attestation. The user MUST confirm that it is the intended application and compare the code across the actual application and wallet surfaces. For same-device use, the UI MUST support switching between those surfaces to compare it.
  4. Each endpoint requires its user's explicit confirmation and sends encrypted APPROVE. Once both local and peer approvals exist, the connection becomes usable. Until then, a requester MUST NOT send and a wallet MUST NOT dispatch any BRC-100 method. Reject/back sends REJECT and destroys the session. A relay event or URL callback never substitutes for encrypted approval.

Let h be the final Noise handshake hash. Compute SHA-256 over UTF8("BRC-185 confirmation v1\n") || h; interpret its first four bytes as an unsigned big-endian integer; take its value modulo 100000000 and display eight zero-padded decimal digits, grouped 1234 5678. A refresh/retry MUST generate a new invitation, not repeatedly guess confirmation codes within one invitation. The code is an authenticated-channel comparison aid, not a password or wallet identity.

An origin claim is bound against modification by the relay through the prologue, but a malicious QR issuer can claim a false origin. The code comparison and user's verification of the actual application surface are mandatory; branding or an unverified origin label MUST NOT be represented as cryptographic website ownership proof. In a browser the controller MUST source the claim from the real document origin. Permissions MUST bind to the confirmed originator and MUST never be silently reused for another origin.

Wallets MUST accept the vendor-neutral offer through an in-wallet scan or paste/intake route. They SHOULD also expose OS-verified HTTPS app/universal links. After the user chooses a wallet, the application MAY wrap the same offer as:

https://<wallet-controlled-host>/<registered-path>#brc185=<base64url(JCS(offer))>

The wallet link endpoint is supplied by an application-configured adapter or an explicitly selected onboarding entry. The application MUST NOT follow a relay-supplied redirect to choose a wallet. The HTTPS route MUST use the platform's verified app-link association when opening a native wallet; a generic custom scheme MAY be offered as a disclosed fallback, never treated as authenticated app identity. Neither route changes the offer, encryption, or confirmation requirements. An OS's own app chooser is outside this BRC's ordering control; an application-owned list is subject to section 5.

After approval, rejection, or cancellation the wallet MAY open returnUrl after user action, appending brc185_state=<returnState> and brc185_status=connected|rejected|cancelled using normal URL query encoding. Existing occurrences of those two reserved parameters make an offer invalid. The wallet MUST NOT include tokens, keys, signatures, BRC-100 results, or permission grants in the return URL. The application MUST verify state against its locally outstanding invitation and take authoritative connection status only from the encrypted channel. It MUST consume the state once. A return link MUST NOT initiate/replay a wallet operation. A failed return MUST leave a visible manual Return to app path.

8.5 Pairing presentation and recovery

Graphical pairing integrations MUST distinguish waiting for wallet intake, establishing the encrypted channel, awaiting endpoint confirmations, and connected status. They MUST make cancel/back reachable before approval and explain that both the application and wallet surfaces require confirmation. A relay peer-presence event, successful handoff, or one endpoint's approval MUST NOT be displayed as a completed connection.

While offering intake, the application MUST communicate the invitation's expiry and provide the equivalent non-QR path required by section 8.2. After the handshake, it removes the invitation artwork/link as required by section 8.1 and presents the origin/code comparison instead. Until both approvals establish the session, section 8.2's deadline still applies. On expiry it MUST disable the old attempt, explain the expiry, and offer explicit Refresh or cancel. Refresh MUST terminate the prior unapproved attempt and create fresh invitation material; codes and approvals MUST NOT carry into the replacement. Rejection, cancellation and process/state loss MUST have distinguishable recovery guidance and MUST NOT release queued methods onto another connection.

Same-device UI MUST explain how to switch between the actual application and wallet surfaces to compare the code, and provide the section 8.4 manual return path when automatic return fails. Ordinary surface switching with retained process state follows sections 9–10's suspension/reconnection rules, not automatic re-pairing. Once connected, the UI MUST distinguish wallet authentication and operation permission prompts from pairing confirmation, and MUST NOT apply the invitation expiry to those decisions. Active-connection and revocation controls remain required by section 10.

9. Relay wire profile

9.1 Allocation and attachment

A version 1 relay exposes POST /brc185/v1/sessions over HTTPS. Request JSON is {version:1, topic, expires}. The relay MUST atomically reject an existing topic with HTTP 409 and expired/invalid requests with HTTP 400. On HTTP 201 it returns {version:1, relay, clientToken, walletToken}; tokens are distinct random 32-byte base64url values. The application validates fields and MUST refuse any relay URL that violates its configured relay-origin policy. The relay MUST NOT request the PSK or complete offer. Authentication, quotas, payment, and abuse controls for allocation MAY be deployment-specific; they cannot alter session semantics. No secret is transmitted in a URL query.

The WebSocket uses subprotocol brc185.v1. Its first message is UTF-8 JSON {version:1, type:"attach", topic, role, token}, with role client or wallet. The server responds {version:1,type:"attached",peerPresent:boolean}. It subsequently sends {version:1,type:"peer",present:boolean} on peer attachment changes. Invalid attachment closes with WebSocket code 1008. Expired, unapproved sessions cannot accept a first wallet attachment. At most one socket per role can be active; a valid replacement attachment closes that role's previous socket before accepting frames from the new socket. Client implementations MUST serialize their own reconnects.

The relay routes each binary message unchanged to the opposite role, in order, and never echoes it to the sender. It MUST NOT reinterpret the binary contents. When the other role is absent it MAY discard messages; endpoint replay, not relay storage, provides continuity. It MUST make peer presence changes visible. Text after attachment is restricted to the server control messages above and client {version:1,type:"close"}; unexpected text fails the session. Close erases tokens and ends the relay session. Either endpoint can close; this is transport closure, not proof of wallet-side rollback.

To preserve a successfully established session beyond invitation expiry, the client sends {version:1,type:"established"} after both encrypted approvals. This is the one additional permitted client text message. It MUST be sent before the invitation expires. The relay then accepts reattachments with the existing role tokens until explicit closure or actual resource/service failure; it MUST NOT impose a permission-response deadline. This relay notification is not authorization: endpoints still require encrypted approvals, and a malicious relay cannot grant them. Service policy MAY retire an idle session only when both peers have been absent for at least 24 hours, reporting it as disconnection on a subsequent attachment.

TLS certificate validation is mandatory. Redirects during allocation or attachment MUST NOT be followed automatically. A deployment MAY require a user-approved relay account or fee, but that is a relay service choice, not wallet-provider certification. Logs MUST omit tokens and payloads. The relay MUST enforce allocation/attachment resource limits and return a visible failure; none of these controls establishes trust in ciphertext contents.

9.2 Handshake and transport records

The Noise prologue is:

UTF8("BRC-185 mobile v1\n") || JCS(publicOffer)

publicOffer is the complete offer with only psk omitted; the PSK is supplied separately to the Noise psk0 input. Both sides MUST use identical canonical bytes. Neither the prologue nor its fields need to be sent to the relay. The handshake starts after both roles are attached. The first binary message is byte 0x00 followed by the initiator's Noise handshake message; the second is 0x01 followed by the responder's. With this pattern, 32-byte public keys, and empty payloads, each message is exactly 49 bytes including its leading type byte. Unexpected direction, extra handshake payloads, failed authentication, or malformed lengths abort pairing. Each side MUST cache its exact sent and received handshake bytes for the session. On reconnect before approval, it retransmits any already-generated handshake message verbatim; a byte-identical previously processed handshake message is a duplicate, not another Noise state-machine step. A responder receiving the duplicate first message retransmits its cached second message. A different message in an already processed handshake position is fatal. Retained handshake state can survive socket loss until invitation expiry; it MUST never be restarted with reused ephemeral keys. Loss of that state requires a fresh invitation.

After Noise Split, the initiator's first cipher state sends client-to-wallet and its second receives wallet-to-client; the responder uses the inverse directions. Each binary transport record is:

0x02 || sequence:uint64be || noiseCiphertext

Each direction starts sequence 0. Sequence MUST equal that direction's Noise cipher nonce. Additional authenticated data is UTF8("BRC-185 record v1\n") || topicBytes || directionByte || sequenceBytes, where direction is 0x00 client-to-wallet and 0x01 wallet-to-client. Use the Noise cipher state's normal EncryptWithAd / DecryptWithAd and monotonic nonce progression. Ciphertext is at most 65535 bytes, including its 16-byte authentication tag. Application plaintext is at most 65519 bytes. The nine-byte outer header is not part of that ciphertext limit. At 2^31 records in either direction, endpoints MUST stop admitting new requests and drain pending work before establishing a fresh approved session. They MUST never generate or accept a record with sequence 2^32 or greater; exhausting that remaining drain allowance is a resource failure with appropriate delivery uncertainty, never a nonce reset under an existing key.

Record plaintext is:

kind:uint8 || requestId:16bytes || total:uint32be || offset:uint32be || payload

The header is 25 bytes; each payload fragment is at most 65494 bytes. total counts the entire unfragmented payload; offset is its zero-based byte position. Fragments for a given (kind,requestId) MUST be contiguous, start at offset 0, preserve total, and end exactly at total. Different requests MAY interleave. Empty payloads use one fragment with total 0 and offset 0. Reassembly MUST finish and validation succeed before a wallet method is dispatched. Partial requests cannot execute.

Direction, ID, and payload
0x01 ACKEither; zero ID; exactly 8 payload bytes containing highest contiguous authenticated sequence received, uint64be.
0x02 HELLOWallet to client; zero ID; JCS descriptor proof, at most 16384 bytes.
0x03 APPROVEEither; zero ID; empty.
0x04 REJECTEither; zero ID; empty.
0x05 CLOSEEither; zero ID; empty.
0x10 REQUESTClient to wallet; new random nonzero 16-byte ID; complete BRC-100 binary request including method call code, originator frame, and parameters.
0x11 RESPONSEWallet to client; matching ID; complete BRC-100 binary response including success/error code and existing response/error serialization.
0x12 FAILUREEither; matching request ID, or zero for a session failure; JCS section 6 connection error, at most 4096 bytes.

Controls other than HELLO MUST fit in one record with offset 0. Every request and response payload MUST be at most 67,108,864 bytes (64 MiB). Endpoints MUST support this limit for a single message, at least 16 outstanding dispatched calls, and at least 64 MiB of aggregate in-progress reassembly; greater aggregate use MAY return RESOURCE_LIMIT. This finite transport resource limit is not permission to omit methods. Oversize requests fail locally not-sent; oversize responses produce RESOURCE_LIMIT with unknown delivery because wallet effects may already exist. Implementations MUST bound length arithmetic and allocation before accepting fragments and never silently truncate a or other byte array.

The receiver MUST compare the BRC-100 originator frame with the approved offer's originator and reject mismatches before wallet dispatch. It MUST pass that originator to the wallet and MUST NOT substitute the relay hostname. Method codes, serialization, and results are exactly BRC-100, with no method-name whitelist or rewritten fields. Unknown connection kinds or invalid control ordering terminate the connection with PROTOCOL_ERROR; an existing wallet error is returned as RESPONSE.

The endpoint state machine is fixed:

StateAccepted transition/control
offeredValidate offer and attach; expiry/reject goes to closed. No wallet RPC.
handshakeExactly the two directional handshake messages; success goes to confirming; authentication/state failure goes to closed; socket loss with retained state goes to suspended.
confirmingWallet sends exactly one HELLO before its APPROVE. Client accepts/verifies HELLO before its own APPROVE. Each side sends at most one APPROVE after local confirmation. ACK, REJECT, CLOSE, or fatal FAILURE are permitted; REQUEST/RESPONSE are prohibited.
connectedBoth local and peer approvals exist. REQUEST/RESPONSE, ACK, FAILURE, and CLOSE are permitted. A newly encrypted HELLO, APPROVE, or REJECT is a protocol error.
suspendedPrior handshake/confirmation/connection state survives but a socket/peer is absent. Reattach and replay cached handshake bytes and/or encrypted records to return to that prior state; no newly admitted application requests until connected. The invitation deadline still applies until approval.
closedTerminal. No frame can reopen it; a new invitation is required.

A socket loss while the user switches between same-phone surfaces MUST NOT by itself discard a retained handshake or confirmation. Before approval, the invitation expiry remains in force; after approval, no permission deadline is added. Duplicate cached handshake messages and duplicate ciphertext are handled as specified above, not as new controls. A terminal state change completes pending application promises according to section 6.

10. Delivery, mobile lifecycle, and revocation

Within a live session, each endpoint retains its send/receive cipher states, next sequence, unacknowledged ciphertext records, request dispatch table, and unfinished responses. Keys and role tokens MUST remain in endpoint memory or equivalently protected live-process storage and MUST NOT enter ordinary browser persistent storage. Loss of any state required to avoid nonce reuse or duplicate dispatch terminates the session. A new connection requires new pairing material; it MUST NOT replay old requests.

The receiver processes only its expected next sequence. Lower sequences are duplicates and MUST be discarded without decryption, nonce advancement, or wallet dispatch. A higher sequence is a gap: the receiver suspends application delivery, closes that socket, and reconnects to request peer retransmission through the attachment/presence transition. It MUST NOT decrypt out of order or invent missing results. Authentication failure on the expected sequence is fatal. Relays can disrupt availability; endpoints MUST never trade authenticity for reconnection.

After every successfully received non-ACK record, the receiver MUST enqueue an encrypted ACK for the highest contiguous received sequence, coalescing adjacent acknowledgements when possible. ACK records do not trigger ACKs by themselves. An ACK can acknowledge earlier ACK records. Only an authenticated peer ACK can release stored outbound ciphertext; relay presence, write completion, and attach responses cannot. ACK values beyond the largest sequence sent are protocol errors; older ACK values never move the acknowledged position backward. An implementation MUST retain at least 8 MiB of unacknowledged ciphertext; at its configured bound it applies backpressure to new data and continues processing incoming records/ACKs. It MUST reserve an additional 64 KiB for ACK/control output so two full send buffers cannot deadlock acknowledgements. It MUST NOT drop unacknowledged records to make space. Exhausted control capacity is an explicit resource failure, not permission to reuse a nonce.

On either role reconnecting, both endpoints retransmit their retained, unacknowledged records in original sequence order before sending new records. These MUST be the exact original bytes: do not re-encrypt, reset counters, rebuild requests, or dispatch an ID twice. After that replay batch, each endpoint that has received at least one peer record MUST send a fresh ACK for its current contiguous receive position, even if the batch contained only duplicates. This recovers a lost ACK without an ACK-to-ACK response loop. Reconnect replay happens only after attachment/peer-presence transitions, not on every ordinary ACK.

A terminal RESPONSE/FAILURE ID cannot be reused; the wallet retains dispatched IDs for the session even after acknowledging the response. Once a REQUEST has completed reassembly, that ID in a new REQUEST is PROTOCOL_ERROR, not a retry; correctly ordered continuation fragments of its original reassembly are not new requests. A second terminal response for one ID is also a protocol error. Response ciphertext retransmission is handled by sequence deduplication. These rules provide at-most-once dispatch within the surviving session, not durable exactly-once transactions after process loss. Implementations MAY bound session dispatch history, but MUST stop admitting new requests and drain/close before discarding IDs; they cannot silently forget them while keeping the session usable.

A request-specific FAILURE ends only its matching request. If failure occurs during reassembly, the receiver MUST discard subsequent payload fragments for that ID while continuing authenticated sequence processing through the declared total; it MUST never dispatch the partial request. A sender receiving that failure MUST still send any already encrypted records in sequence and MAY omit only fragments not yet encrypted. The receiver such an abandoned reassembly terminal on receiving the failure's authenticated ACK from the sender or on session closure. A zero-ID FAILURE is fatal to the session. Any failure that prevents reliable sequence/state retention MUST terminate the whole session and report possibly dispatched calls as unknown.

An account change sends a zero-ID PROVIDER_CHANGED FAILURE with cause: 'account-changed' and permanently closes that session. Section 6 permits selecting the same freshly proven provider for a new epoch without a chooser; it does not permit socket reattachment, nonce/state reuse, or old-request replay from the closed session. Fresh pairing and both endpoint approvals remain required.

Ordinary backgrounding, screen lock, suspension, and foreground return MUST preserve the session and pending prompt while the process/state remain alive. A reconnecting UI shows suspended, retains the original request, and offers cancel/disconnect. It MUST NOT reissue the method or start a second permission prompt merely because the user returned through a link. There is no elapsed-time limit on a live permission decision. Implementations MUST honor BRC-219 prompt visibility and restoration. Actual process/state loss, unrecoverable relay failure, explicit disconnect, and service closure end the connection with unknown delivery for possibly dispatched calls. They are not permission timeouts.

Users MUST be able to see and revoke active mobile connections in the wallet and disconnect in the application. Revocation sends encrypted CLOSE when possible, closes relay attachment, erases keys/tokens and unresolved invitation material, and blocks future requests under that session. Receiving CLOSE has the same effect. Revoking transport access does not delete wallet data or retroactively revoke completed operations; wallet permission persistence is separately controlled by BRC-116. A future mobile connection requires a new invitation and explicit approval, even when a wallet preference is remembered.

11. Complete interface and conformance evidence

The method set is fixed by BRC-100. The following table is a coverage checklist, not a negotiable :

CodesRequired methods
1–7createAction, signAction, abortAction, listActions, internalizeAction, listOutputs, relinquishOutput
8–10getPublicKey, revealCounterpartyKeyLinkage, revealSpecificKeyLinkage
11–16encrypt, decrypt, createHmac, verifyHmac, createSignature, verifySignature
17–22acquireCertificate, listCertificates, proveCertificate, relinquishCertificate, discoverByIdentityKey, discoverByAttributes
23–28isAuthenticated, waitForAuthentication, getHeight, getHeaderForHeight, getNetwork, getVersion

A conforming implementation MUST publish a versioned test report identifying wallet, controller, adapter, runtime, and test fixture versions. A declaration alone does not establish conformance. The suite MUST include:

AreaRequired
Full interfaceAll 28 methods over each advertised substrate; valid results and wallet errors; preservation of optional fields, byte arrays, originator, and large/fragmented payloads. Method stubs fail.
Additive metadataBRC-205 consumers accept base-only and extended strings within its bounds without opt-in or support discovery; unknown attributes ignored and malformed attributes handled under BRC-205; scheme advertisements never reduce the core method set or grant permission; unsupported reserved schemes remain rejected.
Icon metadataOptional HTTPS/UHRP/CHIRP references in brc185-icon and signed descriptors; encoding and identifier validation; bounds/duplicate keys; safe image display; no fetch on announcement alone; generic fallback for absent/invalid/unavailable artwork; no effect on wallet selection, identity or epochs.
Independent implementationsTwo independently implemented endpoints interoperate; same-library loopback alone is insufficient.
PortsTwo wallets concurrently; occupied unrelated ports; gaps; bind races; HTTP-range overlap at 3321; IPv4/IPv6 consistency; ceiling exhaustion; default and extended discovery coverage.
Every adapter familyExtension announcements, legacy window.CWI, embedded object, XDM, React Native, localhost, mobile, and an independently registered custom adapter can enter the chooser without vendor logic.
ChoiceNo popup for single desktop or configured embedded default; sole automatically selected nondefault provider is immediately identified with Change wallet; explicit ask still works; multiple wallets require choice; remembered provider returns; missing/rotated/legacy provider asks again; no permission/funding-based substitution.
UX states and labelsPending empty discovery versus completed empty/blocked/partial states; reachable recovery/cancel controls; differently styled applications preserve neutral rows; labels queried only in the selected context; generic fallback and retired-response rejection; display rename does not replace a binding; bundled default does not become another application's default.
NeutralitySwapping vendor names, arrival order, port order, and sponsorship changes no prominence except the documented neutral sort; all eligible candidates remain accessible.
SwitchingMultiple explicit connections; pending prompt; action reference isolation; wrapper/funding state; abandon with uncertain delivery; no replay onto replacement wallet.
Account-change rebindingSame present provider freshly proves the exact /context: optional chooser-free new epoch and application notification; old epoch/connection stays closed; queued and in-flight calls, including not-sent calls, never cross or replay; action references and wrapper state stay isolated. Missing/changed/conflicting/unverified provider, wrong context, stale proof, generic failure, or ask policy requires explicit selection; mobile still requires fresh pairing and both approvals.
Mobile pairingDesktop QR to Android and iOS; same-phone verified link and manual return; paste intake; expired/altered offer; rejected comparison; rejected backend disclosure; cancellation before dispatch.
Pairing presentationIntake/handshake/confirmation/connected states; peer presence and one-sided approval never imply completion; expiry before both approvals; explicit refresh invalidates old invitation/session/code/approvals; non-QR route; comparison across same-phone surfaces; rejection/cancel/state-loss recovery sends no queued methods.
CryptographyOfficial primitive/Noise known-answer vectors; independent interoperability; altered prologue/mode/origin; wrong PSK; all-zero DH; corrupted record; replay; gaps; duplicate ID; sequence limits; no plaintext in relay capture.
Mobile continuityBackground/foreground, screen lock, socket replacement, ciphertext retransmission, ACK loss, reassembly backpressure, request approval after a long wait, process loss, relay outage, and revocation.
Compatibility/DXCore SDK import in browser/SSR/worker/Node without DOM access; no registration retains old behavior; explicit objects remain untouched; graphical/terminal/headless adapters; Babbage Go-style wrapper retains configured funding and fees without duplicate onboarding UI.
Privacy/originNo account probes during discovery; separate full-origin memories and continuity keys; wrong source/frame/origin rejected; metadata inert; no secrets in logs/return links; blocked local access accurately reported.

Wallet operations in conformance tests MUST use controlled fixtures/test environments, not involuntary user payments. Method conformance requires implemented behavior, not forcing a user to grant every permission. The initial release of the encrypted profile MUST undergo an independent protocol/implementation security review covering the complete pairing, identity, nonce, replay, origin, and lifecycle design. Passing primitive tests alone is insufficient to claim the entire connection system has been audited.

12. Compatibility and rollout requirements

This proposal adds transport/control protocols alongside existing ones. It MUST preserve BRC-100's mandatory core methods, method wire codes, argument/result shapes, ordinary operation semantics and security/authorization guarantees, as well as legacy BRC-5 routes and BRC-6/BRC-7 message meanings. Section 1 permits BRC-205's additive getVersion string-content format and future advertisements of explicitly selected reserved schemes; it does not authorize a reduced core interface or make metadata a permission grant. Legacy wallets remain usable through compatibility adapters, with limitations honestly identified. They MUST NOT be labeled BRC-185 conforming merely because one readiness call succeeds. Older strict 30-byte or base-only validators may reject extended strings and need updates; this compatibility caveat does not require coordinated adoption or a support-discovery exchange.

New desktop listeners SHOULD retain the default port when it is available, making a lone new wallet reachable by legacy clients. When it is occupied, ascending allocation makes the additional wallet available to new discovery clients. A legacy client that probes only the default port cannot see every new listener; documentation MUST say so. A new client MUST NOT send a legacy JSON request to a binary listener based only on its port number.

Existing mobile-relay protocols and branded URI schemes MAY coexist through explicit legacy adapters. They MUST NOT be silently accepted as the encrypted version 1 profile, and version 1 MUST NOT downgrade to them after an authentication failure. A relay method subset cannot be made conforming by advertising optional capabilities. Migration requires implementing complete carriage, preserving request/error semantics, and the new pairing security contract.

Implementation work MUST preserve the layering in section 2. Public packages SHOULD release independent adapters/controller/UI components and document compatible version combinations. Reference UI/framework packages MUST NOT become transitive dependencies of the core SDK. Wallet-specific integration can supply an adapter; applications and wrappers MUST NOT need vendor-specific branches to call BRC-100.

Design rationale and implementation guidance (non-normative)

Illustrative user journeys

These journeys explain the normative requirements above; screen names and layouts are illustrative. Existing implementation screens do not define this contract.

StageDesktop browser and phone walletBrowser and wallet on the same phone
StartUser chooses Use phone from the connection surface.User chooses Use phone and a supported wallet handoff/intake route.
IntakeApp offers a temporary QR and equivalent non-QR route, with expiry and cancel. Wallet scans or accepts the offer.App opens the explicitly selected wallet through a supported link, or the user uses manual intake. Handoff is not connection approval.
CompareAfter the handshake, the app identifies the wallet; both surfaces show the application origin, matching code, and any backend-access disclosure.User switches between the actual app and wallet surfaces to compare the same information. Retained session state survives ordinary surface switching.
ApproveUser explicitly approves on both surfaces before wallet requests can pass.Same approvals, then user-initiated return link or manual Return to app. A callback alone cannot establish connection or replay a request.
UseApp stays on the desktop; phone handles wallet authentication and operation permissions as needed, separately from pairing.App continues in the browser; wallet handles the same separate authentication/permission steps.
RecoverExpiry offers fresh pairing; rejection/cancel ends the attempt; suspension preserves retained state; state loss requires fresh pairing.Same rules, including visible manual return when automatic handoff back fails.

For a bundled-wallet example, Pocket Photos can onboard a user through its explicitly configured embedded default without opening a provider chooser. If the bundled wallet also exposes a supported external substrate, another browser application can discover its declared product name, for example "Pocket Photos Wallet". That application applies its own selection policy: complete discovery of that sole provider may connect automatically with visible provider identification and Change wallet, while multiple candidates require the neutral chooser unless another section 5 rule applies. After selection, a valid BRC-205 label can produce "Continue with your Pocket Photos Account"; absent metadata produces generic account copy. This does not authenticate the person or carry Pocket Photos' permissions into the new application. A later label of "Metanet Account" changes presentation, not provider identity or the bound account by itself. Phone-hosted wallets still require explicit pairing; an installed phone application is not automatically a discovered desktop provider.

Source findings and proposed changes

At the September 25, 2026 capture, the TS stack's WalletClient used fixed default endpoints and priority among successful readiness probes. It could accept a supplied WalletInterface, but it did not enumerate ascending ports or choose among all wallets. BSV Desktop listeners used fixed ports. The proposed controller and descriptor are new; they are not descriptions of shipped discovery behavior.

The existing ts-wallet-relay provides useful pairing, mobile prompt, and modal experience. Its application backend is an encryption endpoint, whereas its WebSocket relay routes encrypted traffic. Its typed proxied-method list was a subset of BRC-100 at the captured revision. The new client endpoint mode, full method carriage, URI, wire protocol, and security requirements therefore require implementation work; compatibility must be explicit. The BSV Browser pairing route delegates to the shared mobile wallet-toolbox pairing UI, providing another concrete integration point.

@babbage/go version 0.1.20, commit 448505904367b8990e5493f68c419bebf06b88c7, accepts WalletInterface in its constructor, defaults to WalletClient when none is supplied, handles wallet-unavailable UI, and wraps createAction with funding and monetization behavior. Its platform contribution and developer fee are distinct settings. Its existing modal/error handling can deliberately leave a promise pending; integration with typed selection cancellation needs specific attention. Its existing base and pending payment state make one wrapper per immutable connection the appropriate composition model. These are source observations, not execution tests or recommendations to change its commercial terms.

EIP-6963 supplies useful precedent for independent browser provider announcements without redefining the wallet API. WalletConnect's pairing specifications supply precedent for explicit pairing versions and expiry. This BRC does not adopt Ethereum methods, a WalletConnect network dependency, or a central wallet directory. Chrome's local-network access controls and Android/iOS app-link behavior are platform constraints that adapters must respect; a BRC cannot grant a browser permission or native-app association.

The bounded discovery window makes routine connection work finite. Ascending allocation remains valid above that window; the coverage report and explicit extension avoid pretending that a bounded scan is exhaustive. Signed, per-origin descriptors provide continuity and alias merging without asking every discovered wallet to reveal a user identity. They do not certify vendors.

Noise is selected as a documented protocol construction, rather than composing ad hoc encryption primitives. Fresh ephemeral keys and a random invitation PSK establish encrypted sessions without using wallet spending keys as transport identities. The extra confirmation and honest origin labeling address the fact that a QR payload alone does not attest a browser origin. The mandatory security review remains a release gate, not evidence that this newly composed profile is already secure in deployed implementations.

Initial implementation and acceptance plan

The specification precedes implementation. Historical source findings and referenced screens are integration inputs, not alternative definitions of conformance. Earlier method subsets, timeouts, backend assumptions, or branded flows do not create exceptions to the normative requirements above. Suggested work packages, in dependency order, are:

  1. Build independent fixtures for descriptor signatures, origin normalization, allocation/discovery overlap, binary carriage, state transitions, and encrypted records. Produce deterministic cross-language test vectors for this BRC's envelopes in addition to upstream cryptographic vectors. Complete the protocol review before presenting the profile as production-ready.
  2. Implement the external controller interfaces, storage, runtime checks, and neutral selection policy. Add the narrow optional WalletClient delegation hook with compatibility tests. Publish plain HTML, React, terminal, headless, and wrapper composition examples separately.
  3. Add signed discovery and next-free-port allocation to desktop wallets; implement extension, window.CWI, XDM, and React Native adapters and their provider events. Test simultaneous independent wallets and alias deduplication before mobile integration.
  4. Implement the complete encrypted relay/phone profile and its lifecycle/replay tests, then QR, same-phone links, return, revocation, and backend disclosure UIs. Retain legacy mobile adapters only as explicitly identified paths.
  5. Exercise Babbage Go around the bound interface, including missing-wallet UI ownership, insufficient-funds retry, both fee settings, action signing and pending state, cancellation, and switching. Verify behavior from source and execution rather than assuming constructor compatibility proves every flow.
  6. Run the published conformance matrix with BSVA reference wallets first, then independent vendor wallets. Candidate follow-on integrations include Metanet Desktop, Metanet Explorer, Peacock Wallet, PaperTrade, and Metanet Docs. This order is an implementation plan, not chooser ranking, certification, or a claim of by those projects.

No package names, reference applications, or proposed rollout order grant a vendor special treatment in normative discovery or selection. Onboarding opinions such as BRC-110/BRC-137 and the separate sponsored-onboarding proposal are background inputs; their catalogs or business flows are not prerequisites for this connection protocol.

Implementations

No implementation of this complete proposal is claimed at submission. Existing code listed below is prior art and integration input. This section should gain pinned releases, cross-implementation conformance reports, and security-review links when they exist.

Deterministic format fixtures accompany this draft for descriptor canonicalization/signing, confirmation-code formatting, BRC-100 getVersion request framing, record associated data, and size boundaries. Their intentionally public RFC 8032 test seed is for testing only. They do not include a complete Noise handshake/ciphertext interoperability vector and are not an implementation audit. For the fixture handshake-hash bytes 00 through 1f, the displayed comparison code is 9420 8747; this is a format example, not a reusable pairing credential.

References

Existing wallet contracts

Public discussion, September 23–24, 2026

Pinned implementation inputs

Protocol and platform references

Was this helpful?

Search Beersy

Search standards by number, title, author or topic