XDM Wallet Communications Substrate
Two web pages from different sites cannot safely reach into each other, which becomes a problem when one of them is your wallet. This uses the browser's own page-to-page messaging so an application can ask a wallet to act while each side stays sealed off from the other.
Summary
- Why
- Browsers block pages from directly accessing each other's content, so a wallet running in one browser tab or frame needs a safe, standard way to exchange requests and responses with app pages that are not allowed to run a local HTTP server.
- What
- BRC-6 defines a message format and exchange pattern, built on the browser's postMessage mechanism, for a parent page running a wallet to receive requests from and send responses to child app pages.
- How
- An app sends a window.postMessage with a type of CWI, a random id, a call name, and params, then listens for a matching response; the wallet page listens for such messages, performs the requested BRC-1/BRC-2/BRC-3/BRC-53/BRC-56-style operation, and posts back a result or an error keyed to the same id.
What this lets you do
- Send a wallet request from an iframe or child window without direct DOM access
- Match asynchronous responses to requests using a generated message id
- Distinguish real responses from echoed outgoing messages and untrusted events
- Return structured errors with a code and human-readable description
- Serve multiple app pages from one logged-in wallet page at once
Written by claude-sonnet-5 from the specification text. Where the two differ, the original is correct.
Reference for an AI
Everything an assistant needs to answer questions about BRC-6 accurately, including what it depends on.
The specification
Abstract
Cross-document messaging enables web-based applications to communicate with one another in a secure manner, without allowing the two applications to manipulate each other's DOM trees. It defines a mechanism by which messages can be sent and received, with browser-based attestation of the origin of each message. We define the framework and conventions for operating a BRC-56 wallet over XDM, enabling a parent page that runs a wallet to communicate with one or multiple child pages that run applications.
Motivation
BRC-56 defines a suite of abstract messages used by applications and wallets to facilitate various Bitcoin and MetaNet operations that enable micropayments, protect user privacy and ensure secure authentication without the need for each application to maintain a separate user account. While BRC-5 defines a method of using a BRC-56 wallet over HTTP on the local machine, some mobile devices are unable to support running HTTP servers due to platform-specific limitations. Additionally, it is sometimes desirable for a MetaNet environment to run fully in the browser, enabling users to access their identities on devices or platforms where a desktop-based BRC-5 client cannot be installed. This specification provides a ubiquitous communications substrate enabling the use of BRC-56 functionality across a wide range of devices and deployment contexts, including fully in-browser experiences.
Status Note
The XDM substrate concept remains relevant, but this document's BRC-56 method table is historical. Current XDM-style wallet bridges SHOULD expose the BRC-100 wallet method set. Legacy call names such as createCertificate and ninja.findCertificates are deprecated for new implementations.
Specification
We specify that the parent page runs the wallet, responding to messages from child pages. This has several advantages:
- The wallet can always pop-up any necessary permission popups or user-interactive authorization screens without interference from client pages
- When the user visits the parent page, they log in once, then they can access any applications after login
- Multiple applications, all running in the same parent page, can access and utilize the BRC-56 wallet at once
- Reducing the number of wallets running in parallel reduces the chances of UTXO synchronization or corruption issues
- It is possible to run the parent wallet page locally, connecting to remote services only when required by specific applications
- If a specific application was the parent and the wallet was a child, failure of the parent page to respond could constitute failure of a user to access their identity or assets, which might otherwise be available through another application
Application Message Sending and Processing
We start with the message relay interface defined by XDM. JavaScript code for sending messages from the application to the wallet is as follows:
new Promise((resolve, reject) => {
const id = 'abcdabcd' // get a random message ID
window.addEventListener('message', async e => {
if (e.data.type !== 'CWI' || !e.isTrusted || e.data.id !== id || e.data.isInvocation) return
if (e.data.status === 'error') {
const err = new Error(e.data.description)
err.code = e.data.code
reject(err)
} else {
resolve(e.data.result)
}
})
window.parent.postMessage({
type: 'CWI',
isInvocation: true,
id,
call: 'getVersion',
params: {}
}, '*')
})
We stipulate that:
- All messages (requests, responses and errors) have a
typeproperty equal toCWI(this value, which stands for Computing with Integrity, is historical) - A random message ID is generated by the application
- The application listens for new, incoming response messages. As part of the listener, the application:
- Drops any events without the correct
typein the event data - Drops any events where
isTrustedis nottrue - Drops any events where the event data contains the
isInvocationflag, denoting any outgoing messages that were echoed back - Drops any events where the event data contains an
idfield other than the one generated by step 2 - Handles any error messages if the event's data contains a
statusfield equal toerror, relying on thecodeanddescriptionfields to construct an appropriate error - Otherwise, if no errors are detected, the application is now free to make use of the
resultfield from the event's data payload, which will be the response from the wallet as specified by the relevant BRC standard for the specific message being sent
- Drops any events without the correct
- With the listener in place and ready to process the response when it arrives, the application now constructs and sends the message to the wallet through the parent window:
- Like all messages, the outgoing message contains a
typefield equal toCWI - An
isInvocationflag is set totrue, allowing listeners to easily drop outgoing messages rather than trying to process them as responses - The
idthat was generated in step 2 is included. The sameidmust be used by wallets when sending back the response - The
calldetermines which message is being sent. Specificcallvalues are defined below - The
paramsfield comprises the specific parameters as specified in BRCs that define specific message types
- Like all messages, the outgoing message contains a
This application-side interface facilitates exchanging and receiving messages with the BRC-56 wallet over XDM. We now proceed to how the wallet handles its side of the interaction.
Wallet Message Receipt, Processing and Response
The wallet listens for incoming messages and replies to the originator with appropriate responses after obtaining permission from the user (if applicable) and processing the request. Some JavaScript code that implements this functionality is provided:
window.addEventListener('message', async e => {
if (e.data.type !== 'CWI' || !e.isTrusted || typeof e.data.call !== 'string') return
// This is where the wallet will do its processing, based on `call` and `params`.
// ... in a rudamentary implementation ...
if (e.data.call === 'createAction') { // BRC-1
try {
let result = await doBRC1Thing({
...e.data.params,
originator: e.origin // You might let BRC1Thing know which app is sending the request, for permission purposes
})
e.source.postMessage({
type: 'CWI', result, id: e.data.id
}, e.origin)
} catch (error) {
e.source.postMessage({
type: 'CWI',
id: e.data.id,
status: 'error',
code: error.code || 'ERR_UNKNOWN',
description: error.message
}, e.origin)
}
} else if (e.data.call === 'encrypt') { // BRC-2 encrypt
try {
let result = await doBRC2Thing({
...e.data.params,
originator: e.origin // You might let BRC2Thing know which app is sending the request, for permission purposes
})
e.source.postMessage({
type: 'CWI', result, id: e.data.id
}, e.origin)
} catch (error) {
e.source.postMessage({
type: 'CWI',
id: e.data.id,
status: 'error',
code: error.code || 'ERR_UNKNOWN',
description: error.message
}, e.origin)
}
} // ... implement all functions ...
})
We stipulate, before any client applications are loaded which might send any messages to the wallet, that the wallet running on the parent page must bind a message event handler that:
- Upon receipt of a message with an event data field
typenot equal toCWIwill drop the message - Upon receipt of an untrusted message, or one without a
callwill drop the message - Upon receipt of a message with an unknown or unsupported
callwill proceed to step 6 - Based on the
callandparamswill perform the necessary steps as required by the relevant BRC specifications for the specific operation - Compose a response message and send it back to the originator, the response message comprising an event payload with the following fields:
- A
typevalue ofCWI - The
idthat was specified by the application when the message was created - A
resultvalue that is the result of the operation being performed, as specified by the particular operation
- A
- In case of any errors with the operation, the wallet will instead send back a response message comprising an event payload with the following fields:
- A
typevalue ofCWI - A
statusvalue oferror - The
idthat was specified by the application when the message was created - A relevant
codefor the error, as specified by the particular operation in question - A human-readable
descriptionfor the error
- A
Values for call Associated with Various Message Types
For each of the message pairs (request and response) incorporated into BRC-56, we specify the existence of a corresponding XDM message pair with a specific call value:
| Message Pair | call Value | Specific Implementation Notes |
|---|---|---|
| BRC-1 Transaction Creation | createAction | |
| BRC-2 Encryption | encrypt | |
| BRC-2 Decryption | decrypt | |
| BRC-3 Signature Creation | createSignature | |
| BRC-3 Signature Verification | verifySignature | |
| BRC-53 Certificate Creation | createCertificate | |
| BRC-53 Certificate Verification | proveCertificate | |
| BRC-56 HMAC Creation | createHmac | |
| BRC-56 HMAC Verification | verifyHmac | |
| BRC-56 Public Key Derivation | getPublicKey | |
| BRC-56 Certificate List | ninja.findCertificates | This call name prefix is historical and retained for compatibility |
| BRC-56 Version Request | getVersion | |
| BRC-56 Network Request | getNetwork | |
| BRC-56 Authentication Request | isAuthenticated | |
| BRC-56 Async Auth Request | waitForAuthentication |
Implementations
Implementers of applications and wallets should create and process XDM messages in the manner described, using BRC-100 method names and payloads for current interoperability. Older Babbage SDK references are retained only for historical context.
Implementation questions should be directed to the author.
One (crude) example of a deployed architecture in which a parent page uses XDM to communicate with child application pages, facilitating the operation of multiple client applications which communicate with the parent wallet, has been implemented by the Babbage team at BabbageOS.com.