Normative
Reference
Field ledger, formulas, fee and size, limitations, checklist.
- Protocol
- DUST-20Registry id dust20, alias dust-20
- Chain
- BitcoinNetwork: mainnet
- Ownership model
- UTXOUnits are carried by satoshis in unspent outputs
- Carrier
- Inscription + spendDeploy and mint are inscribed; movement is a spend
- Decimals
- 0Balances are whole units only
- Document version
- 1.1.0Revised 2026-09-01
- Lifecycle
- ExperimentalNot a ratified multi-party standard
- Owning repository
- bitcoinuniverseio/dust-20Documentation source of truth
R.1 Operations
Four operations exist. Two are written messages carried by inscriptions. Two are outcomes of transaction shape that nobody writes down.
| Operation | Carrier | Required fields | Optional | Provenance |
|---|---|---|---|---|
| DeployCreates a ticker and fixes the satoshi backing rule for every one of its units. | Inscription | p op tick supply unit_sats max_sats | lim_sats | VERIFIED |
| MintCreates units in a Bitcoin output whose value equals the declared satoshi backing. | Inscription | p op tick amt sats | none | VERIFIED |
| TransferAn ordinary Bitcoin spend. There is no transfer inscription. | Bitcoin spend | none | none | UNIVERSE |
| BurnA derived outcome, not an operation anyone writes. | Bitcoin spend | none | none | UNIVERSE |
- Deploy
A deploy declares the maximum supply, the exact satoshis that back one unit, the total backing that follows from those two numbers, and an optional per-mint satoshi cap.
- Mint
A mint resolves an accepted deployment, states an amount of whole units, and states the satoshis backing them. The Bitcoin output that carries the inscription must hold exactly that many satoshis.
- Transfer
Units follow the satoshis that carry them, in ordinal first-in-first-out order. A whole unit that lands entirely inside one supported output survives; anything else is burned. No JSON payload participates.
- Burn
Units are burned when the satoshis carrying them are paid to miners as fees, sent to an output the indexer cannot attribute to an address, or split so that no whole unit fits inside a single output.
R.2 Field ledger
Every field of every operation. Byte counts are the range of the value literal in UTF-8, excluding the quotes. Each constraint is exactly what the decoder enforces.
| Field | Ops | Type | Req | Bytes | Constraint and rule | Provenance |
|---|---|---|---|---|---|---|
| p | deploy mint | string | yes | 7 | Exactly "dust-20". Content whose "p" is not the literal dust-20 is not a DUST-20 message and is ignored. The protocol identifier is matched literally. There is no aliasing, casing tolerance, or versioned variant. |
VERIFIED |
| op | deploy mint | string | yes | 4 or 6 | Exactly "deploy" or "mint". Any other operation value is rejected. No transfer or burn inscription exists. The production indexer rejects an inscription whose op is anything other than deploy or mint, including op: "transfer". |
VERIFIED |
| tick | deploy mint | string | yes | 1 to 64 | Unicode NFC text, 1 to 64 UTF-8 bytes, with no control characters, whitespace, or the URL delimiters / ? # \. The identity of a ticker is its NFC form case-folded to lower case. A mint resolves against that folded identity. The legacy guide says the ticker is not limited to four bytes but does not settle case handling or duplicates. The production indexer settles both: identity is NFC plus lower-case folding, and the first valid deployment of an identity wins. Later deploys of the same identity are rejected. |
UNIVERSE |
| supply | deploy | uint128 string | yes | 1 to 39 | Strict positive decimal string matching /^[1-9][0-9]*$/, at most 2^128 - 1. Total whole units that may ever be minted for this ticker. No leading zeros, no sign, no decimal point, no exponent, no whitespace. DUST-20 balances have zero decimal places, so a unit is always a whole number. |
VERIFIED |
| unit_sats | deploy | sats string | yes | 1 to 16 | Strict positive decimal string, at most Bitcoin’s monetary supply of 2,100,000,000,000,000 satoshis. The exact number of satoshis that back one unit for the entire life of the ticker. 546 is a common documented example because it is Bitcoin’s usual dust threshold for P2WPKH. It is not a default. A payload that omits unit_sats is rejected, so never assume 546. |
VERIFIED |
| max_sats | deploy | sats string | yes | 1 to 16 | Strict positive decimal string that equals supply × unit_sats and does not exceed 2,100,000,000,000,000. Rejected unless it equals supply × unit_sats exactly, computed with exact integer arithmetic. This is a redundancy check, not an independent quantity. It exists so a reader can detect a builder that computed the backing with floating point or the wrong unit size. |
VERIFIED |
| lim_sats | deploy | sats string | no | 1 to 16 | Optional strict non-negative decimal string, at most max_sats. Omitted or "0" means no per-mint limit. When present and greater than zero, every mint of this ticker must satisfy sats ≤ lim_sats. The cap is expressed in satoshis, not units. The equivalent unit cap is lim_sats ÷ unit_sats. A lim_sats that is not a whole multiple of unit_sats simply makes the effective unit cap the floor of that division. |
VERIFIED |
| amt | mint | uint128 string | yes | 1 to 39 | Strict positive decimal string matching /^[1-9][0-9]*$/, at most 2^128 - 1. Whole units to create. Rejected if previously minted units plus amt would exceed supply. Zero, negative, fractional, and leading-zero values are rejected outright. |
VERIFIED |
| sats | mint | sats string | yes | 1 to 16 | Strict positive decimal string that equals amt × unit_sats of the resolved deployment. Rejected unless it equals amt × unit_sats, and unless the Bitcoin output carrying the inscription holds exactly this many satoshis. Three numbers must agree: the deployment ratio, the sats field in the JSON, and the real output value on Bitcoin. A one-satoshi difference invalidates the mint. |
VERIFIED |
R.3 Formulas
The only arithmetic in DUST-20. All of it must be evaluated with exact integers: a 64-bit float loses precision above 253, which is well inside the satoshi range.
| Expression | Applies to | Meaning | Rule | Provenance |
|---|---|---|---|---|
| max_sats = supply × unit_sats | deploy | Total backing is supply multiplied by the satoshis behind one unit. 1,000,000 units × 546 sats = 546,000,000 sats |
DUST-5.4 | VERIFIED |
| sats = amt × unit_sats | mint | The satoshis a mint declares are the units it creates multiplied by the unit size. 100 units × 546 sats = 54,600 sats |
DUST-6.4 | VERIFIED |
| output_value = sats | mint | The Bitcoin output that carries the mint inscription must hold exactly the declared satoshis. A mint declaring 54,600 sats must sit in an output worth exactly 54,600 sats. |
DUST-6.7 | VERIFIED |
| mint_unit_cap = floor(lim_sats ÷ unit_sats) | deploy | A satoshi cap per mint implies a whole-unit cap per mint. 54,600 ÷ 546 = 100 units per mint |
DUST-5.6 | UNIVERSE |
| input_units = surviving_units + burned_units | transfer | Units leaving an input either land whole inside a supported output or are burned. Nothing else can happen to them. 91 units in produces 10 to the receiver, 81 as change, 0 burned |
DUST-7.4 | UNIVERSE |
| output_backing = units_in_output × unit_sats | transfer | Every output carrying units must hold at least the satoshis those whole units occupy, positioned so each unit fits entirely inside it. 81 units × 546 = 44,226 sats of backing |
DUST-7.4 | UNIVERSE |
| fee = sum(inputs) - sum(outputs) | transfer | The miner fee is whatever the transaction does not pay out. Any colored satoshi that falls into that gap is burned, so fees must be funded by ordinary bitcoin. Fund the fee from a separate cardinal input, never by shrinking a colored output. |
DUST-7.8 | UNIVERSE |
R.4 Fee and size considerations
Payload bytes are almost never the expensive part of a DUST-20 transaction. Locked backing and output count are.
- Payload size is small and nearly constant
A deploy payload is typically 110 to 130 bytes of JSON and a mint is 60 to 70 bytes. Both sit in the witness, so they are discounted to one weight unit per byte. Payload size is almost never what makes a DUST-20 transaction expensive.
- Backing is locked capital, not a fee
A mint of 100 units at 546 satoshis per unit puts 54,600 satoshis into an output and keeps them there for as long as you hold the units. Sizing a deployment is a decision about how much bitcoin every holder must immobilise.
- unit_sats below the relay dust threshold is valid but unspendable
DUST-20 permits any positive unit_sats, and a reader will accept it. Bitcoin nodes will not relay an output below the standard dust threshold for its script type, which is 546 satoshis for P2WPKH and 330 for P2TR. Check the threshold for the output type you will actually use.
- Colored change adds an output, and outputs cost more than payload bytes
A safe partial send needs a receiver output, a colored change output, a cardinal change output and at least one cardinal input. Budget for that shape rather than for the smallest transaction that would confirm.
- Never let the fee come out of backing
A wallet that subtracts the fee from the largest output will silently shrink a colored output, move the colored span, and burn units. Fee funding must come from cardinal inputs, and the builder must verify every colored output value after the fee is applied.
- Consolidate deliberately, not automatically
Merging allocations of one identity into a single output is valid and reduces future fees. Automatic consolidation that treats colored outputs as ordinary change will destroy units, so consolidation must be an explicit DUST-20 aware operation.
Worked cost shape for a safe partial send
Sending 10 of 91 units of a ticker deployed at 546 satoshis per unit needs: one colored input (49,686 sats), one cardinal input to fund the fee, a receiver output of 5,460 sats, a colored change output of 44,226 sats, and a cardinal change output. Five entries, not two. Budget for that shape rather than for the smallest transaction that would confirm.
R.5 Limitations
What DUST-20 cannot do, and what it costs to use. None of these are bugs; they follow from binding units to satoshis.
- Validity is an interpretation, not consensus
Bitcoin does not know DUST-20 exists. Every balance is one reader’s conclusion from applying these rules to the chain. Two readers that differ on a rule will differ on a balance, and Bitcoin will not settle the argument.
- Most transfer behaviour is settled by one implementation
The legacy specification documents deploy and mint and one colored-UTXO transfer example. Ordinal flow, burn conditions, ticker identity and output ordering are settled here by the Bitcoin Universe production indexer. Rules labelled UNIVERSE are a working profile, not a multi-party agreement.
- Holding units immobilises bitcoin
Backing is real bitcoin sitting in an output. A large supply at a large unit_sats can require more bitcoin than the deployer expects, and DUST-5.5 rejects a deployment that would require more than exists.
- Units are indivisible and non-fractional
There are no decimals. A holder cannot send half a unit, and a unit that cannot be placed whole in an output is destroyed rather than rounded.
- There is no reliable unconfirmed view
The production indexer declares partial coverage: no exhaustive mempool feed, no stable pending lifecycle, no disappearance handling. Applications must not present unconfirmed DUST-20 state as settled.
- Trading DUST-20 inside Bitcoin Universe is not available
Marketplace availability for DUST-20 is read-only. Listing, buying, offers and settlement are all unavailable until a typed authoritative ownership resolver exists. See the Universe support page for the exact recorded reason.
- Replacement can change the outcome
Replace-by-fee can alter the output layout, which changes where colored satoshis land. A replacement can burn units the original would have preserved, so state must be derived from the transaction that actually confirmed.
R.6 Implementation checklist
Everything a conforming implementation has to do, grouped by the part of the system that does it. Each line names the rule it satisfies.
Reader
| Requirement | Rule |
|---|---|
Decode content as UTF-8 and reject anything over 4096 bytes. | DUST-2.2, DUST-2.3 |
Reject nested values, non-string values, duplicate keys and trailing content. | DUST-2.4 to DUST-2.6 |
Parse every quantity as a strict decimal string into an exact integer type. | DUST-3.1, DUST-3.5 |
Enforce the exact key set per operation; never ignore an unknown field. | DUST-5.1, DUST-6.1 |
Record rejected payloads as invalid events rather than discarding them. | DUST-9.7 |
Identity and state
| Requirement | Rule |
|---|---|
Fold tickers with NFC plus lower case, and keep the written spelling for display. | DUST-4.5, DUST-4.6 |
Apply first-deploy-wins by height, then transaction index, then inscription order. | DUST-5.7 |
Check every mint against the resolved deployment, not against its own payload. | DUST-6.2 |
Compare the declared sats against the real output value before accepting a mint. | DUST-6.7 |
Reject a mint into an output that already carries an allocation. | DUST-6.9 |
Allocation
| Requirement | Rule |
|---|---|
Store a satoshi offset with every allocation. | DUST-7.2 |
Compute movement by concatenating input and output ranges in index order. | DUST-7.3 |
Keep only whole units, and record the difference as burned. | DUST-7.4, DUST-8.3 |
Treat output order as significant and never normalize it away. | DUST-7.5 |
Account for fee burns and unattributable-output burns in supply. | DUST-8.1, DUST-8.2, DUST-8.4 |
Publication
| Requirement | Rule |
|---|---|
Give every event a stable source event id. | DUST-9.1 |
Require complete block placement on confirmed events and none on pending events. | DUST-9.2, DUST-9.3 |
Balance wallet deltas per operation, and reject an address appearing twice. | DUST-9.4 |
Emit deploys with no economic effect. | DUST-9.5 |
Publish a coverage label and a checkpoint with every answer. | DUST-9.7 |
Invalidate newest first on a reorganization and replay the replacement branch. | DUST-9.6 |
Wallet and application
| Requirement | Rule |
|---|---|
Show the receiver, colored change, cardinal funding and fee as distinct roles before signing. | DUST-7.7 |
Block a partial send that has no colored change output. | DUST-7.7 |
Fund fees from cardinal inputs and re-verify colored output values after fee application. | DUST-7.8 |
Revalidate outpoints at build time, not at page load. | DUST-9.7 |
Treat disagreement between two readers as a blocking error, not a choice. | DUST-9.7 |
R.7 Where sources disagree
The legacy specification and the current implementation do not agree about everything, and earlier versions of this document left several questions open. Both positions are published here rather than silently resolved.
Is max_sats required in a deploy?
VERIFIED- Legacy position
The legacy profile includes max_sats and frames it as supply × unit_sats. Earlier versions of this documentation additionally reported that a builder emitted deploys without max_sats, and labelled the question an unreconciled compatibility split.
- Current implementation
The production indexer requires max_sats. Its deploy key set is exactly p, op, tick, supply, unit_sats, max_sats, with lim_sats optional, and a deploy missing max_sats is rejected rather than inferred.
- Resolution
Resolved in favour of required. Emit max_sats and verify it equals supply × unit_sats. A deploy without it is not indexed by the current implementation.
Is there an official transfer payload?
UNIVERSE- Legacy position
The legacy guide documents deploy and mint JSON and a colored-UTXO transfer example, but publishes no transfer schema. Earlier documentation listed a transfer inscription as an open question and noted an application emitting a transfer preview containing amt.
- Current implementation
The production indexer accepts no transfer inscription at all. Movement is derived purely from ordinal satoshi flow through ordinary Bitcoin spends.
- Resolution
Resolved: there is no transfer payload. Treat any op: "transfer" inscription as invalid, and treat an application preview containing amt as a user-interface artifact, not a protocol message.
How are duplicate and differently-cased tickers handled?
UNIVERSE- Legacy position
The legacy guide states the ticker is not limited to four bytes but does not settle case normalization or which deployment wins when a name is reused.
- Current implementation
Identity is the NFC form case-folded to lower case, bounded to 64 UTF-8 bytes, excluding control characters, whitespace and the delimiters / ? # \. The first valid deployment of an identity wins; later ones are rejected.
- Resolution
Resolved by the current implementation. Independent implementations that compare tickers byte-for-byte will disagree with it, so preserve the original spelling for display and compare on the folded identity.
What counts as a burn?
UNIVERSE- Legacy position
The legacy material defines no burn rule, and earlier documentation advised showing recovery actions as potentially allocation-destroying rather than as settled burns.
- Current implementation
Burning is defined precisely and is a consequence of transaction shape, not an instruction: satoshis paid as fees, sent to an unsupported output, or split so no whole unit fits are burned, and the supply accounting records it.
- Resolution
Resolved as a derived outcome. There is still no way to express "burn this" as a message. You burn by constructing a transaction that drops the satoshis.
Does output order matter?
UNIVERSE- Legacy position
Earlier documentation listed output ordering as an open question.
- Current implementation
Order matters completely. Allocation is positional: inputs and outputs are concatenated into satoshi ranges in index order, so moving an output changes which units land where.
- Resolution
Resolved: build outputs deliberately and test the exact ordering your builder produces. Reordering outputs is never cosmetic.
Is there one official DUST-20 indexer API?
UNRESOLVED- Legacy position
No API schema, pagination model, or response versioning is published in the legacy guide.
- Current implementation
The production indexer exposes its own normalized and legacy routes with bearer authentication, stable cursors and explicit coverage labels, but these are one service’s contract.
- Resolution
Unresolved as a standard. Validate remote responses against a schema you control and treat coverage, source identity and observation height as part of every answer.
R.8 Open questions
Questions with no authoritative answer. They are listed rather than invented, with the posture that keeps you safe while they stay open.
Do independent implementations agree with these rules?
Why it matters. Most of the transfer, burn and ticker-identity behaviour on this site is settled by one production implementation rather than by a multi-party specification.
Safe posture. Treat rules marked UNIVERSE as a working profile. Before relying on them across tools, test against the specific reader your users will see.
How many confirmations make a DUST-20 balance safe to act on?
Why it matters. The protocol defines no threshold, and reorg handling restores or removes allocations depending on depth.
Safe posture. Publish your own finality depth, show confirmation counts to users, and never present unconfirmed DUST-20 state as settled.
What is the pending lifecycle for an unconfirmed DUST-20 movement?
Why it matters. The production indexer reports partial coverage precisely because it has no exhaustive mempool feed with stable pending and disappearance handling.
Safe posture. Do not build balance guarantees on mempool state. Show pending movements as unverified and re-check at confirmation.
What happens to DUST-20 state when a transaction is replaced?
Why it matters. Replace-by-fee can change the output layout, which changes where colored satoshis land, so a replacement can burn units the original preserved.
Safe posture. Re-derive allocation from the transaction that actually confirmed. Never carry forward the pre-replacement interpretation, and never blindly rebroadcast.
What would a typed authoritative ownership resolver look like?
Why it matters. This is the exact blocker recorded against DUST-20 marketplace mutations: a boolean "is this output still good" answer cannot prove amount, owner, schema, freshness or settlement authority.
Safe posture. A resolver has to return the identity, the exact amount, the owning address, the outpoint, the observation height and the coverage label, and it has to be first-party. Until one exists, DUST-20 stays read-only in Bitcoin Universe products.