Mint walkthrough
Two transactions with at least 144 blocks between them. The wait is what makes your claim impossible to snipe.
- How to compute the commitment,
SHA256("PTNA/commit" || claimant_xonly || salt), and build the 68 byte commit leaf around it. - Which byte of the SEED payload names the carrier output, and what a wrong value costs you.
- The value the carrier must hold: at least 100000 sats for a founding Seal, at least 10000 sats once the window has closed.
- How to recompute the artifact id yourself and check the indexer against it.
- The reason code behind each way a reveal fails, and why your bitcoin survives every one of them.
Computed by docs/tools/vectors.mjs from the example key and salt shown below. Your values
will differ because your salt and key differ. Rerun the script to check the arithmetic.
Before you start
- Finish wallet setup, including one full rehearsal on regtest.
- Check the window is open. Ask your indexer:
curl -s https://indexer.example.org/patina/window
A response with "state": "OPEN" and a positive blocks_remaining means founding
commits are still being accepted. Field by field detail is in the
endpoint reference, and the cohort rules that state reports on are laid
out on the public page for the Firstlight Seals.
Step one, the commit
-
Generate a claimant key and a salt
The claimant key is a 32 byte x-only public key that you control. The salt is 16 random bytes from a cryptographically secure generator. Never reuse a salt.
claimant_xonly f13dc99dc544736fca031f924b0ae1c54ff4a331f5c3b2951c6d4d5e1878866a salt 2fe862993a92197e084e4070cc8aa1c3No private key exists for that example point. Do not use it.
-
Compute the commitment
Single SHA-256 over the ASCII tag, the key, then the salt. No separators.
commitment = SHA256("PTNA/commit" || claimant_xonly(32) || salt(16)) = ac2e3090ef3265ff4fc99463cec7e2b8d9227ac29f3ac0f5fc8e24dbaa2afdd6 -
Build the commit leaf
A tapscript that requires your signature, pushes the commitment, and drops it without executing a conditional.
<claimant_xonly(32)> OP_CHECKSIG PUSH32(commitment) OP_DROP 68 bytes: 20f13dc99dc544736fca031f924b0ae1c54ff4a331f5c3b2951c6d4d5e1878866aac20ac2e3090ef3265ff4fc99463cec7e2b8d9227ac29f3ac0f5fc8e24dbaa2afdd675Do not rebuild a pending commit with a different leaf. Historical 70 byte conditional leaves remain parseable and must reveal with their original script.
-
Fund the commit output
Build the P2TR output that commits to that leaf, then pay it. Fund it with enough to cover the reveal fee plus the carrier value you intend, or plan to add a second input at reveal time.
Record the exact outpoint. You need
txid:voutat reveal time. -
Broadcast and wait for confirmation
The height at which the commit output confirms is the height that decides founding status. Note it.
Using the planner instead
POST /patina/plan/commit
Content-Type: application/json
{
"network": "signet",
"claimant_xonly": "f13dc99dc544736fca031f924b0ae1c54ff4a331f5c3b2951c6d4d5e1878866a",
"salt": "2fe862993a92197e084e4070cc8aa1c3",
"fee_rate_sat_vb": 12
}
The baseline freezes that the planner returns an unsigned plan and a human review summary, holds no keys and broadcasts nothing you have not signed. The exact request field names belong to the app backend, not to the protocol. Treat the block above as the shape of the information the planner needs, and check the backend for the current field names.
The wait
The reveal is invalid unless reveal_height - commit_output_height >= 144. That is about 24
hours at ten minutes a block, and it can be longer if blocks are slow.
A reveal published one block too early does not fail politely. It confirms on chain, spends your commit
output, produces the reason code SEED_COMMIT_TOO_YOUNG, and creates nothing. You keep your
coins and lose the fees and the commit. Count the blocks, and add a safety margin of a few blocks.
While you wait, verify your commit output height with your own node or your indexer, and confirm the window was still open at that height.
Step two, the reveal
-
Spend the commit output by the script path
The reveal transaction must have exactly one input whose revealed leaf commits to this salt and claimant key, with your signature satisfying the
OP_CHECKSIG. Two leaves qualifying for the same salt fail the same way as none. The indexer recomputesSHA256("PTNA/commit" || claimant_xonly || salt)from the leaf and the SEED payload, and the result must equal the commitment inside the leaf. -
Create the carrier output
Pick which output is the carrier and note its index. It must not be an OP_RETURN, and its value must be at least 100000 sats for a founding Seal, or 10000 sats after the window closes.
This output is the artifact. Its confirmation height is where depth starts counting.
-
Add the SEED marker
One OP_RETURN output, one minimal push, 24 bytes of data.
OP_RETURN PUSH(24) [ "PTNA" | 0x01 | 0x01 | salt(16) | flags(1) | carrier_vout(1) ] payload 18 bytes 2fe862993a92197e084e4070cc8aa1c30001 push data 24 bytes 50544e4101012fe862993a92197e084e4070cc8aa1c30001 scriptPubKey 26 bytes 6a1850544e4101012fe862993a92197e084e4070cc8aa1c30001The trailing
00 01isflags = 0x00thencarrier_vout = 1. Get that last byte wrong and you point the artifact at the wrong output. Full grammar on Marker grammar. -
Check the transaction before signing
- Exactly one OP_RETURN output whose payload starts with
PTNA. Two of them void the marker. - The
carrier_voutbyte matches the index of the output you funded. - That output holds at least the minimum for the cohort you are claiming.
- The salt in the payload is byte for byte the salt in your commitment.
- The commit input is at least 144 blocks old at the height you expect to confirm at.
- Exactly one OP_RETURN output whose payload starts with
-
Broadcast
Once it confirms, the artifact exists. Compute the id yourself if you want to check the indexer:
artifact_id = SHA256("PTNA/artifact" || reveal_txid_wire(32) || carrier_vout_le(4))reveal_txid_wireis the internal byte order, which is the reverse of the txid a block explorer shows you. Worked example on Identity and derivations.
Confirm it landed
Ask any indexer for the id you just computed. Four fields decide whether the mint did what you meant.
curl -s https://indexer.example.org/patina/artifacts/d3b8d3013c23dd3df76882034df80935ba55aa75772007f0b142b63adce5eff0
Check status is ALIVE, founding is what you expected,
carrier is your outpoint, and endowment_sats matches the value you funded. If the
artifact is missing, look for your reveal in GET /patina/invalid-events, which tells you
the reason code.
Six confirmations is where the indexer treats the record as final. Before that, a reorg can undo it.
None of these checks require trusting an indexer. The public page on verifying it yourself reads the same facts off your own node.
What can go wrong, and what it looks like
Every row below is a transaction that confirmed on Bitcoin and created no artifact. The middle column is
what the indexer reports for it in GET /patina/invalid-events, so this is the table to scan
when your reveal is in a block but the artifact is not there.
| Mistake | Reason code | Result |
|---|---|---|
| Revealed before 144 blocks passed | SEED_COMMIT_TOO_YOUNG | Commit spent, nothing created |
| Salt in the payload differs from the salt in the commitment | SEED_COMMITMENT_MISMATCH | Nothing created |
| No input revealed a qualifying commit leaf | SEED_NO_COMMIT_INPUT | Nothing created |
carrier_vout points past the last output | SEED_CARRIER_OUT_OF_RANGE | Nothing created |
carrier_vout points at the OP_RETURN | SEED_CARRIER_IS_OPRETURN | Nothing created |
| Carrier funded below the minimum | SEED_CARRIER_BELOW_MIN | Nothing created |
| Two OP_RETURN outputs start with PTNA | VOID_DUPLICATE_MARKER | Marker void, nothing created |
| Push is not minimal, or extra opcodes present | SEED_BAD_GRAMMAR | Nothing created |
In every one of these cases your bitcoin stays yours. You lose fees and the commit, not the coins. Full list on Reason codes, and repair paths on When something goes wrong.