Reference · For implementers and operators
SRC-20 reference
Terminology, the semantics an indexer has to reproduce, the honest state of the Counterparty dependency, and the things that will bite you.
Contents
1. Terminology
- Account ledger
- An ownership model where a balance is attached to an address rather than to a specific unspent output. SRC-20 uses this model. It is the reason there is no such thing as an output that "holds" your tokens, and the reason a listing cannot be escrowed by locking one.
- ARC4
- The stream cipher applied to the bare multisig carrier. It provides no confidentiality here, since the key is public: the key is the txid of the output spent by input 0, byte-reversed. Its function is to bind the payload to that input and to make the data bytes look like plausible public keys.
- Burn key
- One of five recognised public keys used as the third key of a 1-of-3 multisig data output. Nobody holds the private key, so the output is unspendable. Its presence sets the keyburn flag, and without that flag a multisig-carried payload is not recognised as SRC-20.
- Clamping
- Reducing a mint's amount to the remaining supply (status
OMA) or to the per-mint limit (statusODL). A clamped mint is valid and does change state. See V-6 to V-8. - Creator
- The address that controls the output spent by input 0. It is the actor for every operation: the deployer of a DEPLOY, the payer of a MINT, the sender of a TRANSFER.
- Destination
- The address encoded in output 0. It receives a MINT and receives a TRANSFER. It need not be the creator.
- Excluded
- A transaction that never becomes an SRC-20 record: the carrier failed, the prefix was missing, the JSON did not parse, or a field failed a carrier-level check. Excluded transactions have no status code and appear nowhere.
- Invalid
- A transaction that is recognised and recorded as an SRC-20 operation but changes no balance. It carries a status code and appears in activity feeds.
- Keyburn
- A flag set when a recognised burn key is seen in a multisig data output, and set unconditionally by the OLGA decoder. SRC-20 recognition requires it.
- Ledger hash
- A per-block digest of the valid SRC-20 state produced by the reference indexer. Two indexers that agree on every block's ledger hash agree on the ledger. It is the practical tool for detecting divergence.
- OLGA
- The P2WSH carrier, available for SRC-20 from block 865000. The payload occupies the 32-byte witness program of each data output, unencrypted. It takes precedence over bare multisig.
- Stamps prefix
- The six ASCII bytes
stamp:that mark a decoded buffer as Bitcoin Stamps data. Shared by SRC-20, SRC-721 and SRC-101, which are told apart by thepfield of the JSON that follows. - Tick hash
- The SHA3-256 hex digest of the lowercased ticker. NIST SHA3, not Keccak. Used as a stable database key for tickers containing awkward characters.
- Unprunable output
- An output whose script a full node must retain because the output is unspent and could in principle be spent. SRC-20 data outputs are unspendable in practice, so they are retained permanently.
2. Indexer semantics
2.1 Ordering
Blocks are processed in height order and transactions in block order. A running balance and a running mint total are maintained within a block, so an operation sees the effect of every earlier valid operation in the same block. Two transfers of a full balance in one block cannot both succeed.
Where two DEPLOY operations for the same ticker land in the same block, the earlier transaction in block order takes the ticker, and the later is invalid with status DE.
2.2 Confirmation
SRC-20 state is derived from confirmed blocks only. The reference indexer parses transactions as it processes a block; there is no separate confirmation threshold at which an operation becomes valid, because an operation in a confirmed block is either valid or not the moment that block is processed.
Practical confirmation depth is therefore a policy decision for the application, not a protocol rule. What the protocol gives you is: once a block is buried deeply enough that a reorg is implausible, the operations in it are settled.
2.3 Mempool
SRC-20 has no mempool semantics. An unconfirmed transaction has no SRC-20 meaning at all: it cannot be validated, because validity depends on the total minted and the sender's balance at a specific point in the chain, and neither is defined for an unconfirmed transaction.
Bitcoin Universe's own Stamps indexer reports its SRC-20 source coverage as partial for exactly this reason: the upstream adapters lack an exhaustive mempool feed with a stable pending lifecycle and disappearance handling. A wallet may show a transaction as pending, but no indexer can honestly promise what it will do until it confirms.
The consequence for user interfaces: never show a projected balance from an unconfirmed SRC-20 operation as though it were real. A mint that looks like 1000 in the mempool can be a valid mint of 3 once the block lands.
2.4 Reorganizations
Because SRC-20 balances are a fold over the whole chain, a reorg invalidates every derived value from the divergence point forward. The reference indexer handles this by rolling back rather than by patching.
- R-1
On detecting a chain reorganization, the reference indexer rolls back 10 blocks below the affected height rather than exactly to the divergence point, deliberately over-rewinding for safety, then re-processes forward.
- R-2
Rollback re-derives balances. There is no attempt to reverse individual operations in place, because clamped mints and running balances make an operation's effect dependent on everything before it.
- R-3
Downstream consumers must be told. Bitcoin Universe's Stamps indexer publishes explicit event invalidations on reorg, clears orphaned deployment snapshots, and marks orphaned assets unavailable before replacement state is published, so a consumer never sees two conflicting truths at once.
- R-4
A reorg at or below a finalized height is rejected outright rather than applied. Choose that finality depth conservatively; it is the point past which your indexer refuses to rewrite history.
The interesting failure mode is a reorg that reorders two transactions within a block boundary. That can change which DEPLOY won a ticker, or turn a valid transfer into a BB failure, without either transaction being dropped from the chain.
2.5 Checking that you agree with everyone else
The reference indexer computes a ledger hash per block over the valid SRC-20 state and can validate it against an external API, queueing mismatches for background revalidation. If you are writing an indexer, reproduce that hash and compare: it will find a divergence far faster than noticing a wrong balance months later.
The usual causes of divergence, in rough order of frequency: treating clamped mints as invalid, applying strict numeric parsing below height 833000, adding a multisig fallback when the OLGA branch fails, and getting the truncation asymmetry between max/lim and amt wrong.
3. The Counterparty dependency
Stated plainly, because it is usually stated wrongly in both directions.
| Scope | Counterparty required? | Detail |
|---|---|---|
| SRC-20, blocks 788041 to 795999 | Yes | Operations ride inside Counterparty issuances. The indexer requires a numeric asset id beginning with A and a declared supply of 0. |
| SRC-20, block 796000 onward | No | Counterparty-encoded SRC-20 is ignored from this height. Only transactions with no Counterparty asset id count. |
| Classic Bitcoin Stamps | Yes | Stamps assets remain Counterparty assets, so a Stamps indexer keeps its Counterparty dependency regardless of SRC-20. |
| A new SRC-20-only indexer | Partly | Needed to replay history below 796000. Not needed for anything after it. |
So both of these statements are wrong: "SRC-20 runs on Counterparty" (it stopped in 2023) and "SRC-20 never needed Counterparty" (its first 8000 blocks did, and those balances are still live).
4. Bitcoin Stamps, SRC-101 and SRC-721
Four protocols share one carrier. Everything below the JSON is identical; the p field is the only thing that separates them.
| Protocol | What it is | Ownership model | First block |
|---|---|---|---|
| Bitcoin Stamps | Images stored in unprunable outputs | Counterparty asset | 779652 |
| SRC-20 | Fungible tokens | Account ledger, balance per address | 788041 |
| SRC-721 | Non-fungible tokens | Asset-based | 792370 |
| SRC-101 | Names: namespaces, registration, renewal, records | Hybrid | 870652 |
For an indexer this matters in one specific way: you cannot decide whether a transaction is SRC-20 until after you have decoded the carrier and parsed the JSON. The prefix check tells you it is a Stamps transaction; only the p field tells you which one. A parser that assumes every stamp: payload is SRC-20 will trip over images and names.
SRC-101 got its own OLGA P2WSH activation later than SRC-20, and one detail of the OLGA decoder is conditional on that height, so the family members do not share a single activation table. Treat each sub-protocol's heights separately.
5. Size and fee considerations
- OLGA capacity32 Bper P2WSH data output
- Multisig capacity62 B2 keys of 31 B per output
- Framing overhead8 B2 B length plus "stamp:"
- Length ceiling65535the 16-bit length prefix
Three properties follow from the carrier and are worth designing around.
- No witness discount
- Output data pays full transaction weight. The same payload costs roughly four times what it would in a witness. This is the price of the durability guarantee, not an implementation inefficiency.
- Dust is destroyed, not spent
- Every data output must carry enough value to be relayed, and that value can never be recovered. It is a real cost per output, so payload length translates directly into permanently lost value as well as fee.
- JSON length is under your control
- Omitting optional metadata from a DEPLOY, and keeping tickers short, changes how many data outputs you need. The guide's cost table shows a 29-byte difference moving a payload from 2 data outputs to 3.
The 16-bit length prefix caps a single payload at 65535 bytes including the prefix. No realistic SRC-20 operation approaches it; the limit matters for Bitcoin Stamps images sharing the same carrier.
6. Limitations
- L-1
No escrow primitive. Because balances are account-based, no output can be locked to back a listing. A seller can transfer the same balance elsewhere before settlement, and no indexer can prevent it. Any marketplace must therefore either accept liveness and dispute risk through cooperative multisig, or take custody.
- L-2
No conditional transfers. There is no hashlock, timelock, or atomic swap primitive in the active protocol. Proposals exist as drafts; a draft is not protocol behaviour and must not be implemented as though it were.
- L-3
No burn, no mint authority, no supply change. A DEPLOY is final. There is no way to increase supply, reduce it, revoke a balance, or pause a token.
- L-4
No batch operations. One operation per transaction. The
BULK_XFERcode path in the reference indexer is unreachable, and payloads using it are invalid with statusUO. - L-5
No standards body. The protocol is whatever the indexers agree on. There is no versioned specification with a changelog upstream, which is why this document quotes constants from code rather than citing clause numbers.
- L-6
Permanent UTXO growth. Every SRC-20 transaction adds unspendable entries to every full node's unspent output set, forever. This is an externality the protocol imposes on people who are not using it.
- L-7
Ticker squatting is trivial. Tickers are at most 5 code points, first-deploy-wins, and cost only a transaction fee to claim.
7. Security considerations
For users
- Confirmation is not success. A transaction can confirm and the operation still be invalid. Check the status in an explorer.
- Lookalike tickers. The character set includes many visually confusable characters, and the length limit makes near-misses easy. Identify a token by its deploy transaction hash.
- Metadata is unverified. The
x,web,emailandtgfields of a DEPLOY are free text with no verification of any kind. - Failed operations still cost. The Bitcoin fee is paid and the dust in the data outputs is destroyed regardless of outcome.
- Never blind-sign. An SRC-20 transaction legitimately has several odd-looking outputs. That makes it a good disguise for something else, so read what you are signing.
For implementers
- Do not trust a single indexer for balances that back value. Compare ledger hashes across at least two independent implementations before settling anything.
- Reproduce historical behaviour exactly, including the pre-833000 numeric stripping. An indexer that is "more correct" than the reference is wrong, because the protocol is the agreement, not the ideal.
- Do not add a multisig fallback when the OLGA branch fails its length check. It looks like a bug fix and it forks the ledger.
- Handle reorgs by re-deriving, not by reversing operations. Clamped mints make an operation's effect context-dependent.
- Do not surface unconfirmed operations as balances. There is no honest pending state.
- Treat draft proposals as drafts. Implementing an unactivated escrow primitive because a proposal describes it produces state no other indexer shares.
8. Implementation checklist
Work through this against the test vectors.
Carrier
- Decode 1-of-3 bare multisig with exactly the shape
OP_1 pk1 pk2 pk3 OP_3 OP_CHECKMULTISIG. - Recognise all five burn keys, and require keyburn for multisig-carried SRC-20.
- Take 31 data bytes per data public key as
pubkey[1:-1], in output order. - ARC4-decrypt with the byte-reversed txid of the output spent by input 0.
- Decode OLGA P2WSH from outputs at index above 0, at height 865000 and above.
- Strip trailing zero padding from the concatenated OLGA buffer.
- Read the two-byte big-endian length, check the
stamp:prefix, and reject a buffer shorter than the declared length. - Give OLGA precedence, with no multisig fallback when it fails.
- Derive the creator from input 0's previous output and the destination from output 0.
Payload
- Reject unquoted exponent literals during JSON parsing.
- Compare
pcase-insensitively againstsrc-20. - Uppercase
opbefore dispatch, and accept only DEPLOY, MINT and TRANSFER. - Normalise empty strings to absent.
- Treat required-field checks as a superset test, and do not reject unknown extra fields.
Ticker
- Re-decode Latin-1 to UTF-8 where that succeeds.
- Enforce a maximum of 5 code points, not bytes.
- Check every character against the exact ASCII set and the exact emoji allowlist.
- Lowercase before every comparison, and compute the SHA3-256 tick hash from the lowercased form.
Numbers
- Enforce the range 0 to 18446744073709551615, and reject NaN.
- Apply the numeric regex, and remember which failures exclude and which merely invalidate.
- Truncate
maxandlimdown to integers. Do not truncateamt. - Enforce
decin 0 to 18, defaulting to 18 when absent. - Compare
amt's decimal places againstdecafter normalising away trailing zeros. - Apply the pre-833000 digit-and-dot stripping when replaying historical blocks.
State
- First deploy wins, in block-then-transaction order.
- Reject a DEPLOY whose
maxorlimis missing or zero, with statusDE. - Use
min(lim, max)as the effective per-mint limit. - Clamp to remaining supply, then to the per-mint limit, and keep the mint valid.
- Reject a mint only when total minted is already at or above
max. - Reject a transfer outright when the running balance is short. No partial fills.
- Maintain running balances and mint totals within a block.
- Ignore Counterparty-encoded SRC-20 at and above height 796000.
Operations
- Compute a per-block ledger hash and compare it against an independent implementation.
- Roll back and re-derive on reorg, over-rewinding rather than patching.
- Publish invalidations to consumers before publishing replacement state.
- Refuse reorgs at or below your finality depth.
- Expose no pending or mempool balance.