# BRC-38: User Wallet Data Format Ty Everett (ty@projectbabbage.com) ## 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: 1. export a single user's Wallet Toolbox data into the canonical format defined here 2. parse a BRC-38 blob and reconstruct the exported dataset semantics 3. 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: ```json { "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": "", "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: - `brc` MUST equal `38`. - `title` MUST equal `User Wallet Data Format`. - `formatVersion` MUST equal `1` for this version of the format. - `exportedAt` MUST be the UTC timestamp at which the export payload was finalized. - `sourceStorage` MUST contain the exported storage's current `settings` row in portable form. - `user` MUST contain exactly one exported user row in portable form. - `tables` MUST 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.lockingScript` - `TableOutput.lockingScript` - `TableProvenTx.merklePath` - `TableProvenTx.rawTx` - `TableProvenTxReq.rawTx` - `TableProvenTxReq.inputBEEF` - `TableTransaction.inputBEEF` - `TableTransaction.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.history` - `TableProvenTxReq.notify` - `TableSyncState.syncMap` - `TableSyncState.errorLocal` - `TableSyncState.errorOther` Their portable BRC-38 forms are: - `history`: object - `notify`: object - `syncMap`: object - `errorLocal`: object or omitted - `errorOther`: 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 `user` row for the requested user identity - exactly one `sourceStorage` object 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_baskets` - `transactions` - `commissions` - `outputs` - `output_tags` - `tx_labels` - `certificates` - `certificate_fields` - `sync_states` #### 6.3 User-Linked Map Tables The export MUST include: - every `tx_labels_map` row whose `txLabelId` references an exported `tx_labels` row - every `output_tags_map` row whose `outputTagId` references an exported `output_tags` row An exporter SHOULD additionally verify that: - every exported `tx_labels_map.transactionId` references an exported transaction - every exported `output_tags_map.outputId` references an exported output #### 6.4 User-Linked Proof Tables The export MUST include: - every `proven_tx_reqs` row whose `txid` matches the `txid` of an exported transaction - every `proven_txs` row whose `provenTxId` is referenced by: - an exported transaction, or - an exported `proven_tx_reqs` row #### 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_events` MUST NOT be included ### 7. Portable Row Forms The BRC-38 portable row forms are defined below. #### 7.1 User ```json { "created_at": "2026-04-01T00:00:00.000Z", "updated_at": "2026-04-23T20:00:00.000Z", "userId": 1, "identityKey": "", "activeStorage": "" } ``` #### 7.2 Proven Transaction ```json { "created_at": "2026-04-01T00:00:00.000Z", "updated_at": "2026-04-23T20:00:00.000Z", "provenTxId": 7, "txid": "", "height": 900000, "index": 12, "merklePath": "", "rawTx": "", "blockHash": "", "merkleRoot": "" } ``` #### 7.3 Proven Transaction Request ```json { "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": "", "batch": "abc123", "history": { "notes": [] }, "notify": { "transactionIds": [42] }, "rawTx": "", "inputBEEF": "" } ``` `history` MUST conform to: ```json { "notes": [ { "when": "2026-04-23T20:00:00.000Z", "what": "sent" } ] } ``` `notify` MUST conform to: ```json { "transactionIds": [1, 2, 3] } ``` #### 7.4 Output Basket ```json { "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 ```json { "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": "", "isOutgoing": true, "satoshis": 1234, "description": "Payment", "version": 1, "lockTime": 0, "txid": "", "inputBEEF": "", "rawTx": "" } ``` #### 7.6 Commission ```json { "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": "", "isRedeemed": false, "lockingScript": "" } ``` #### 7.7 Output ```json { "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": "", "senderIdentityKey": "", "derivationPrefix": "", "derivationSuffix": "", "customInstructions": "optional", "spentBy": 99, "sequenceNumber": 0, "spendingDescription": "optional", "scriptLength": 25, "scriptOffset": 0, "lockingScript": "" } ``` #### 7.8 Output Tag ```json { "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 ```json { "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 ```json { "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 ```json { "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 ```json { "created_at": "2026-04-01T00:00:00.000Z", "updated_at": "2026-04-23T20:00:00.000Z", "certificateId": 6, "userId": 1, "type": "", "serialNumber": "", "certifier": "", "subject": "", "verifier": "", "revocationOutpoint": ".", "signature": "", "isDeleted": false } ``` #### 7.13 Certificate Field ```json { "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": "" } ``` #### 7.14 Sync State ```json { "created_at": "2026-04-01T00:00:00.000Z", "updated_at": "2026-04-23T20:00:00.000Z", "syncStateId": 9, "userId": 1, "storageIdentityKey": "", "storageName": "Backup Storage", "status": "success", "init": true, "refNum": "", "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: ```json { "code": "string", "description": "string", "stack": "optional string" } ``` ### 8. Array Ordering For canonical serialization, the arrays inside `tables` MUST be sorted ascending as follows: - `provenTxs` by `provenTxId` - `provenTxReqs` by `provenTxReqId` - `outputBaskets` by `basketId` - `transactions` by `transactionId` - `commissions` by `commissionId` - `outputs` by `outputId` - `outputTags` by `outputTagId` - `outputTagMaps` by `outputId`, then `outputTagId` - `txLabels` by `txLabelId` - `txLabelMaps` by `transactionId`, then `txLabelId` - `certificates` by `certificateId` - `certificateFields` by `certificateId`, then `fieldName` - `syncStates` by `syncStateId` ### 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.userId` MUST equal `user.userId` - every exported `output.userId` MUST equal `user.userId` - every exported `output.transactionId` MUST reference an exported transaction - every exported `commission.transactionId` MUST reference an exported transaction - every exported `txLabelMap.txLabelId` MUST reference an exported transaction label - every exported `txLabelMap.transactionId` MUST reference an exported transaction - every exported `outputTagMap.outputTagId` MUST reference an exported output tag - every exported `outputTagMap.outputId` MUST reference an exported output - every exported `certificateField.certificateId` MUST 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.activeStorage` - `syncStates` 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: - `users` - `proven_txs` - `proven_tx_reqs` - `output_baskets` - `transactions` - `commissions` - `outputs` - `output_tags` - `output_tags_map` - `tx_labels` - `tx_labels_map` - `certificates` - `certificate_fields` - `sync_states` - storage `settings` as export metadata The storage-global `monitor_events` table is intentionally excluded because it is not scoped to a single user. ## References - [BRC-40: User Wallet Data Synchronization](./0040.md) - [BRC-100: Unified, Vendor-Neutral, Unchanging, and Open BSV Blockchain Standard Wallet-to-Application Interface](../wallet/0100.md) - [RFC 8785: JSON Canonicalization Scheme (JCS)](https://www.rfc-editor.org/rfc/rfc8785) - [Wallet Toolbox package in `ts-stack`](https://github.com/bsv-blockchain/ts-stack/tree/main/packages/wallet/wallet-toolbox)