DUST-20 REV 1.1.0

Normative

Normative specification

Numbered rules, encoding, identifiers, operations and state transitions.

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

1 Scope and conventions

This section defines what the following rules cover, how they are numbered, and how to read a provenance label. Sections 2 to 9 are normative. Sections 10 to 12 restate the same rules as tables for implementers.

1.1 Scope

DUST-20 is a fungible token protocol on Bitcoin mainnet. It defines two written messages, deploy and mint, both carried as inscription content, and it defines how token units follow satoshis through ordinary Bitcoin spends. It defines no consensus change, no script template and no new transaction field. Bitcoin nodes do not know DUST-20 exists.

1.2 Rule identifiers

Every rule has a stable identifier of the form DUST-<section>.<index>, for example DUST-6.7. Identifiers do not change between document versions. A rule that is withdrawn keeps its number and is marked withdrawn rather than being reused. Each rule links to the conformance vector that exercises it, where one exists.

1.3 Requirement wording

A rule that says a payload is rejected means a conforming reader must not index it, must not repair it, and must not fall back to a partial interpretation. Rejection is total: there is no warning level, and no rule degrades to "ignore what you do not understand".

1.4 Provenance

DUST-20 is not a ratified multi-party standard. Every rule carries a label saying which source supports it, so an implementer can see the difference between an agreed protocol rule and one production implementation's decision.

Provenance labels used throughout this document
LabelMeaning
VERIFIED

Documented in the legacy DUST-20 specification and enforced by the Bitcoin Universe production indexer. Both sources agree.

UNIVERSE

Enforced by the Bitcoin Universe production indexer. The legacy specification does not settle this point, so an independent implementation may differ.

LEGACY

Stated by legacy DUST-20 documentation. Superseded, narrowed, or not enforced identically by the current implementation.

IMPL

The behaviour of one application or service. Useful evidence, but not a protocol rule. Do not rely on it across implementations.

UNRESOLVED

No authoritative answer exists. Documented as an open question rather than invented. Stop before an irreversible step.

1.5 Sources

Sources this document is built from
SourceWhat it provides
Legacy DUST-20 specification

Documents the deploy and mint payloads and a colored-UTXO transfer example.

Bitcoin Universe production indexer

index-dust20 reconstructs colored UTXO ownership from Bitcoin Core and a shared Ord 0.29 instance.

Bitcoin Universe capability registry

Records, per protocol, which product surfaces and marketplace actions are actually available.

This documentation repository

Source of the published documentation and its conformance fixtures.

2 Message carrier

DUST-20 defines two written messages, deploy and mint. Both are carried as inscription content on Bitcoin. Movement is not a written message at all: it is the shape of an ordinary spend, covered in section 7.

Deploy and mint are inscription content

A DUST-20 deploy or mint is the content of a Bitcoin inscription. There is no OP_RETURN carrier, no separate script template, and no auxiliary transaction field. A reader obtains authoritative inscription identity, content and location from an inscription index and obtains transaction values and addresses from a Bitcoin node.

VERIFIED

Content is UTF-8

Inscription content is decoded as UTF-8. Content that is not well-formed UTF-8 is not a DUST-20 message.

UNIVERSE

Content larger than 4096 bytes is not a candidate

A reader considers content of at most 4096 bytes. Larger content is not evaluated as DUST-20.

UNIVERSE

Content is exactly one JSON object

Content must parse as a single JSON object. Nothing may follow the closing brace except insignificant whitespace. Trailing content makes the message ambiguous and is rejected.

Every value is a JSON string

The object is flat and every value is a JSON string. Nested objects, arrays, numbers, booleans and null are rejected. JSON numbers cannot carry values above 2^53 exactly, so a bare number is never accepted for a quantity.

Duplicate keys are rejected

A key that appears more than once is rejected rather than resolved last-wins or first-wins. Two readers must never be able to disagree about which value applied.

Raw control characters are rejected

A raw character below U+0020 inside a JSON string makes the content invalid. Escaped sequences that decode to control characters are still subject to the ticker rules in section 4.

UNIVERSE

3 Integer encoding

Every quantity in DUST-20 is a decimal string in strict form. This exists so that two implementations reading the same bytes compute the same number, and so that a reader cannot silently accept a value produced by floating-point arithmetic.

Positive quantities match /^[1-9][0-9]*$/

supply, unit_sats, max_sats, amt and sats are strict positive decimal strings. A leading zero, a leading plus or minus, whitespace, a decimal point, a thousands separator and exponent notation are all rejected.

