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.

The three outcomes
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.

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.

Fixtures
NameValue
Namespace hash3a7f1c8e0b45d29a6f13e874c05b9d2e6a8f04713bc9e25d8a06f1c34b7e9c19
Recipient, in recbc1qs2x9wz22tasf8pfejlcc3e8yhnzhv9qs2kgwls
A second address, not in recbc1qxp2xhn5quxa589v89lm0dm694szv44canvyun6
Namespace owner field14caKuhoxA2wCj7JYjVJhgsuxTsbu5rzch
Whitelist key wla02bb9e14cd42f6487e94071cb25fe5bd6f42753de771be11d21c6a80b5f08cf4a4
Price list pri{"0": 200000, "3": 2000000, "4": 800000}
Granularity idua1
Namesc2F0b3NoaQ== 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.

Framing vectors
PayloadJSON bytesLength prefixFramed totalMultisig data outputsP2WSH data outputs
DEPLOY43101b5 (437)439814
MINT, no img23200ee (238)24048
MINT, with img278011c (284)28659
TRANSFER18700c1 (193)19547
SETRECORD22200e4 (228)23048
RENEW137008f (143)14535
Carrier acceptance vectors
Transaction shapeOutcomeRule
Bare multisig, 1-of-3 with a burn key, ARC4 payload decrypting to a framed stamp: bufferAccepted9, 16
Bare multisig with no burn key, so keyburn is not 1Excluded16
P2WSH data outputs at index 1 and above, framed correctlyAccepted, keyburn forced to 110
P2WSH data output at index 0 onlyExcluded, index 0 is never collected10
Both P2WSH and multisig present, P2WSH payload fails its length checkExcluded. The multisig branch is deliberately not attempted12
Payload in an OP_RETURNExcluded. Not a carrier8
Framed buffer whose declared length exceeds the data presentExcluded9, 10

3. Key-set vectors

Every vector here is either accepted for dispatch or excluded. None produces a status code.

Key matching at block 900000
VectorOutcomeWhy
transfer with exactly its five keysDispatchedSymmetric difference is empty
transfer plus "memo": "hello"ExcludedExact matching. One extra key is fatal
transfer missing toaddressExcludedExact matching. One missing key is fatal
renew plus "coef": "500"ExcludedRenew has no coefficient. There is no discount on renewal
deploy plus "hash"ExcludedA deploy must not name a namespace
deploy missing descExcludedAll sixteen deploy keys are mandatory, including the unvalidated ones
setrecord missing primExcludedMandatory even when you do not want a primary. Use "false"
mint plus "note": "x"DispatchedAt 872200 and above, mint matching is containment. Extras are ignored
mint plus "img": [...]Dispatched, img discardedContainment permits it, and the derived URL is written instead
mint missing sigExcludedContainment still requires every key in the set. Use ""
"op": "TRANSFEROWNERSHIP"ExcludedNot one of the five. Key matching discards it before dispatch, so not even UO
"op": "burn"ExcludedThere is no burn operation
"P": "SRC-101", "OP": "RENEW", upper-cased keysDispatchedTop-level keys are lower-cased before matching
"p": "SRC-101", upper-cased valueDispatchedThe protocol value is compared case-insensitively
"p": "src101", no hyphenExcludedThe hyphen is part of the string
The same mint payload at two heights
PayloadBlock 871000Block 900000
Nine keys, no imgExcluded, img missingDispatched
Ten keys including imgDispatched, img must match derivedDispatched, img discarded
Ten keys including an unrelated extraExcludedDispatched

4. Field-rule vectors

Every failure here is an Excluded, because field validation is fail-closed. There is no status code and no record.

