Conformance
Test vectors
Cases a conforming reader must reproduce exactly. Every expected outcome is the behavior of the bitcoin-universe-block20-v1 profile, and every rejection cites the rule that causes it.
Setup
Shared fixture
Unless a vector says otherwise, all vectors share this state.
- Network
- Bitcoin mainnet
- Ticker
BLK, identityblk- Deploy height (D)
767430- Maximum supply
21000000- Per-mint limit
1000- Anchor block used
- 000000000000000000029730547464f056f8b6e2e0a02eaf69c24389983a04f5 at height
767430 - Freshness window (W)
144blocks- Finality depth
6confirmations
The anchor block above is a real Bitcoin mainnet block: it is the first height a BLOCK-20 reader scans on mainnet. Ticker, supply, and reveal heights are fixture values.
Anywhere a vector writes <HASH>, substitute that 64 character block ID. Run any mint vector through the anchor verifier to see the derivation evaluated step by step.
Group G
Payload grammar and field validation
| ID | Inscription content | Expected outcome | Rule |
|---|---|---|---|
| G1 | {"p":"block-20","op":"deploy","tick":"BLK","max":"21000000","lim":"1000"} | Accepted ticker blk created, all supply figures zero | R5.1 to R5.6 |
| G2 | {"p":"block-20","op":"mint","tick":"BLK","amt":"1","amt":"2","hash":"<HASH>"} | Rejected repeated field | R3.4 |
| G3 | {"p":"block-20","op":"mint","tick":"BLK","amt":1000,"hash":"<HASH>"} | Rejected value is not a JSON string | R3.3 |
| G4 | {"p":"block-20","op":"transfer","tick":"BLK","amt":"250"} extra | Rejected trailing content | R3.2 |
| G5 | {"p":"block-20","op":"transfer","tick":"BLK","amt":{"v":"250"}} | Rejected nested object | R3.3 |
| G6 | {"p":"block-20","op":"mint","tick":"BLK","amt":"1000","hash":"<HASH>","memo":"hi"} | Rejected unknown field on a mint | R3.8 |
| G7 | {"p":"block-20","op":"deploy","tick":"BLK","max":"21000000"} | Rejected missing required lim | R3.8 |
| G8 | {"p":"block20","op":"mint","tick":"BLK","amt":"1","hash":"<HASH>"} | Ignored not a BLOCK-20 message | R3.7 |
| G9 | {} | Ignored parses, but has no p | R3.7 |
| G10 | {"p":"block-20","op":"deploy","tick":"blk","max":"21000000","lim":"1000"} after G1 | Ignored identity blk already deployed | R4.3 |
| G11 | {"p":"block-20","op":"deploy","tick":"BLOCK","max":"100","lim":"1"} | Accepted five characters is the maximum, identity block | R4.1 |
| G12 | {"p":"block-20","op":"deploy","tick":"BLOCK2","max":"100","lim":"1"} | Rejected six characters | R4.1 |
| G13 | {"p":"block-20","op":"transfer","tick":"BLK","amt":"0"} | Rejected zero is not a positive integer | R4.4 |
| G14 | {"p":"block-20","op":"transfer","tick":"BLK","amt":"0250"} | Rejected leading zero | R4.4 |
| G15 | {"p":"block-20","op":"transfer","tick":"BLK","amt":"2.5"} | Rejected decimal point, and BLOCK-20 has zero decimals | R4.4, R4.6 |
| G16 | {"p":"block-20","op":"deploy","tick":"OVR","max":"1000","lim":"1001"} | Rejected per-mint limit exceeds maximum supply | R5.3 |
| G17 | Deploy with a des of 257 bytes | Rejected description over 256 bytes | R5.4 |
| G18 | Any valid payload inscribed with content type image/png | Ignored content type not accepted | R2.2 |
| G19 | Any valid payload padded to 2049 bytes | Ignored over the size ceiling, not even parsed | R2.3 |
Group A
Anchor boundaries
All vectors use the mint payload {"p":"block-20","op":"mint","tick":"BLK","amt":"1000","hash":"<HASH>"} with D = 767430 and W = 144. Only the anchor height A and the reveal height R vary. These are the cases where implementations diverge, so test every one.
| ID | A | R | R - A | Expected outcome | Rule |
|---|---|---|---|---|---|
| A1 | 767430 | 767500 | 70 | Valid credits 1000. Boundary case A = D. | R6.6 |
| A2 | 767429 | 767500 | 71 | Rejected anchor is older than the deployment | R6.6 |
| A3 | 767499 | 767500 | 1 | Valid credits 1000. Boundary case A = R - 1. | R6.7 |
| A4 | 767500 | 767500 | 0 | Rejected a mint cannot anchor to its own block | R6.7 |
| A5 | 767501 | 767500 | -1 | Rejected anchor is after the reveal | R6.7 |
| A6 | 767356 | 767500 | 144 | Valid credits 1000. Boundary case R - A = W. | R6.8 |
| A7 | 767355 | 767500 | 145 | Rejected one block outside the freshness window | R6.8 |
| A8 | hash is uppercase hex | Rejected before any chain lookup | R6.4 | ||
| A9 | hash has a 0x prefix, or is 63 or 65 characters | Rejected syntax | R6.4 | ||
| A10 | hash is a valid transaction ID, not a block ID | Rejected resolves to no block | R6.5 | ||
| A11 | hash names a header the node knows, but that height now holds a different block | Rejected the anchor is on an orphaned branch | R6.5 | ||
| A12 | hash names a block with zero confirmations | Rejected not on the active chain | R6.5 | ||
A1, A3, A6, and A7 are the vectors that catch off-by-one errors. An implementation that uses A <= R passes A4 incorrectly. One that uses R - A < W fails A6 incorrectly. One that uses A > D fails A1 incorrectly.
Group S
Limit and supply
| ID | amt | Minted before | Expected outcome | Rule |
|---|---|---|---|---|
| S1 | 1000 | 0 | Valid credits 1000. Equal to lim is allowed. | R6.3 |
| S2 | 1001 | 0 | Rejected above the per-mint limit. Not reduced to 1000. | R6.3 |
| S3 | 1000 | 20999500 | Valid credits 500. Partial final mint: requested 1000, credited 500. | R6.10 |
| S4 | 1000 | 21000000 | Ignored supply exhausted. No event is produced at all. | R6.9 |
| S5 | 1 | 21000000 | Ignored same as S4, regardless of amount | R6.9 |
After S3 the ticker's minted supply is exactly 21000000, total is 21000000, and every later mint follows S4.
Group T
Transfer and settlement
Starting state for this group: address ALICE holds 1000 BLK with no reservations.
| ID | Action | Expected outcome | Rule |
|---|---|---|---|
| T1 | ALICE reveals {"p":"block-20","op":"transfer","tick":"BLK","amt":"250"} | Accepted reservation created. Balance stays 1000, available becomes 750. No supply change. | R7.5 |
| T2 | After T1, ALICE reveals a second transfer for 800 | Ignored available balance is 750, below 800. No reservation created. | R7.3 |
| T3 | After T1, ALICE reveals a second transfer for 750 | Accepted available becomes 0. Two independent reservations now exist. | R7.3 |
| T4 | After T1, the inscription moves to BOB | Settled ALICE 750, BOB 250. Supply unchanged. | R7.7 |
| T5 | After T1, the inscription moves back to ALICE | Settled ALICE 1000, reservation released, movement still recorded. | R7.7 |
| T6 | After T1, the inscription is spent in transaction fees | Settled ALICE 1000, reservation released. No supply change. | R7.8 |
| T7 | After T1, the inscription is burnt or sent to a destination with no supported address | Settled ALICE 750. Total and circulating fall by 250, burned rises by 250, minted unchanged. | R7.9 |
| T8 | After T4, BOB moves the same inscription to CAROL | No effect the reservation is already settled. | R7.10 |
| T9 | A transfer inscription for a ticker that was never deployed | Ignored no active deployment. | R7.2 |
| T10 | After T7, ALICE mints again while minted supply is still below max | Valid burning did not free capacity, but it also did not consume any. | R7.9, R8.6 |
Group O
Within-block ordering
Fixture change for this group: ORD is deployed with max = 1500 and lim = 1000, and nothing has been minted.
| ID | Situation | Expected outcome | Rule |
|---|---|---|---|
| O1 | Two mints of 1000 ORD in one block, at transaction index 3 and 7 | Index 3 credits 1000. Index 7 credits 500 as a partial final mint. Minted supply reaches 1500. | R8.2, R8.3, R6.10 |
| O2 | Same block, both mints at transaction index 3, inscription numbers 100 and 101 | Inscription 100 credits 1000, inscription 101 credits 500. Order is by ordinal, then inscription ID. | R8.2 |
| O3 | A transfer settlement and a new inscription share transaction index 5 | The settlement is processed first, so the released or moved balance is visible to the new inscription in the same block. | R8.2 |
| O4 | A deploy of NEW and a mint of NEW in the same block, deploy ordered first | The deploy is accepted. The mint is accepted only if its anchor is an earlier block, since A must be below R and D equals R here. | R8.4, R6.7 |
| O5 | The same pair with the mint ordered first | The mint is ignored: no active deployment existed when it was processed. | R6.2, R8.3 |
Group X
Reorganization
| ID | Situation | Expected outcome | Rule |
|---|---|---|---|
| X1 | A block containing a mint, a transfer inscription, and a settlement is replaced | All three events are invalidated in descending order of height, transaction index, then event index. Their recorded deltas are reversed exactly. The settled reservation is restored and the created reservation is removed. | R10.2, R10.3 |
| X2 | The replacement branch contains the same mint inscription at a new height, and the anchor block is still on the active chain | The mint is re-evaluated against the new reveal height. If it still satisfies R6.6 through R6.8 it is credited again, otherwise it is not. | R10.7 |
| X3 | The reorganization orphans the anchor block itself | The mint is invalid on replay even though its bytes are unchanged, because the hash no longer resolves on the active chain. | R6.5, R10.7 |
| X4 | A deploy is invalidated while mints of that ticker are still applied | Fatal. The reader must not leave applied events attached to an inactive deployment, and must stop instead. | R10.4 |
| X5 | A reorganization would require invalidating an event at or below the finalized height | Fatal. The reader refuses and stops rather than produce a state it cannot justify. | R10.5 |
| X6 | A checkpoint arrives with a lower height, or the same height with a different hash, and no invalidations | Rejected as incomplete reorganization data if any applied event lies outside the new chain. | R10.1 |
X4, X5, and X6 are the vectors that separate a careful reader from a lossy one. Each is a case where continuing would produce balances the reader could not derive from the chain, and the correct behavior is to stop.