BSV-21 Basket Profile for BRC-46 / BRC-100
Fungible tokens on BSV (like a stablecoin or a points system) live as tiny special outputs mixed in with a wallet's ordinary money and its collectible items. Without agreement between wallets on where to keep these outputs and what information to attach to them, one wallet's tokens look like spendable cash or junk collectibles to another wallet, balances can't be reliably totaled, and sending a token to someone else risks losing the details they need to use it. This lets independent wallets hold, list, send and receive the same kind of token and understand each other correctly.
Reference for an AI
Everything an assistant needs to answer questions about BRC-163 accurately, including what it depends on.
Summary
- Why
- Independent wallets need a shared, agreed-upon place and format for fungible token outputs so they don't confuse them with spendable money, collectibles, or each other's incompatible conventions.
- What
- BRC-163 defines a standard wallet "basket" named bsv21 and a set of tags and custom instructions for holding, listing, transferring and importing BSV-21 fungible token outputs under the BRC-46 and BRC-100 wallet interfaces.
- How
- A wallet files eligible 1-satoshi BSV-21 value outputs under the basket bsv21, tags them like bsv21:<tokenId> for exact-match listOutputs filters, and stores compact JSON in customInstructions (id, amt, op, sym, dec, icon, and derivation fields) so balances can be listed by token id and transfers via createAction or…
What this lets you do
- Hold BSV-21 token outputs in a dedicated bsv21 basket separate from cash and collectibles
- List and sum token balances by token id using listOutputs filters and customInstructions
- Transfer token value with createAction while preserving remittance data and derivation for the receiver
- Import an existing token tip via internalizeAction with proper basket insertion
- Refuse to spend cosigner-gated tokens instead of silently failing on-chain
Written by claude-sonnet-5 from the specification text. Where the two differ, the original is correct.
The specification
Abstract
This BRC defines the application basket profile for BRC-161 BSV-21 fungible value outputs under BRC-46 / BRC-100. It reserves the basket name bsv21, specifies eligibility, documents tags for listOutputs filters, BRC-37 customInstructions for load-bearing token fields and derivation, and describes normative hold / list / transfer / import flows.
It does not redefine BSV-21 token economics (deploy, auth, mint, transfer, burn). Those live in BRC-161 (JSON) and BRC-162 (binary).
Trust model: holders verify their tips (local history / remittance / BEEF for the outs they hold). Issuers are trusted for mint policy. Global supply-cap / world-conservation proofs are not required by this profile.
Motivation
BRC-161 / BRC-162 define how BSV-21 appears on outputs. Without a shared basket name and remittance contract, wallets cannot reliably:
- Hold the same token UTXOs in a common basket (separate from spendable BSV and from
1satcollectables). - List and aggregate balances by token id for apps and UI.
- Transfer value outputs while preserving remittance (
customInstructions+ filter tags) the receiver needs. - Import historical tips via
internalizeActionwith interoperable basket insertion.
This profile records that convention so independent wallets can interoperate without trusting a single vendor. It is the fungible counterpart to BRC-147 for collectables.
Specification
Basket identifier
- The basket name is the UTF-8 string
bsv21. - Per BRC-46, implementations normalize basket identifiers by trimming whitespace and lowercasing. After normalization, conforming use of this profile MUST use exactly
bsv21. - This identifier does not use the BRC-99 reserved
pprefix. It is a standard application basket under BRC-46 permissioning.
Eligibility
An output SHOULD be placed in basket bsv21 only when all of the following hold:
satoshis === 1(JSON carrier is a 1-sat inscription output per BRC-161 / BRC-160).- The output is a BSV-21 value output under BRC-161: balance-bearing
deploy+mint,mint, ortransfer(not authority-onlydeploy+auth/auth, and notburn). - The sender or importer reasonably believes the inscription (or prior remittance) names a valid token
idandamtfor that output.
Wallets MUST NOT place ordinary payment change, 1sat collectables, or authority-only outputs into bsv21 merely to attach token-looking tags. Authority UTXOs MAY be tracked in a future companion basket; they are out of scope for this profile’s list/send surface.
Outputs that fail eligibility MAY still appear if a buggy sender used the basket; receivers SHOULD re-check inscription / remittance fields and MUST NOT present them as spendable token value solely because of basket membership.
Binary encoding (BRC-162)
This profile’s normative remittance examples use the JSON carrier (BRC-161). Wallets that hold BRC-162 binary value outputs MAY use the same basket name bsv21 and the same tag / customInstructions field map (token id underscore string, decimal amt string). Encoding detection is local; the basket contract is shared.
Outpoint and token id encoding
- BRC-100 wire
outpointfields use dot form:txid.vout. - BSV-21 token ids use underscore form only:
txid_vout(BRC-161) — in on-chain fields, in CIid, and after thebsv21:tag prefix. - Writers MUST use underscore form for those token-id strings. Readers MUST still accept and normalize either outpoint form before comparison.
- Other outpoint-shaped values (e.g. CI / tag
icon): writers SHOULD use underscore form; readers MUST normalize either form.
Tags vs customInstructions
Both tags and customInstructions travel with the output under BRC-46 / BRC-37. They serve different jobs. Writers SHOULD stay within reference BRC-100 client limits (@bsv/sdk / wallet-toolbox): tags ≤ 300 UTF-8 bytes each; customInstructions on internalizeAction basket insertion ≤ 1000 UTF-8 bytes.
| Tags | customInstructions | |
|---|---|---|
| Role | Exact-match filters for listOutputs (tags / tagQueryMode) | Load-bearing remittance for this tip: token fields apps read by key, plus derivation when this wallet locked it |
| Case | Trimmed and lowercased before store/match (BRC-100) | JSON string values preserve case |
| Typical content | bsv21:<tokenId>, optional kind markers / issuer / BRC-164 id: | id, amt, op, sym, dec, icon, and protocolID / keyID / counterparty when derived |
On-chain token fields remain defined by BRC-161 / BRC-162. CI is a compact remittance object for the tip — not a paste of the full inscription JSON (p and other wire-only keys need not appear).
Placement:
customInstructions— token fields apps need without scanning tags or re-parsing script on every UI read: at leastidandamt;op,sym,dec,iconwhen applicable; plus derivation when this wallet created the lock.- Tags — values useful as exact
listOutputsqueries (primarilybsv21:<tokenId>). Optional kind / issuer / list-key tags as below.
Reader priority for token id, amount, and display metadata: customInstructions → tags (legacy / compat) → locking script / local history.
Tags
Tags are optional BRC-46 / BRC-100 output tags used for exact-match filtering. Tag equality is case-insensitive. Writers SHOULD use a lowercase-safe alphabet (hex outpoints, ASCII markers).
| Tag | Requirement | Meaning |
|---|---|---|
bsv21:<tokenId> | SHOULD | Token id (txid_vout of deploy). Primary filter: “UTXOs of this token.” |
bsv21:deploy | SHOULD when applicable | Deploy output under this profile. |
bsv21:auth | SHOULD when applicable | Authority UTXO filed in this basket (if tracked here). |
op:<string> | MAY | Op for this output (transfer, mint, deploy+mint, …). |
issuer:<pubkeyHex> | MAY when issuer is known | Issuer pubkey hex for filters. Verify Sigma on-chain for proof. |
id:<string> | SHOULD when adopting BRC-164 | Per-output list key (wallet-local). Not the BSV-21 token id. |
amt:<string> | MAY | Legacy / compat amount filter. Prefer CI amt for reads; exact tag match is rarely useful for balances. |
sym:<string> | MAY | Legacy lowercased symbol filter. Prefer CI sym for display. |
dec:<string> / icon:<outpoint> | MAY | Legacy / compat mirrors of CI fields. |
Unknown tags MUST be preserved when transporting the output (BRC-37). Receivers that adopt BRC-164 stamp their own id: on import.
Custom instructions (BRC-37)
When present for basket bsv21, customInstructions MUST be a UTF-8 JSON object serialized as a string.
Conforming writers SHOULD emit a compact object — load-bearing token fields plus derivation when this wallet locked the tip:
{
"id": "<txid_vout>",
"amt": "<uint64 decimal string>",
"op": "transfer",
"sym": "<case-preserving ticker>",
"dec": "<optional 0-18 string>",
"icon": "<optional txid_vout>",
"protocolID": [0, "onesat"],
"keyID": "<wallet-defined>",
"counterparty": "self"
}
| Field | Type | Requirement | Meaning |
|---|---|---|---|
id | string | SHOULD | Token id, underscore form (BRC-161). |
amt | string | SHOULD | Amount in this UTXO (integer units as decimal string). |
op | string | SHOULD when known | Value op for this output (transfer, mint, deploy+mint, …). |
sym | string | SHOULD when known | Display symbol (case-preserving). |
dec | string | SHOULD when known | Deploy decimals 0–18. |
icon | string | SHOULD when known | Deploy icon outpoint (txid_vout). Pointer only — see Icon media. |
protocolID | array | SHOULD when wallet-derived | BRC-43 protocol used to derive the locking key. |
keyID | string | SHOULD when wallet-derived | BRC-43 key id (only when that key locked the output). |
counterparty | string | SHOULD when wallet-derived | BRC-43 counterparty (self, identity key, …). |
Additional JSON keys are permitted and MUST be ignored by readers that do not understand them. Readers MUST still store and forward the entire string unchanged (BRC-37), subject to the reference client’s size limits.
When the tip is locked with a literal script or external address and this wallet has no derivation triple, omit derivation fields and still SHOULD carry id / amt / display fields the sender knows.
Icon media (P2P — no content indexer)
BRC-161 icon is an outpoint of a prior image inscription (B-protocol / ord envelope), not a URL. Conforming remittance carries that pointer in CI icon (and MAY mirror a tag).
- Writers SHOULD include the icon inscription transaction in the BEEF that accompanies transfer / import (
inputBEEF, AtomicBEEF subject parents, or equivalent known-tx set) whenever they know that transaction (minting wallet, prior holder with stored BEEF). - Receivers SHOULD prefer decoding icon bytes from local BEEF / locking script (
ordenvelope field 0 + content-type) and MAY cache them keyed by the icon outpoint. - Media bytes travel in BEEF (keep CI under the 1000-byte
internalizeActioncap). - When no icon outpoint is present, or icon bytes cannot be recovered locally, wallets MAY show a deterministic hash tile (identicon) from token id /
sym— a UI fallback, not on-chain metadata. - Content HTTP APIs remain an optional recovery aid when local BEEF is missing.
Issuer attestation
sym is not unique. Meaningful tokens bind a deploy to an issuer identity.
- Proof (normative when claimed): the deploy transaction SHOULD carry a Sigma signature over the value tip, signed with the issuer’s identity key (prefer BRC-77 so the pubkey is recoverable; BSM is acceptable when the remittance carries the matching compressed pubkey). This matches
js-1sat-orddeployBsv21Token({ signer: { idKey } }). - Filter tag: writers MAY set tag
issuer:<pubkeyHex>forlistOutputsfilters. - Tags / CI are not proof. Receivers SHOULD verify Sigma on the tip when present. Unsigned deploys remain fully valid value tips under this profile — issuer attestation is optional. When an issuer is shown, wallets SHOULD distinguish a Sigma-matched claim from a remittance-only claim; when no issuer is known, omit issuer UI rather than framing the tip as defective.
- Token id remains CI
id/ tagbsv21:<tokenId>/ on-chain token id. Per-output list keys use BRC-164id:. - Display SHOULD key by
(issuer, tokenId)withsymas a nickname only.
Claims vs admission
- CI token fields and tags are claims for remittance / filter convenience.
- Token admission for a tip the wallet holds is local: valid script / history the wallet verifies for that tip. Global supply audits and indexers are optional.
- A wallet MUST NOT treat
amt/ token id as proven solely because they appear in CI or tags from an untrusted sender — prefer script parse and local history when trust matters. - Issuers (especially authority-mint tokens) are trusted for mint policy under this profile.
- Issuer pubkey in tags is a claim until Sigma (or equivalent on-chain attestation) verifies.
Non-plain locks (out of scope for this profile’s send path)
This profile’s normative hold / list / transfer path is plain owner-controlled BSV-21 value tips (inscription + P2PKH-style owner lock), matching common 1Sat / js-1sat-ord BSV-21 tooling.
Cosigner-gated instruments (e.g. MNEE) use issuer-specific APIs and a different signing flow. They are not intermixed with plain BSV-21 in the 1Sat SDK and are not standardized by this basket profile. Wallets that encounter a non-plain lock on a tip filed under bsv21 MUST fail closed (named refuse such as cosigner_required / unknown_lock) and MUST NOT fall through to bare P2PKH createAction.
A separate instrument / cosigner profile MAY document those routes later. Remittance MUST NOT invent a generic “any cosigner” send path that pretends MNEE-style flows are ordinary BSV-21 transfers.
Hold and list
Applications list held tips with BRC-100 listOutputs:
{
"basket": "bsv21",
"includeTags": true,
"includeCustomInstructions": true
}
UI SHOULD aggregate by token id and sum amounts from CI (id / amt), with tag / script fallbacks per Reader priority. Filter held tips of one token with tag bsv21:<tokenId>. Wallets MAY require basket permission per BRC-46 / BRC-100 before returning outputs.
Transfer (send)
A conforming transfer of held bsv21 value SHOULD use BRC-100 createAction with:
- One or more inputs spending value tips (
satoshis === 1) whose token id matches the token being sent (CI / script / tags). - One or more outputs with
satoshis: 1,basket: "bsv21", filter tags (at leastbsv21:<tokenId>), andcustomInstructionswith load-bearing fields (id,amt, …) plus derivation when this wallet locks the output. - Output amounts MUST NOT exceed input amounts for that token id under BRC-161 conservation (excess burns). Change SHOULD return to the sender as another
bsv21output when needed. - Prefer supplying
inputBEEF/ BEEF for spent tips when available so the receiver can validate the spend graph (BRC-62, BRC-95). - When an icon outpoint is known for the token, SHOULD merge the icon inscription transaction into that BEEF so the receiver can decode ticker media offline (see Icon media).
- Action
labelsMAY includebsv21; labels are non-normative for balances. Held spends MAY use BRC-164 list keys in labels when a permission module is present; that is out of scope for this profile’s remittance rules.
Self-kept change SHOULD carry updated CI amt (and filter tag bsv21:<tokenId>) and derivation CI when the wallet derived the lock. Senders MUST NOT invent a different token id for the same economic tip.
Import / receive (internalizeAction)
To place an existing value tip into basket bsv21, use BRC-100 internalizeAction with protocol basket insertion:
{
"tx": "<AtomicBEEF or BEEF bytes for the tip transaction>",
"description": "Import BSV-21 token",
"labels": ["bsv21"],
"outputs": [{
"outputIndex": 0,
"protocol": "basket insertion",
"insertionRemittance": {
"basket": "bsv21",
"tags": ["bsv21:<txid_vout>"],
"customInstructions": "{\"id\":\"<txid_vout>\",\"amt\":\"<amt>\",\"op\":\"transfer\",\"sym\":\"DEMO\"}"
}
}]
}
Import CI MUST stay within the reference client’s 1000-byte cap. Prefer CI for token fields; use tag bsv21:<tokenId> so later listOutputs can filter; parse the tip script when verifying admission. Derivation fields apply only if this wallet will unlock the tip with a known triple.
Payment separation (wallet policy guidance)
Spending or revealing bsv21 basket outputs is not a BRC-29 / default-basket payment. Conforming wallets SHOULD:
- Require distinct user authorization (or a dedicated token/basket grant) before
createAction/relinquishOutputthat spendsbsv21outputs. - Not treat a general “pay” or auto-pay grant as authorization to spend
bsv21tips. - Not fund ordinary payment outputs from
bsv21basket UTXOs. - Not mix
bsv21value into basket1sat(BRC-147) or count it toward BSV balance views.
Compatibility
- Wallets that do not implement this profile MUST still store and forward unknown baskets’
customInstructionsand tags unchanged (BRC-37). - Basket
1satremains collectables only (BRC-147); BSV-21 value MUST NOT be filed there. - Deprecated tick-based BSV-20 inventory is out of scope.
- Fine-grained BRC-99 permission schemes for
bsv21(e.g.p bsv21 …view/spend modules) are out of scope for this profile. This document is the plain basket / remittance contract only.
Security considerations
- Indexer trust — Display MUST NOT depend on HTTP content indexers for
iconwhen BEEF was supplied. Indexers are optional recovery only (see Icon media). - Remittance spoofing — CI
id/amt/symand filter tags are forgeable. Receivers SHOULD verify tip script / history for outs they accept; do not treat remittance alone as admission. - Trusted issuer — This profile does not require wallets to prove a global supply cap. Over-mint by an authority issuer is an issuer/policy risk accepted by holders of that token.
- Symbol collision —
symis not unique; always key balances by token id. - Basket pollution — Non-value or multi-sat outs in
bsv21confuse list UIs; re-checksatoshisand inscription rules. - Over-transfer burn — Building outputs whose
amtsum exceeds spent inputs burns value (BRC-161). Local send paths SHOULD conserve the sender's inputs. - Non-plain lock bypass — Unlocking a cosigner-gated tip with a plain P2PKH path fails on-chain and MUST be refused in the wallet. Do not treat MNEE-class instruments as ordinary
bsv21sends. - CI / tag limits — Oversized
customInstructionsoninternalizeActionis rejected by the reference SDK (1000-byte cap). Case-sensitive data in tags alone is lost to lowercasing.
Implementations
- HandCash Desktop (reference, in progress): basket
bsv21list / import under Collect (not Pay); plain BSV-21 tips; non-plain locks refuse rather than fall through; ticker icons from local BEEF / mint cache (not HTTP content APIs).
Source:src/wallet/bsv21.ts,src/wallet/fungibles.ts,src/wallet/tokenIconCache.tsin HandCash/HANDCASH-DESKTOP. - HandCash Cloud: MNEE remains a separate Pay / cosigner instrument path (
mneeCosignerService,mneeFactory) — not the plain BRC-163 send surface.
References
- BRC-161 — BSV-21 Fungible Tokens (JSON / Legacy)
- BRC-162 — BSV-21 Fungible Tokens (Binary)
- BRC-147 — 1Sat Ordinals Basket Profile
- BRC-164 — Output Identity Tags (
id:) - BRC-37 — Basket and Custom Instructions
- BRC-46 — Output Baskets
- BRC-100 — Wallet-to-Application Interface
- BRC-159 / BRC-160 — 1Sat carrier (not FT admission)