Numeric fields
Value of duaOutcomeWhy
"2"2A plain integer string
22A JSON integer is equally accepted
"02"2Leading zeros are accepted by the pattern and by conversion
"1.5"ExcludedMatches the pattern but will not convert to an integer
1.51Silently truncated. The asymmetry with the string form is real
"1e3"ExcludedScientific notation fails the pattern
"-1"ExcludedThe pattern has no sign, and the value must be at least zero
"1,000"ExcludedSeparators fail the pattern. SRC-20's historical loose parsing has no equivalent here
""Normalised to null, then IND or ITD at the handlerThe empty string is nulled before the numeric rule runs
"0" on a mintITDPasses the field rule, fails the handler which requires greater than zero
Typed fields
VectorOutcomeWhy
"prim": "true"trueThe only accepted true form
"prim": trueExcludedThe comparison is textual. A JSON boolean fails
"prim": "True"ExcludedThe comparison is case-sensitive
"hash" of 63 hex charactersExcludedExactly 64 are required
"toaddress" a valid Bech32 mainnet addressAcceptedBech32 goes through the bech32 branch
"toaddress" a valid P2PKH addressAcceptedEverything not starting bc1 or tb1 goes to Base58Check
"toaddress" a Bech32 address with one character alteredExcludedThe checksum fails
"toaddress" a tb1 testnet addressAcceptedThe branch is chosen on the prefix, and tb1 takes the bech32 branch. A quirk, not a feature
"rec": []Accepted at the field ruleAn empty list passes, and makes every later mint fail with IR
"rec" containing the same address twiceAccepted, de-duplicatedStored as a set. Order is not preserved
"wla" of 66 hex characters beginning 04ExcludedOnly compressed keys, beginning 02 or 03
"pri": {"0": "200000"}ExcludedPrices must be JSON integers, not strings
"pri": {"three": 200000}ExcludedEvery key must parse as an integer
"img": "https://x/y.png"ExcludedMust be a list of strings, even for one name
"sig": ""Accepted, nulledThe conventional way to satisfy a mandatory key with no value

5. Name encoding vectors

tokenid acceptance
tokenidDecodes toOutcomeWhy
c2F0b3NoaQ==satoshiValidStandard alphabet, length 12, a multiple of four
YWJjabcValidNo padding needed
U0FUT1NISQ==satoshiValid, same nameThe decoded value is lower-cased, so this collides with the first row
c2F0b3-oaQ==n/aExcludedThe URL-safe alphabet is rejected by the acceptance pattern
c2F0b3NoaQn/aExcludedLength 10 is not a multiple of four. Padding is required
132 characters of valid Base64n/aExcludedOver the 128 character cap on the encoded form
dHdvIHdvcmRztwo wordsExcludedA space is a special character
YS1ia-bExcludedThe hyphen is in the special punctuation set
YS5ia.bExcludedThe dot is in the special punctuation set. Names are not dotted
4pyTa check markValidOrdinary symbols outside the special set are accepted. SRC-20's emoji allowlist has no equivalent here
["c2F0b3NoaQ==", "U0FUT1NISQ=="]satoshi twiceExcludedDuplicates are compared after decoding, not as Base64
["c2F0b3NoaQ=="] on a transfersatoshiNMA transfer looks up a scalar name. A list cannot match an entry
"c2F0b3NoaQ==" on a mintsatoshiITTA mint requires the list form, even for one name

6. deploy vectors

Deploy outcomes
VectorOutcomeWhy
All sixteen keys, all lengths within limitsValidThe namespace exists from the next block onward
"tick" of 32 charactersIDPThe limit is shorter than 32, so 32 fails
"tick" of 31 charactersValid31 is within the limit
"imglp" of 255 charactersIDPShorter than 255 is required
"pri" with 255 entriesIDPFewer than 255 entries is required
"lim": "0"Valid, and permanently unusableThe namespace lookup treats a zero limit as not found, so every later operation gets ND
"idua": "0"Valid, and unmintableTerm rounding divides by the granularity, so every mint and renew gets ITID
"mintend": "0"ValidStored as 18446744073709551615, meaning no end
"mintend" at or before "mintstart"Valid, and unmintableNo block timestamp can be both at least the start and below the end
"pri" with no 0 entry, minting a name of an unlisted lengthIRLNo price and no fallback
A mint in the same block as this deployNDA 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.