Optional caps match /^(0|[1-9][0-9]*)$/

lim_sats is a strict non-negative decimal string. The single character "0" is valid and means no per-mint limit.

Satoshi fields are bounded by the money supply

unit_sats, max_sats, lim_sats and sats may not exceed 2,100,000,000,000,000 satoshis, the total that will ever exist. A payload can be internally consistent and still exceed this bound, in which case it is rejected.

Unit quantities are bounded by 2^128 - 1

supply and amt may not exceed 340,282,366,920,938,463,463,374,607,431,768,211,455. This is the atomic ceiling the production indexer enforces on token quantities.

UNIVERSE

Arithmetic is exact integer arithmetic

An implementation must evaluate supply × unit_sats and amt × unit_sats with exact integers. A 64-bit float loses precision above 2^53, which is well inside the satoshi range, and a rounded product produces a rejected payload that looks correct on screen.

Numeric ceilings a reader enforces
QuantityCeilingRule
Satoshi fields2,100,000,000,000,000DUST-3.3
Unit quantities340282366920938463463374607431768211455DUST-3.4
Content size4096 bytesDUST-2.3
Ticker length1 to 64 bytesDUST-4.3
Decimal places0DUST-5.8

4 Identifiers

Three fields identify a message: the protocol tag, the operation, and the ticker. The ticker is the only one with a normalization step, and getting that step wrong is the most common source of disagreement between implementations.

p is the literal string dust-20

Content whose p field is absent or is anything other than the seven bytes dust-20 is not a DUST-20 message. There is no case tolerance, no alias, and no versioned variant of the tag.

op is deploy or mint

Exactly two operation values exist. Any other value, including transfer, is rejected.

tick is 1 to 64 UTF-8 bytes in NFC form

The ticker must already be in Unicode Normalization Form C, and its UTF-8 encoding must be between 1 and 64 bytes. Length is measured in bytes, not code points, so a multi-byte ticker is shorter in characters than the bound suggests.

UNIVERSE

tick excludes control characters, whitespace and / ? # \

These characters are excluded so a ticker identity is always safe to place in a path or query without escaping, and so two tickers cannot look identical in a log line.

Identity is the NFC form folded to lower case

Two tickers are the same ticker when their NFC forms are equal after lower-case folding. DUST, dust and Dust are one identity. Comparison must happen on the folded identity, never on raw bytes.

The original spelling is preserved for display

An implementation stores the folded identity for comparison and the spelling as written for display. Replacing the display form with the folded form loses information a deployer chose deliberately.

UNIVERSE

5 Deploy

A deploy creates a ticker and fixes, permanently, how many satoshis stand behind one of its units. Everything a mint or a transfer later computes depends on the numbers written here.

The key set is exact

A deploy contains exactly p, op, tick, supply, unit_sats and max_sats, and may contain lim_sats. A missing required key and an unrecognised extra key both reject the whole payload. Unknown fields are not ignored.

supply is the permanent unit ceiling

supply is the total whole units that may ever be minted for this ticker. It cannot be raised, lowered or reissued by any later message.

VERIFIED

unit_sats is the permanent backing ratio

unit_sats is the exact number of satoshis that back one unit, for the entire life of the ticker. There is no default: a deploy that omits unit_sats is rejected, and 546 is an example, not a fallback.

VERIFIED

max_sats equals supply × unit_sats

max_sats is a redundancy check, not an independent quantity. A deploy whose max_sats differs from the exact product by even one satoshi is rejected rather than corrected.

max_sats may not exceed the money supply

A deployment whose total backing exceeds 2,100,000,000,000,000 satoshis can never be fully minted, so it is rejected at deploy time rather than at the mint that would cross the line.

lim_sats is an optional per-mint satoshi cap

When absent or "0", one mint may take the entire supply. When present and positive it may not exceed max_sats, and every mint must satisfy sats ≤ lim_sats. The equivalent unit cap is the floor of lim_sats ÷ unit_sats.

The first valid deployment of an identity wins

Once a valid deployment exists for a folded ticker identity, every later deployment of that identity is rejected. Ordering is by block height, then transaction index within the block, then inscription order within the transaction.

There are no decimals

DUST-20 balances have zero decimal places. No decimals field exists, and supplying one rejects the payload under DUST-5.1.

UNIVERSE

5.x Deploy field set

{"p":"dust-20","op":"deploy","tick":"dust","supply":"1000000","unit_sats":"546","max_sats":"546000000","lim_sats":"54600"}

Required: p, op, tick, supply, unit_sats, max_sats. Optional: lim_sats. Nothing else is permitted.

