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 / BRC-176 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 a BRC-176 validity packet (
inputBEEF/ Atomic BEEF) for spent tips when available so the receiver can prove those outs without an indexer. - 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-176 — BSV-21 Validity Proofs
- 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)