User Wallet Data Format
Wallet data trapped inside one provider's software is a problem for the user who wants to leave and for anyone who has to answer a data request. This defines a single portable file holding everything about one user's wallet.
Summary
- Why
- Wallet users need a way to fully export their data so they can migrate providers, back up their wallet, or satisfy legal data-access requests, and no shared file format exists for that.
- What
- BRC-38 is a canonical JSON file format for exporting all of one user's Wallet Toolbox data as a single portable, unencrypted blob.
- How
- An exporter pulls every row tied to a given userId across a fixed set of tables, encodes binary fields as base64 and structured string fields as nested JSON, sorts each table array by a defined key, and serializes the whole thing with RFC 8785 JSON canonicalization.
What this lets you do
- Export a single user's entire wallet dataset as one JSON file
- Migrate a user's wallet data between storage providers
- Reconstruct labels, baskets, certificates, and sync state from an import
- Preserve tombstoned (deleted) rows instead of dropping them
- Produce a byte-stable file via canonical JSON serialization for hashing or diffing
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-38 accurately, including what it depends on.
The specification
Abstract
This specification defines a canonical, complete, portable file format for exporting Wallet Toolbox data for a single user as one blob.
The format is intended to support:
- strong user data portability
- GDPR and similar access/export requirements
- wallet import and export
- storage-provider migration
- backup, archival, and forensic recovery
This specification is based on the Wallet Toolbox storage implementation. It defines a canonical plaintext payload format for a single-user export. Encryption and protected transport are out of scope for this document and belong in a separate extension.
Motivation
Wallet data portability is only meaningful if the exported artifact is complete, stable, and implementation-neutral.
Exporting only high-level balances or transaction lists is insufficient. A serious wallet export must preserve:
- all user-owned records
- all user-linked proof and broadcast state
- deleted/tombstoned rows
- wallet-local metadata needed to reconstruct labels, baskets, certificates, sync state, and action history
Wallet Toolbox already has a rich schema that captures this information, but it does not yet define a canonical single-file export format. This specification fills that gap.
Specification
1. Scope
This specification defines the canonical plaintext payload for exporting all Wallet Toolbox data for one user.
It does not define:
- encryption of the export blob
- compression of the export blob
- transport protocols for moving the blob
- multi-user export containers
- chain-wide or storage-global operational logs unrelated to one user
- root-key, profile, or encrypted snapshot material that is not part of the Wallet Toolbox storage schema
2. Conformance
An implementation conforms to BRC-38 if it can:
- export a single user's Wallet Toolbox data into the canonical format defined here
- parse a BRC-38 blob and reconstruct the exported dataset semantics
- preserve all included row values, relationships, tombstones, and binary payloads without loss
3. Canonical File Form
The canonical BRC-38 artifact is a UTF-8 JSON document serialized according to RFC 8785 JSON Canonicalization Scheme (JCS).
The canonical unencrypted file extension SHOULD be:
.brc38.json
If a deployment wraps, compresses, or encrypts the document, the canonical BRC-38 payload remains the inner JSON document defined here.
4. Top-Level Document
The canonical top-level object MUST have the following shape:
{
"brc": 38,
"title": "User Wallet Data Format",
"formatVersion": 1,
"exportedAt": "2026-04-23T20:00:00.000Z",
"sourceStorage": {
"created_at": "2026-04-01T00:00:00.000Z",
"updated_at": "2026-04-23T20:00:00.000Z",
"storageIdentityKey": "<hex>",
"storageName": "Primary Wallet Storage",
"chain": "main",
"dbtype": "SQLite",
"maxOutputScript": 1024
},
"user": {
"...": "..."
},
"tables": {
"provenTxs": [],
"provenTxReqs": [],
"outputBaskets": [],
"transactions": [],
"commissions": [],
"outputs": [],
"outputTags": [],
"outputTagMaps": [],
"txLabels": [],
"txLabelMaps": [],
"certificates": [],
"certificateFields": [],
"syncStates": []
}
}
Top-level requirements:
brcMUST equal38.titleMUST equalUser Wallet Data Format.formatVersionMUST equal1for this version of the format.exportedAtMUST be the UTC timestamp at which the export payload was finalized.sourceStorageMUST contain the exported storage's currentsettingsrow in portable form.userMUST contain exactly one exported user row in portable form.tablesMUST contain every array listed above, even if empty.
5. Canonical Value Encoding
5.1 Timestamps
All timestamp values MUST be exported as strings in UTC using the exact form:
YYYY-MM-DDTHH:MM:SS.sssZ
Offsets other than Z MUST NOT be used.
5.2 Binary Data
Fields stored in Wallet Toolbox as number[] byte arrays MUST be exported as standard RFC 4648 base64 strings with padding.
No whitespace is permitted in base64 values.
The following fields are binary:
TableCommission.lockingScriptTableOutput.lockingScriptTableProvenTx.merklePathTableProvenTx.rawTxTableProvenTxReq.rawTxTableProvenTxReq.inputBEEFTableTransaction.inputBEEFTableTransaction.rawTx
5.3 Structured JSON-in-String Fields
The Wallet Toolbox schema stores some structured values as JSON strings. In BRC-38 these MUST be exported as decoded JSON values, not as embedded JSON strings.
The affected fields are:
TableProvenTxReq.historyTableProvenTxReq.notifyTableSyncState.syncMapTableSyncState.errorLocalTableSyncState.errorOther
Their portable BRC-38 forms are:
history: objectnotify: objectsyncMap: objecterrorLocal: object or omittederrorOther: object or omitted
5.4 Optional Fields
If a field is absent or undefined in the source dataset, it MUST be omitted from the exported JSON rather than emitted as null.
6. Export Closure
The exported dataset for a user is the following closure over the Wallet Toolbox schema.
6.1 Required Singletons
The export MUST include:
- exactly one
userrow for the requested user identity - exactly one
sourceStorageobject derived from the exporting storage's settings row
6.2 User-Owned Tables
The export MUST include every row from the following tables where userId equals the exported user's userId:
output_basketstransactionscommissionsoutputsoutput_tagstx_labelscertificatescertificate_fieldssync_states
6.3 User-Linked Map Tables
The export MUST include:
- every
tx_labels_maprow whosetxLabelIdreferences an exportedtx_labelsrow - every
output_tags_maprow whoseoutputTagIdreferences an exportedoutput_tagsrow
An exporter SHOULD additionally verify that:
- every exported
tx_labels_map.transactionIdreferences an exported transaction - every exported
output_tags_map.outputIdreferences an exported output
6.4 User-Linked Proof Tables
The export MUST include:
- every
proven_tx_reqsrow whosetxidmatches thetxidof an exported transaction - every
proven_txsrow whoseprovenTxIdis referenced by:- an exported transaction, or
- an exported
proven_tx_reqsrow
6.5 Excluded Storage-Global Tables
The export MUST NOT include storage-global tables that are not scoped to a single user.
In the Wallet Toolbox implementation this means:
monitor_eventsMUST NOT be included
7. Portable Row Forms
The BRC-38 portable row forms are defined below.
7.1 User
{
"created_at": "2026-04-01T00:00:00.000Z",
"updated_at": "2026-04-23T20:00:00.000Z",
"userId": 1,
"identityKey": "<hex>",
"activeStorage": "<storageIdentityKey>"
}
7.2 Proven Transaction
{
"created_at": "2026-04-01T00:00:00.000Z",
"updated_at": "2026-04-23T20:00:00.000Z",
"provenTxId": 7,
"txid": "<hex>",
"height": 900000,
"index": 12,
"merklePath": "<base64>",
"rawTx": "<base64>",
"blockHash": "<hex>",
"merkleRoot": "<hex>"
}
7.3 Proven Transaction Request
{
"created_at": "2026-04-01T00:00:00.000Z",
"updated_at": "2026-04-23T20:00:00.000Z",
"provenTxReqId": 8,
"provenTxId": 7,
"status": "completed",
"attempts": 2,
"notified": true,
"txid": "<hex>",
"batch": "abc123",
"history": {
"notes": []
},
"notify": {
"transactionIds": [42]
},
"rawTx": "<base64>",
"inputBEEF": "<base64>"
}
history MUST conform to:
{
"notes": [
{
"when": "2026-04-23T20:00:00.000Z",
"what": "sent"
}
]
}
notify MUST conform to:
{
"transactionIds": [1, 2, 3]
}
7.4 Output Basket
{
"created_at": "2026-04-01T00:00:00.000Z",
"updated_at": "2026-04-23T20:00:00.000Z",
"basketId": 2,
"userId": 1,
"name": "default",
"numberOfDesiredUTXOs": 32,
"minimumDesiredUTXOValue": 1000,
"isDeleted": false
}
7.5 Transaction
{
"created_at": "2026-04-01T00:00:00.000Z",
"updated_at": "2026-04-23T20:00:00.000Z",
"transactionId": 42,
"userId": 1,
"provenTxId": 7,
"status": "completed",
"reference": "<base64>",
"isOutgoing": true,
"satoshis": 1234,
"description": "Payment",
"version": 1,
"lockTime": 0,
"txid": "<hex>",
"inputBEEF": "<base64>",
"rawTx": "<base64>"
}
7.6 Commission
{
"created_at": "2026-04-01T00:00:00.000Z",
"updated_at": "2026-04-23T20:00:00.000Z",
"commissionId": 3,
"userId": 1,
"transactionId": 42,
"satoshis": 5,
"keyOffset": "<string>",
"isRedeemed": false,
"lockingScript": "<base64>"
}
7.7 Output
{
"created_at": "2026-04-01T00:00:00.000Z",
"updated_at": "2026-04-23T20:00:00.000Z",
"outputId": 11,
"userId": 1,
"transactionId": 42,
"basketId": 2,
"spendable": true,
"change": false,
"outputDescription": "payment output",
"vout": 0,
"satoshis": 1234,
"providedBy": "you",
"purpose": "payment",
"type": "P2PKH",
"txid": "<hex>",
"senderIdentityKey": "<hex>",
"derivationPrefix": "<base64>",
"derivationSuffix": "<base64>",
"customInstructions": "optional",
"spentBy": 99,
"sequenceNumber": 0,
"spendingDescription": "optional",
"scriptLength": 25,
"scriptOffset": 0,
"lockingScript": "<base64>"
}
7.8 Output Tag
{
"created_at": "2026-04-01T00:00:00.000Z",
"updated_at": "2026-04-23T20:00:00.000Z",
"outputTagId": 4,
"userId": 1,
"tag": "invoice",
"isDeleted": false
}
7.9 Output Tag Map
{
"created_at": "2026-04-01T00:00:00.000Z",
"updated_at": "2026-04-23T20:00:00.000Z",
"outputTagId": 4,
"outputId": 11,
"isDeleted": false
}
7.10 Transaction Label
{
"created_at": "2026-04-01T00:00:00.000Z",
"updated_at": "2026-04-23T20:00:00.000Z",
"txLabelId": 5,
"userId": 1,
"label": "expenses",
"isDeleted": false
}
7.11 Transaction Label Map
{
"created_at": "2026-04-01T00:00:00.000Z",
"updated_at": "2026-04-23T20:00:00.000Z",
"txLabelId": 5,
"transactionId": 42,
"isDeleted": false
}
7.12 Certificate
{
"created_at": "2026-04-01T00:00:00.000Z",
"updated_at": "2026-04-23T20:00:00.000Z",
"certificateId": 6,
"userId": 1,
"type": "<base64>",
"serialNumber": "<base64>",
"certifier": "<hex>",
"subject": "<hex>",
"verifier": "<hex>",
"revocationOutpoint": "<txid>.<vout>",
"signature": "<hex>",
"isDeleted": false
}
7.13 Certificate Field
{
"created_at": "2026-04-01T00:00:00.000Z",
"updated_at": "2026-04-23T20:00:00.000Z",
"userId": 1,
"certificateId": 6,
"fieldName": "email",
"fieldValue": "alice@example.com",
"masterKey": "<base64>"
}
7.14 Sync State
{
"created_at": "2026-04-01T00:00:00.000Z",
"updated_at": "2026-04-23T20:00:00.000Z",
"syncStateId": 9,
"userId": 1,
"storageIdentityKey": "<hex>",
"storageName": "Backup Storage",
"status": "success",
"init": true,
"refNum": "<string>",
"syncMap": {
"provenTx": { "entityName": "provenTx", "idMap": {}, "count": 0 },
"outputBasket": { "entityName": "outputBasket", "idMap": {}, "count": 0 },
"transaction": { "entityName": "transaction", "idMap": {}, "count": 0 },
"provenTxReq": { "entityName": "provenTxReq", "idMap": {}, "count": 0 },
"txLabel": { "entityName": "txLabel", "idMap": {}, "count": 0 },
"txLabelMap": { "entityName": "txLabelMap", "idMap": {}, "count": 0 },
"output": { "entityName": "output", "idMap": {}, "count": 0 },
"outputTag": { "entityName": "outputTag", "idMap": {}, "count": 0 },
"outputTagMap": { "entityName": "outputTagMap", "idMap": {}, "count": 0 },
"certificate": { "entityName": "certificate", "idMap": {}, "count": 0 },
"certificateField": { "entityName": "certificateField", "idMap": {}, "count": 0 },
"commission": { "entityName": "commission", "idMap": {}, "count": 0 }
},
"when": "2026-04-23T20:00:00.000Z",
"satoshis": 123456,
"errorLocal": {
"code": "example",
"description": "example",
"stack": "optional"
},
"errorOther": {
"code": "example",
"description": "example"
}
}
Each sync error object MUST conform to:
{
"code": "string",
"description": "string",
"stack": "optional string"
}
8. Array Ordering
For canonical serialization, the arrays inside tables MUST be sorted ascending as follows:
provenTxsbyprovenTxIdprovenTxReqsbyprovenTxReqIdoutputBasketsbybasketIdtransactionsbytransactionIdcommissionsbycommissionIdoutputsbyoutputIdoutputTagsbyoutputTagIdoutputTagMapsbyoutputId, thenoutputTagIdtxLabelsbytxLabelIdtxLabelMapsbytransactionId, thentxLabelIdcertificatesbycertificateIdcertificateFieldsbycertificateId, thenfieldNamesyncStatesbysyncStateId
9. Relationship Integrity
An exporter MUST ensure that every foreign-key-like reference inside the payload resolves within the exported document, except where the source schema intentionally stores an optional unresolved reference.
At minimum:
- every exported
transaction.userIdMUST equaluser.userId - every exported
output.userIdMUST equaluser.userId - every exported
output.transactionIdMUST reference an exported transaction - every exported
commission.transactionIdMUST reference an exported transaction - every exported
txLabelMap.txLabelIdMUST reference an exported transaction label - every exported
txLabelMap.transactionIdMUST reference an exported transaction - every exported
outputTagMap.outputTagIdMUST reference an exported output tag - every exported
outputTagMap.outputIdMUST reference an exported output - every exported
certificateField.certificateIdMUST reference an exported certificate
If user.activeStorage does not equal sourceStorage.storageIdentityKey, the exporter MUST still preserve the exact user.activeStorage value.
10. Import Semantics
BRC-38 defines the portable payload, not a mandatory database insertion strategy.
Importers:
- MUST preserve row values and relationships semantically
- MAY preserve numeric primary IDs exactly
- MAY remap numeric primary IDs during import, provided every internal reference is updated consistently
- MUST preserve tombstones such as
isDeleted - MUST preserve status fields exactly
Importers MAY treat the following as operationally sensitive and choose whether to activate them immediately after import:
user.activeStoragesyncStates
However, a conforming importer MUST still parse and preserve those values in the imported dataset.
11. Privacy and Security
BRC-38 exports are intentionally complete and sensitive.
A BRC-38 blob may contain:
- raw transactions
- merkle proofs
- wallet labeling and description data
- certificate contents
- encrypted field material and derivation metadata
- sync history and storage topology hints
Implementations MUST treat BRC-38 blobs as highly sensitive user data.
12. Implementation Notes
This specification is based on the Wallet Toolbox schema:
usersproven_txsproven_tx_reqsoutput_basketstransactionscommissionsoutputsoutput_tagsoutput_tags_maptx_labelstx_labels_mapcertificatescertificate_fieldssync_states- storage
settingsas export metadata
The storage-global monitor_events table is intentionally excluded because it is not scoped to a single user.