6 Mint

A mint creates units. It is the only point where a written message and the physical shape of a Bitcoin output have to agree exactly, and the only place where an error cannot be repaired after broadcast.

The key set is exact

A mint contains exactly p, op, tick, amt and sats. There are no optional mint fields.

A mint resolves exactly one accepted deployment

The mint names a ticker; the reader folds it to its identity and finds the accepted deployment for that identity. A mint for an identity with no accepted deployment is rejected. Every ratio used to check the mint comes from the resolved deployment, never from the mint payload.

amt is a positive whole number of units

amt is a strict positive decimal string. A mint of zero units does not exist, and neither does a fractional one: there are no decimals, so the smallest possible mint is one whole unit backed by unit_sats satoshis.

sats equals amt × unit_sats

The satoshis a mint declares are the units it creates multiplied by the resolved deployment ratio. sats is never inferred from amt: omitting it rejects the payload.

sats respects the deployment per-mint cap

When the resolved deployment declares a positive lim_sats, a mint whose sats exceeds it is rejected in full.

Mints stop at the declared supply

Previously minted units plus amt must not exceed supply. There is no partial fill: a mint that would cross the ceiling is rejected in full rather than trimmed to the remainder.

The carrying output holds exactly sats satoshis

Three numbers must agree: the deployment ratio, the sats field in the JSON, and the real value of the Bitcoin output carrying the inscription. A one-satoshi difference invalidates the mint, and the transaction still confirms normally.

The inscription sits at satoshi offset 0 of that output

The mint inscription must begin at the first satoshi of the output that carries it. Padding that shifts the inscription away from offset 0 invalidates the mint.

UNIVERSE

The carrying output must not already hold an allocation

A mint cannot be layered on top of an output that already carries a DUST-20 allocation. Two allocations claiming the same satoshis would make ownership undecidable.

UNIVERSE

6.x Mint field set

{"p":"dust-20","op":"mint","tick":"dust","amt":"100","sats":"54600"}

Required: p, op, tick, amt, sats. There are no optional mint fields.

The three numbers that must agree

The deployment ratio (unit_sats), the declared backing (sats) and the real value of the Bitcoin output all have to match. A one-satoshi difference makes the mint invalid, and the transaction still confirms. Nothing can repair it afterwards.

7 Allocation and movement

After a mint, DUST-20 stops using messages. An allocation is a span of satoshis inside an output, and moving units means spending those satoshis. Everything in this section is derived from transaction shape, which is why output order and output size are protocol-significant.

No transfer message exists

DUST-20 uses inscriptions only for deploy and mint. An inscription whose op is transfer is invalid, moves nothing, and must not be treated as a transfer by any implementation.

An allocation is an outpoint, an identity, a ratio, an amount and an offset

The offset is the satoshi position at which the allocation begins inside its output. Without it the next spend cannot be computed, so an implementation that stores only an amount per outpoint is incomplete.

UNIVERSE

Satoshis map input to output in ordinal first-in-first-out order

Lay every input value end to end in input index order to form one satoshi range, and every output value end to end in output index order to form another. The n-th satoshi of the input range is the n-th satoshi of the output range. Satoshis past the end of the output range are the miner fee.

UNIVERSE

A unit survives only when its whole block lands in one supported output

Each unit is a contiguous block of unit_sats satoshis inside the allocation span. It survives where that entire block falls inside a single output the reader can attribute to a supported address. A block split across an output boundary survives nowhere.

UNIVERSE

Output order is protocol-significant

Allocation is positional. Moving an output, changing its value, or inserting one changes which satoshis land where and therefore which units survive. Reordering outputs is never cosmetic.

UNIVERSE

Allocations merge when identity and ratio match

Several allocations of the same ticker identity can be combined into one output, provided every contributing unit lands whole inside it. Allocations of different identities keep separate records even inside one output, tracked by their own offsets.

UNIVERSE

A partial send requires an explicit colored change output

Spending an allocation of 91 units and sending 10 requires a second output holding at least 81 × unit_sats satoshis, positioned so each unit fits whole. Omitting that output does not keep the remainder: whole units land in whatever supported output the satoshis reach next, and the rest fall into the fee and are burned.

VERIFIED

Miner fees are funded from cardinal inputs

Satoshis that become the fee are past the end of the output range, so any units they carried are destroyed. A builder adds an ordinary bitcoin input to pay the fee and never shaves satoshis off a colored output.