Mint outcomes at block 900000
VectorOutput 0OutcomeWhy
satoshi, dua 2, coef 1000400000 to the recipientValid200000 fallback times 2 rounds
satoshi, dua 2, coef 1000399999 to the recipientIRVOne satoshi short
satoshi, dua 2, coef 1000400000 to a different addressIROutput 0 must pay a namespace recipient
abc, dua 12000000ValidLength 3 has an explicit price
abc, dua 1200000IRVPaying the fallback price for a length that has an explicit one
satoshi, dua 2, coef 500, empty sig200000IRVWithout a signature the coefficient stays at 1000. No fallback to half price
satoshi, dua 2, coef 500, a signature that does not verify200000IRSA failed signature ends the mint. It does not fall back to full price
satoshi, dua 2, coef 500, a valid signature200000Valid400000 times 500 over 1000
coef 1001anyITCThe range is 0 to 1000 inclusive
Three free names, dua 1600000Valid, three entries200000 fallback each
Three names, one already held with a live expiry, dua 1600000Valid, two entriesThe taken name is dropped. The payment was computed over all three
Three names, all held with live expiries600000DMThe list became empty
A name whose previous term has expiredthe priceValidThe previous holder is recorded as preowner and the row is overwritten
Block timestamp before mintstartthe priceUTThe window has not opened
Block timestamp equal to mintendthe priceOTThe window is half-open. Equal to the end is closed
toaddress different from the creatorthe priceValidYou may mint a name to somebody else
Carried over P2WSH below block 940000the price, actually paidIRVThe decoder reports the destination value as zero on that branch below that height
Term rounding, with granularity 2
Requested duaRoundedRoundsPayment at 200000 per round
121200000
221200000
342400000
442400000
563600000

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

Owner-only operations
VectorOutcomeWhy
Transfer by the current owner, term runningValidExpiry unchanged, records cleared, primary flag cleared
Transfer by anyone elseNOThe creator must be the current owner
Transfer of a name never mintedNMNo entry to move
Transfer one second after the expiryOENo grace period
Transfer with no payment at output 0ValidA transfer costs nothing beyond the Bitcoin fee
Renew by the owner one block before expiry, dua 1ValidNew expiry is the old expiry plus one year, so nothing is lost
Renew by the owner one block after expiryOEThe term ended. The name is now open to anyone
Renew paying one satoshi shortIRVThe renew payment has no coefficient and no discount
Renew with dua 0ITDMust be greater than zero
Setrecord with "type": "address" and a valid btc memberValidThe resolved Bitcoin address is written
Setrecord with "type": "txt" and any valueValidThe text record is written
Setrecord with "type": "url"IDProduces a field no handler reads, so neither record is set
Setrecord with "data": {} and type addressIDAn address record must carry btc, eth, or both
Setrecord with an eth signature that recovers a valid addressValidThe recovered address is stored without its 0x prefix
Setrecord with an eth member that is an Ethereum address rather than a signatureIDRecovery fails. The member is a signature, not an address
Setrecord with "prim": "true" and btc equal to the creatorValidAlso clears the flag from the holder's other names in this namespace
Setrecord with "prim": "true" and btc not equal to the creatorIDBYou cannot mark primary for an address you are not spending from
Setrecord with "prim": "true" and a txt record onlyIDBThe primary claim needs an address record to check against
Setrecord writing only txt over an existing address recordValidRecords 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.

Namespace 3a7f...c19bName satoshi

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
What a rollback would do to this history

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

The same input at different heights
Input869000871000900000950000
Any SRC-101 payload Excluded ProcessedProcessedProcessed
Mint with nine keys, no img Excluded Excluded ProcessedProcessed
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 ProcessedProcessedProcessed

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.