Test vectors · Bitcoin mainnet · btc_stamps 1.9.3
SRC-101 test vectors
Every vector states the outcome the reference indexer produces and the rule that decides it. All are constructed for testing. None is drawn from a transaction on chain, and this site makes no claim about what any particular real transaction did.
- Valid
- Recorded, and the register changes.
- Invalid
- Recorded with a status code. Appears in activity, changes nothing.
- Excluded
- Never becomes an SRC-101 record. No status code, no row, nothing to look up. In SRC-101 this covers key-set failures and every field-level failure.
Contents
1. Shared fixtures
Every vector below uses these. Addresses and the public key are checksum-valid and lie on the curve, because a broken fixture teaches the wrong lesson, but each is derived from fixed text rather than from a key anyone holds.
| Name | Value |
|---|---|
| Namespace hash | 3a7f1c8e0b45d29a6f13e874c05b9d2e6a8f04713bc9e25d8a06f1c34b7e9c19 |
Recipient, in rec | bc1qs2x9wz22tasf8pfejlcc3e8yhnzhv9qs2kgwls |
A second address, not in rec | bc1qxp2xhn5quxa589v89lm0dm694szv44canvyun6 |
| Namespace owner field | 14caKuhoxA2wCj7JYjVJhgsuxTsbu5rzch |
Whitelist key wla | 02bb9e14cd42f6487e94071cb25fe5bd6f42753de771be11d21c6a80b5f08cf4a4 |
Price list pri | {"0": 200000, "3": 2000000, "4": 800000} |
Granularity idua | 1 |
| Names | c2F0b3NoaQ== is satoshi, Yml0Y29pbg== is bitcoin, bGFwc2Vk is lapsed, YWJj is abc |
2. Carrier framing
The framed buffer is a two-byte big-endian length, then stamp:, then the JSON. The declared length covers the prefix and the JSON and excludes itself. Counts are computed from the exact payloads used on the guide.
| Payload | JSON bytes | Length prefix | Framed total | Multisig data outputs | P2WSH data outputs |
|---|---|---|---|---|---|
DEPLOY | 431 | 01b5 (437) | 439 | 8 | 14 |
MINT, no img | 232 | 00ee (238) | 240 | 4 | 8 |
MINT, with img | 278 | 011c (284) | 286 | 5 | 9 |
TRANSFER | 187 | 00c1 (193) | 195 | 4 | 7 |
SETRECORD | 222 | 00e4 (228) | 230 | 4 | 8 |
RENEW | 137 | 008f (143) | 145 | 3 | 5 |
| Transaction shape | Outcome | Rule |
|---|---|---|
Bare multisig, 1-of-3 with a burn key, ARC4 payload decrypting to a framed stamp: buffer | Accepted | 9, 16 |
| Bare multisig with no burn key, so keyburn is not 1 | Excluded | 16 |
| P2WSH data outputs at index 1 and above, framed correctly | Accepted, keyburn forced to 1 | 10 |
| P2WSH data output at index 0 only | Excluded, index 0 is never collected | 10 |
| Both P2WSH and multisig present, P2WSH payload fails its length check | Excluded. The multisig branch is deliberately not attempted | 12 |
Payload in an OP_RETURN | Excluded. Not a carrier | 8 |
| Framed buffer whose declared length exceeds the data present | Excluded | 9, 10 |
3. Key-set vectors
Every vector here is either accepted for dispatch or excluded. None produces a status code.
| Vector | Outcome | Why |
|---|---|---|
transfer with exactly its five keys | Dispatched | Symmetric difference is empty |
transfer plus "memo": "hello" | Excluded | Exact matching. One extra key is fatal |
transfer missing toaddress | Excluded | Exact matching. One missing key is fatal |
renew plus "coef": "500" | Excluded | Renew has no coefficient. There is no discount on renewal |
deploy plus "hash" | Excluded | A deploy must not name a namespace |
deploy missing desc | Excluded | All sixteen deploy keys are mandatory, including the unvalidated ones |
setrecord missing prim | Excluded | Mandatory even when you do not want a primary. Use "false" |
mint plus "note": "x" | Dispatched | At 872200 and above, mint matching is containment. Extras are ignored |
mint plus "img": [...] | Dispatched, img discarded | Containment permits it, and the derived URL is written instead |
mint missing sig | Excluded | Containment still requires every key in the set. Use "" |
"op": "TRANSFEROWNERSHIP" | Excluded | Not one of the five. Key matching discards it before dispatch, so not even UO |
"op": "burn" | Excluded | There is no burn operation |
"P": "SRC-101", "OP": "RENEW", upper-cased keys | Dispatched | Top-level keys are lower-cased before matching |
"p": "SRC-101", upper-cased value | Dispatched | The protocol value is compared case-insensitively |
"p": "src101", no hyphen | Excluded | The hyphen is part of the string |
| Payload | Block 871000 | Block 900000 |
|---|---|---|
Nine keys, no img | Excluded, img missing | Dispatched |
Ten keys including img | Dispatched, img must match derived | Dispatched, img discarded |
| Ten keys including an unrelated extra | Excluded | Dispatched |
4. Field-rule vectors
Every failure here is an Excluded, because field validation is fail-closed. There is no status code and no record.
Value of dua | Outcome | Why |
|---|---|---|
"2" | 2 | A plain integer string |
2 | 2 | A JSON integer is equally accepted |
"02" | 2 | Leading zeros are accepted by the pattern and by conversion |
"1.5" | Excluded | Matches the pattern but will not convert to an integer |
1.5 | 1 | Silently truncated. The asymmetry with the string form is real |
"1e3" | Excluded | Scientific notation fails the pattern |
"-1" | Excluded | The pattern has no sign, and the value must be at least zero |
"1,000" | Excluded | Separators fail the pattern. SRC-20's historical loose parsing has no equivalent here |
"" | Normalised to null, then IND or ITD at the handler | The empty string is nulled before the numeric rule runs |
"0" on a mint | ITD | Passes the field rule, fails the handler which requires greater than zero |
| Vector | Outcome | Why |
|---|---|---|
"prim": "true" | true | The only accepted true form |
"prim": true | Excluded | The comparison is textual. A JSON boolean fails |
"prim": "True" | Excluded | The comparison is case-sensitive |
"hash" of 63 hex characters | Excluded | Exactly 64 are required |
"toaddress" a valid Bech32 mainnet address | Accepted | Bech32 goes through the bech32 branch |
"toaddress" a valid P2PKH address | Accepted | Everything not starting bc1 or tb1 goes to Base58Check |
"toaddress" a Bech32 address with one character altered | Excluded | The checksum fails |
"toaddress" a tb1 testnet address | Accepted | The branch is chosen on the prefix, and tb1 takes the bech32 branch. A quirk, not a feature |
"rec": [] | Accepted at the field rule | An empty list passes, and makes every later mint fail with IR |
"rec" containing the same address twice | Accepted, de-duplicated | Stored as a set. Order is not preserved |
"wla" of 66 hex characters beginning 04 | Excluded | Only compressed keys, beginning 02 or 03 |
"pri": {"0": "200000"} | Excluded | Prices must be JSON integers, not strings |
"pri": {"three": 200000} | Excluded | Every key must parse as an integer |
"img": "https://x/y.png" | Excluded | Must be a list of strings, even for one name |
"sig": "" | Accepted, nulled | The conventional way to satisfy a mandatory key with no value |
5. Name encoding vectors
tokenid | Decodes to | Outcome | Why |
|---|---|---|---|
c2F0b3NoaQ== | satoshi | Valid | Standard alphabet, length 12, a multiple of four |
YWJj | abc | Valid | No padding needed |
U0FUT1NISQ== | satoshi | Valid, same name | The decoded value is lower-cased, so this collides with the first row |
c2F0b3-oaQ== | n/a | Excluded | The URL-safe alphabet is rejected by the acceptance pattern |
c2F0b3NoaQ | n/a | Excluded | Length 10 is not a multiple of four. Padding is required |
| 132 characters of valid Base64 | n/a | Excluded | Over the 128 character cap on the encoded form |
dHdvIHdvcmRz | two words | Excluded | A space is a special character |
YS1i | a-b | Excluded | The hyphen is in the special punctuation set |
YS5i | a.b | Excluded | The dot is in the special punctuation set. Names are not dotted |
4pyT | a check mark | Valid | Ordinary symbols outside the special set are accepted. SRC-20's emoji allowlist has no equivalent here |
["c2F0b3NoaQ==", "U0FUT1NISQ=="] | satoshi twice | Excluded | Duplicates are compared after decoding, not as Base64 |
["c2F0b3NoaQ=="] on a transfer | satoshi | NM | A transfer looks up a scalar name. A list cannot match an entry |
"c2F0b3NoaQ==" on a mint | satoshi | ITT | A mint requires the list form, even for one name |
6. deploy vectors
| Vector | Outcome | Why |
|---|---|---|
| All sixteen keys, all lengths within limits | Valid | The namespace exists from the next block onward |
"tick" of 32 characters | IDP | The limit is shorter than 32, so 32 fails |
"tick" of 31 characters | Valid | 31 is within the limit |
"imglp" of 255 characters | IDP | Shorter than 255 is required |
"pri" with 255 entries | IDP | Fewer than 255 entries is required |
"lim": "0" | Valid, and permanently unusable | The namespace lookup treats a zero limit as not found, so every later operation gets ND |
"idua": "0" | Valid, and unmintable | Term rounding divides by the granularity, so every mint and renew gets ITID |
"mintend": "0" | Valid | Stored as 18446744073709551615, meaning no end |
"mintend" at or before "mintstart" | Valid, and unmintable | No block timestamp can be both at least the start and below the end |
"pri" with no 0 entry, minting a name of an unlisted length | IRL | No price and no fallback |
| A mint in the same block as this deploy | ND | A namespace cannot be resolved from within its own block |
7. mint vectors
Against the fixture namespace: prices 200000 for the fallback, 2000000 for length 3, 800000 for length 4, granularity 1.
| Vector | Output 0 | Outcome | Why |
|---|---|---|---|
satoshi, dua 2, coef 1000 | 400000 to the recipient | Valid | 200000 fallback times 2 rounds |
satoshi, dua 2, coef 1000 | 399999 to the recipient | IRV | One satoshi short |
satoshi, dua 2, coef 1000 | 400000 to a different address | IR | Output 0 must pay a namespace recipient |
abc, dua 1 | 2000000 | Valid | Length 3 has an explicit price |
abc, dua 1 | 200000 | IRV | Paying the fallback price for a length that has an explicit one |
satoshi, dua 2, coef 500, empty sig | 200000 | IRV | Without a signature the coefficient stays at 1000. No fallback to half price |
satoshi, dua 2, coef 500, a signature that does not verify | 200000 | IRS | A failed signature ends the mint. It does not fall back to full price |
satoshi, dua 2, coef 500, a valid signature | 200000 | Valid | 400000 times 500 over 1000 |
coef 1001 | any | ITC | The range is 0 to 1000 inclusive |
Three free names, dua 1 | 600000 | Valid, three entries | 200000 fallback each |
Three names, one already held with a live expiry, dua 1 | 600000 | Valid, two entries | The taken name is dropped. The payment was computed over all three |
| Three names, all held with live expiries | 600000 | DM | The list became empty |
| A name whose previous term has expired | the price | Valid | The previous holder is recorded as preowner and the row is overwritten |
Block timestamp before mintstart | the price | UT | The window has not opened |
Block timestamp equal to mintend | the price | OT | The window is half-open. Equal to the end is closed |
toaddress different from the creator | the price | Valid | You may mint a name to somebody else |
| Carried over P2WSH below block 940000 | the price, actually paid | IRV | The decoder reports the destination value as zero on that branch below that height |
Requested dua | Rounded | Rounds | Payment at 200000 per round |
|---|---|---|---|
| 1 | 2 | 1 | 200000 |
| 2 | 2 | 1 | 200000 |
| 3 | 4 | 2 | 400000 |
| 4 | 4 | 2 | 400000 |
| 5 | 6 | 3 | 600000 |
The rounded value is what is stored as the term, what appears in the discount signature, and what multiplies the price. Requesting three years of a two-year granularity buys four.
8. transfer, renew and setrecord vectors
| Vector | Outcome | Why |
|---|---|---|
| Transfer by the current owner, term running | Valid | Expiry unchanged, records cleared, primary flag cleared |
| Transfer by anyone else | NO | The creator must be the current owner |
| Transfer of a name never minted | NM | No entry to move |
| Transfer one second after the expiry | OE | No grace period |
| Transfer with no payment at output 0 | Valid | A transfer costs nothing beyond the Bitcoin fee |
Renew by the owner one block before expiry, dua 1 | Valid | New expiry is the old expiry plus one year, so nothing is lost |
| Renew by the owner one block after expiry | OE | The term ended. The name is now open to anyone |
| Renew paying one satoshi short | IRV | The renew payment has no coefficient and no discount |
Renew with dua 0 | ITD | Must be greater than zero |
Setrecord with "type": "address" and a valid btc member | Valid | The resolved Bitcoin address is written |
Setrecord with "type": "txt" and any value | Valid | The text record is written |
Setrecord with "type": "url" | ID | Produces a field no handler reads, so neither record is set |
Setrecord with "data": {} and type address | ID | An address record must carry btc, eth, or both |
Setrecord with an eth signature that recovers a valid address | Valid | The recovered address is stored without its 0x prefix |
Setrecord with an eth member that is an Ethereum address rather than a signature | ID | Recovery fails. The member is a signature, not an address |
Setrecord with "prim": "true" and btc equal to the creator | Valid | Also clears the flag from the holder's other names in this namespace |
Setrecord with "prim": "true" and btc not equal to the creator | IDB | You cannot mark primary for an address you are not spending from |
Setrecord with "prim": "true" and a txt record only | IDB | The primary claim needs an address record to check against |
Setrecord writing only txt over an existing address record | Valid | Records merge. The address record survives |
9. A stateful name lifecycle
One name, satoshi, in the fixture namespace, from first claim to lapse and re-claim. Each step assumes the previous ones applied. Timestamps are the block timestamps of the containing blocks.
Step 1. MINT by A, dua 1, paying 200000
Valid- owner
- A
- preowner
- none
- expiry
- T plus 31536000
- records
- none, address_btc set to A
Step 2. MINT by B, one block later
Invalid, DM- reason
- the only name in the list is held with a live expiry
- register
- unchanged
- payment
- spent, not refunded
- owner
- still A
Step 3. SETRECORD by A, address btc equal to A, prim true
Valid- address_btc
- A
- primary
- yes, cleared from A's other names here
- expiry
- unchanged
- owner
- A
Step 4. RENEW by A, dua 1, paying 200000
Valid- expiry
- T plus 63072000, the old expiry plus a year
- owner
- A
- records
- carried over
- primary
- carried over
Step 5. TRANSFER by A to B
Valid- owner
- B
- preowner
- A
- expiry
- unchanged, T plus 63072000
- records
- cleared, primary set to false
Step 6. SETRECORD by A after the transfer
Invalid, NO- reason
- A is no longer the owner
- owner
- B
- register
- unchanged
- note
- the same code applies to transfer and renew
Step 7. The expiry passes with no renew
Expired- row
- still present, owner still B
- renew by B
- refused, OE
- transfer by B
- refused, OE
- setrecord by B
- refused, OE
Step 8. MINT by C, dua 1, paying 200000
Valid- owner
- C
- preowner
- B
- expiry
- the new block time plus 31536000
- records
- none, the row is overwritten
If the chain reorganised back past step 5 and the register were rebuilt from the valid operations, the replay would reproduce owner and expiry correctly, but not every field. The rebuild sets no previous owner on a mint, does not clear the primary flag on a transfer, and does not clear the primary flag from other names when one is set. The reference gives the full list.
10. Height-dependent behaviour
| Input | 869000 | 871000 | 900000 | 950000 |
|---|---|---|---|---|
| Any SRC-101 payload | Excluded | Processed | Processed | Processed |
Mint with nine keys, no img |
Excluded | Excluded | Processed | Processed |
Mint with ten keys including img |
Excluded | Processed, img must match derived, else IRM |
Processed, img discarded |
Processed, img discarded |
| Mint with an unrelated extra key | Excluded | Excluded | Processed, extra ignored | Processed, extra ignored |
| Transfer with an unrelated extra key | Excluded | Excluded | Excluded | Excluded |
| Correctly paid mint carried over P2WSH | Excluded | IRV | IRV | Valid |
| Correctly paid mint carried over bare multisig | Excluded | Valid | Valid | Valid |
| Transfer carried over P2WSH | Excluded | Valid | Valid | Valid |
| SRC-101 carried alongside a Counterparty asset | Excluded | Processed | Processed | Processed |
The last row is worth pausing on. The equivalent SRC-20 row would read "ignored" from block 796000 onward. SRC-101 has no such cut-off at any height, now or in future.