VERIFIED
Ordinal first-in-first-out satoshi mapping through a spend Input values are concatenated in index order into one satoshi range and output values into another. A colored allocation of 91 units at 546 satoshis each occupies a contiguous span in the input range. Ten whole units land in the receiver output, eighty-one land in the colored change output, and the satoshis past the end of the output range become the miner fee. INPUT RANGE, CONCATENATED IN INPUT INDEX ORDER OUTPUT RANGE, CONCATENATED IN OUTPUT INDEX ORDER in[0] colored allocation 91 units x 546 = 49,686 sats in[1] cardinal 20,000 sats out[0] 10 u out[1] colored change 81 units x 546 = 44,226 sats out[2] cardinal change 18,000 sats FEE 1,774 The n-th satoshi of the input range is the n-th satoshi of the output range. Nothing reorders it. Every unit is a block of 546 satoshis. A block survives only where it lands entirely inside one supported output (rule DUST-7.4).
Fig 7.1 A safe partial send. The colored change output at index 1 is what keeps the remaining 81 units alive; without it they fall past the colored span into cardinal change or fee and are destroyed.

8 Burn

Burning is a consequence of transaction shape, not an instruction. There is no way to write "burn this", and there is no way to undo it once the transaction confirms.

Units paid as fees are burned

Satoshis that fall past the last output become the miner fee. Any whole unit inside that range is destroyed.

UNIVERSE

Units sent to an unattributable output are burned

When a reader cannot attribute an output to a supported address, units landing in it are destroyed rather than held in an unknown state.

UNIVERSE

Units split across an output boundary are burned

A unit is atomic. If its block of unit_sats satoshis straddles two outputs, no fraction survives in either.

UNIVERSE

Burns are recorded in supply accounting

A burn decreases total and circulating supply by the burned amount and increases burned supply by the same amount. Minted supply is unchanged: a burn does not free supply for reminting.

UNIVERSE

A burn is silent on Bitcoin

The transaction confirms normally. No node, no wallet and no block explorer flags it. Only a DUST-20 reader knows anything was destroyed.

UNIVERSE

9 Indexer semantics

These rules govern what a reader publishes rather than what the chain contains. They are drawn from the event contract the Bitcoin Universe production indexer enforces on every accepted batch.

Every event carries a stable source event id

An event is identified by an id that does not change between observations of the same chain fact. Downstream consumers deduplicate on it.

UNIVERSE

A confirmed event carries complete block placement

An event marked confirmed must carry block height, block hash, transaction index and event index. Partial placement is rejected: an event either has a full position in the chain or it is pending.

UNIVERSE

A pending event carries no block placement

An event marked pending must not carry a height, hash, transaction index, event index or a non-zero confirmation count. Mixing the two states hides whether a balance is settled.

UNIVERSE

Wallet deltas balance per operation

For a transfer the wallet deltas sum to zero. For a mint they sum to the minted amount, and for a burn to the negative of the burned amount. Each delta must equal its own received minus sent, and an address may appear only once per event.

UNIVERSE

A deploy carries no economic effect

A deploy event states metadata only. It carries no amount, no sender, no receiver, no wallet deltas and no supply movement. Supply appears when units are minted, not when a ticker is created.

UNIVERSE

Reorganizations are replayed, not patched

On a reorganization a reader emits invalidations newest first, reverses ledger mutations, restores allocations that had been marked spent, removes allocations the orphaned branch created, and replays the replacement branch from the fork point.

UNIVERSE

Coverage is declared, not assumed

A reader publishes its coverage as complete, partial or unavailable alongside a checkpoint of network, height, hash, finalized height and observation time. A consumer that ignores the coverage label cannot tell a quiet ticker from a stalled indexer.

UNIVERSE

Mempool coverage is partial

The production indexer reports partial coverage precisely because it has no exhaustive mempool feed with a stable pending lifecycle and disappearance handling. Unconfirmed DUST-20 state is not a reliable input to an irreversible decision.

UNIVERSE

10 Transaction anatomy

The same rules, arranged by where they apply in a transaction. Use this when you are checking a construction rather than reading the protocol.

10.1 Mint transaction

A mint is a reveal transaction. The payload lives in the witness; the value that matters lives in the output.
PartRoleRequirementVerified against
Input 0 Commit outpoint

Spends the commit output that carries the inscription envelope.

Inscription index
Witness Inscription envelope

Holds the content type and the JSON payload. Content is UTF-8, at most 4096 bytes for a DUST-20 candidate.

Inscription index
Output 0 Carrying output

Value must equal the sats field exactly. The inscription must sit at satoshi offset 0. The output must not already carry an allocation.

Bitcoin node + inscription index
Output 1..n Cardinal change

Ordinary bitcoin. Carries no units and is not evaluated.

Bitcoin node
Fee Miner payment

Funded from cardinal value. Satoshis that reach the fee are past the output range and carry nothing.

Derived

10.2 Transfer transaction

A transfer is an ordinary spend. Nothing marks it as DUST-20; the units follow the satoshis.
PartRoleRequirementVerified against
Input 0..i Colored inputs

Outputs that carry allocations. Each contributes a span at a known offset into the concatenated input range.

Reader allocation ledger
Input i+1..n Cardinal inputs

Ordinary bitcoin added to fund the fee without disturbing colored spans.

Bitcoin node
Output 0..j Colored outputs

Sized and ordered so each surviving unit lands whole inside one of them. A partial send needs an explicit colored change output.

Derived from ordinal flow
Output j+1..m Cardinal outputs

Ordinary change. Units landing here are still tracked if the address is supported, which is usually a construction error.

Derived from ordinal flow
Fee Miner payment

Any colored satoshi that falls into the fee gap is burned.

Derived

11 State transitions

Every state transition DUST-20 defines
FromEventToRuleAccounting
No ticker

Valid deploy inscription

Ticker deployed, 0 minted DUST-5.1 to DUST-5.7 The identity is claimed permanently. A second deploy of the same identity changes nothing.
Ticker deployed

Valid mint inscription in an output of exactly sats satoshis

Allocation live at that outpoint, offset 0 DUST-6.1 to DUST-6.9 Minted supply increases by amt. Total and circulating supply increase by amt.
Allocation live

The carrying output is spent, whole units land in supported outputs

Allocation spent, new allocations live DUST-7.3, DUST-7.4 Wallet deltas sum to zero across the event. Supply is unchanged.
Allocation live

The carrying output is spent, some units land nowhere whole

Allocation spent, remainder burned DUST-8.1 to DUST-8.4 Total and circulating supply fall by the burned amount. Minted supply is unchanged.
Allocation live at confirmed depth

The confirming block is orphaned by a reorganization

Allocation invalidated, prior state restored, replacement branch replayed DUST-9.6 Invalidations are emitted newest first. A balance is only as final as the depth it was confirmed at.
Pending event

The transaction is replaced or evicted from the mempool

No event DUST-9.3, DUST-9.8 Coverage is partial, so a pending event may disappear without a stable notification.

12 Invalidity conditions

Every way a payload or a construction can be rejected, with the issue code a reader emits and the rule that defines it. The decoder emits exactly these codes, and the test vectors assert them.

Issue codes a reader emits, and the rule each one enforces
Issue codeRuleCondition
not_object DUST-2.4

Content is not a single JSON object.

not_flat_strings DUST-2.5

A value is not a JSON string, or the object is nested.

duplicate_key DUST-2.6

A field appears more than once.

trailing_content DUST-2.4

Content continues after the closing brace.

content_too_large DUST-2.3

Content exceeds 4096 bytes and is not evaluated.

bad_protocol DUST-4.1

p is not the literal string dust-20.

bad_operation DUST-4.2

op is not deploy or mint.

bad_tick DUST-4.3, DUST-4.4

The ticker is not NFC, is outside 1 to 64 bytes, or contains an excluded character.

missing_field DUST-5.1, DUST-6.1

A required field of the operation is absent.

unknown_field DUST-5.1, DUST-6.1

A field outside the operation key set is present.

bad_integer DUST-3.1, DUST-3.2

A quantity is not a strict decimal string.

exceeds_money_supply DUST-3.3, DUST-5.5

A satoshi quantity exceeds the total that will ever exist.

max_sats_mismatch DUST-5.4

max_sats does not equal supply × unit_sats.

lim_sats_above_max DUST-5.6

lim_sats exceeds max_sats.

duplicate_deployment DUST-5.7

The ticker identity already has an accepted deployment.

deployment_missing DUST-6.2

The mint resolves no accepted deployment.

mint_sats_mismatch DUST-6.4

sats does not equal amt × unit_sats for the resolved deployment.

mint_exceeds_limit DUST-6.5

sats exceeds the deployment per-mint cap.

mint_exceeds_supply DUST-6.6

Minted units plus amt would exceed supply.

output_value_mismatch DUST-6.7

The carrying output value differs from sats.

allocation_overflows_input DUST-7.2

A claimed allocation does not fit inside the output it is recorded against.

outputs_exceed_inputs DUST-7.3

Outputs total more than inputs, so the transaction cannot be built.