V1 dMint mint mechanics: claiming a contract UTXO¶
Why this page exists: V1 dMint deploys describes how a single reveal transaction creates N parallel singleton contract UTXOs. That’s the supply side. This page covers the claim side: how a miner spends one of those contract UTXOs to mint a reward — the canonical 4-output transaction shape, the 72-byte scriptSig push convention, the 64-byte PoW preimage layout, and the FT-conservation covenant check the V1 script enforces on the reward output.
If you want the incident background — the M1 bug where pyrxd’s
scriptSig pushes diverged from the preimage and every signed mint was
silently rejected by the covenant — read
dmint-v1-mint-scriptsig-divergence.md.
This page is the protocol explainer; that page is the post-mortem.
TL;DR¶
A V1 dMint mint is a two-input, four-output transaction. The
contract input is spent by a 72-byte scriptSig carrying a 4-byte
nonce and two 32-byte hashes. The script hashes are
SHA256d(funding_input_locking_script) and
SHA256d(vout[2]_OP_RETURN_script), not the halves of the PoW
preimage — the covenant recomputes
SHA256(input_hash || output_hash) from those exact bytes and folds
the result into the PoW check. Diverging the scriptSig pushes from
the preimage is the recurring failure mode the M1 incident surfaced.
V1 vs V2 differs only in nonce width: V1 uses a 4-byte nonce
(nonce_width=4, 72-byte scriptSig), V2 uses 8 bytes (76-byte
scriptSig). The byte layout, FT-conservation check, and 4-output
shape are otherwise identical-by-construction in pyrxd’s builder.
No V2 dMint contracts exist on chain, so V2 mint mechanics are
not field-verified — V1 is the only path with mainnet golden vectors
pinning it.
Mainnet anchors¶
Two independent mints serve as the byte-equality witnesses pyrxd’s test suite pins against:
Anchor |
Txid |
Notes |
|---|---|---|
snk (Photonic reference mint) |
|
Canonical reference for the 72-byte scriptSig convention. |
pyrxd’s first PXD mint |
|
First successful mint after the M1 scriptSig-divergence fix (2026-05-11). |
Both are pinned in
tests/test_dmint_v1_mint.py’s
TestCovenantShape suite — byte-equal assertions against captured
mainnet bytes, not against pyrxd-generated fixtures. The suite is
the load-bearing regression guard for the scriptSig + preimage
contract.
The 4-output mint transaction shape¶
V1 mints have two inputs and a four-output canonical shape:
vin / vout |
bytes (approx) |
role |
|---|---|---|
vin[0] |
72 |
The contract UTXO being spent; unlocked by the V1 mint scriptSig (see next section). |
vin[1] |
~106 |
Plain-RXD P2PKH funding input — pays the FT reward photons + tx fee. |
vout[0] |
241 |
The re-created contract UTXO at |
vout[1] |
75 |
FT-wrapped reward output, value = |
vout[2] |
variable |
OP_RETURN message output. The covenant binds |
vout[3] |
25 |
P2PKH change to the miner — |
The contract input value is preserved across mints. V1 contracts
are singletons (the on-chain reference deploys all use 1-photon
contract outputs), not a value pool — the reward photons come from
the funding input, not from the contract UTXO. See
_build_dmint_v1_mint_tx for the
builder’s output assembly.
The 241-byte recreated contract layout (state + epilogue) is the same shape covered in V1 dMint deploys under “Reveal tx output shape”; only the 4-byte height field at the start of the state changes between mints.
vout[2] is canonical, not optional¶
pyrxd’s V1 mint builder will produce a 3-output transaction (no
OP_RETURN) if op_return_msg is None. But the V1 preimage helper
build_dmint_v1_mint_preimage
requires unsigned_tx.outputs[2] to exist and to be an OP_RETURN
script (starts with 0x6a). The covenant binds outputHash to that
specific position; building a 3-output mint produces a preimage with
nothing to anchor outputHash to, and a hand-rolled 4-output tx
with a non-OP_RETURN at vout[2] would silently bind the preimage to
the wrong bytes (covenant rejection after a successful mine).
In other words: the canonical V1 mint is the 4-output shape. The 3-output path exists in the builder API but is not the mainnet convention and will not produce a mineable preimage through pyrxd’s public helpers.
The 72-byte scriptSig push convention¶
The contract input’s scriptSig is a fixed byte layout. The 4-byte
nonce variant (V1) totals 72 bytes; the 8-byte variant (V2) totals
76. Layout from
build_mint_scriptsig:
┌── V1 mint scriptSig (72 bytes) ────────────────────────────────────┐
│ 04 <nonce:4B> PUSH4 nonce (5 bytes) │
│ 20 <inputHash:32B> PUSH32 inputHash (33 bytes) │
│ 20 <outputHash:32B> PUSH32 outputHash (33 bytes) │
│ 00 OP_0 (push empty) (1 byte) │
└────────────────────────────────────────────────────────────────────┘
where:
inputHash=SHA256d(funding_input_locking_script)— the funding input atvin[1], not the contract input.outputHash=SHA256d(vout[2]_OP_RETURN_script)— the script bytes of the OP_RETURN message output.The trailing
OP_0(0x00) is required by the V1 epilogue; it pushes an empty item that the script consumes during the unlock sequence.
The V2 form is identical except the first byte is 0x08 and the
nonce is 8 bytes (76 bytes total). The single-byte switch
between layouts is why
build_mint_scriptsig takes a
keyword-only nonce_width: Literal[4, 8] argument — a stray
positional 4 is a type error rather than a silent V1/V2 confusion.
The two hashes MUST come from the same source as the preimage¶
This is the load-bearing rule:
pow_result = build_dmint_v1_mint_preimage(contract_utxo, funding_utxo, unsigned_tx)
# Mine using pow_result.preimage
nonce = mine_solution(pow_result.preimage, target, nonce_width=4)
# Build scriptSig from the SAME PowPreimageResult
scriptsig = build_mint_scriptsig(
nonce, pow_result.input_hash, pow_result.output_hash, nonce_width=4,
)
The scriptSig’s inputHash / outputHash are
PowPreimageResult.input_hash and
.output_hash from the same call that produced the preimage the
miner solved. Splitting the sources (recomputing the hashes
separately in two helpers) is exactly the silent-rejection failure
mode the M1 incident surfaced — the covenant computes
SHA256(scriptSig_inputHash || scriptSig_outputHash) from the
on-chain scriptSig bytes alone, so any divergence between the bytes
the miner solved and the bytes the scriptSig pushes produces
mandatory-script-verify-flag-failed after a successful mine. See
dmint-v1-mint-scriptsig-divergence.md
for the full chain trace.
The PoW preimage layout¶
The 64-byte preimage the miner SHA256d’s against target is:
┌── 64-byte SHA256d preimage ───────────────────────────────────────┐
│ H1 = SHA256(txid_LE || contractRef) (32 bytes) │
│ H2 = SHA256(inputHash || outputHash) (32 bytes) │
└───────────────────────────────────────────────────────────────────┘
│
▼ miner appends nonce, takes SHA256d
│
▼ compares LE-int(result) < target
where:
txid_LE= the contract input’s outpoint txid, in little-endian (internal) byte order (32 bytes).contractRef= the contract’s permanent 36-byte wire ref (txid_LE_reversed || vout_LE_4B), the same value embedded in the 241-byte contract state at deploy time.inputHash/outputHashare the two 32-byteSHA256dvalues the scriptSig also pushes (see previous section).
This is the exact layout
build_pow_preimage returns inside
PowPreimageResult. The miner
appends the nonce, double-SHA256’s the 68 bytes (64 preimage + 4
nonce for V1), and accepts the nonce when the little-endian integer
of the result is less than target.
The covenant then performs the same computation on chain: it pulls
inputHash and outputHash straight from the scriptSig pushes,
recomputes H2 = SHA256(inputHash || outputHash), recomputes H1
from the input’s outpoint and the contract ref stored in the
contract’s state, and verifies the PoW hash. Diverging the
preimage halves from the scriptSig pushes produces a covenant
rejection after a fully-successful mine — the M1 failure mode.
The covenant also binds the preimage to:
Which contract slot was claimed (
H1includescontractRef, so a nonce mined for contract A is not valid against contract B even within the same deploy’s N parallel contracts).The miner’s funding input (
H2includesSHA256d(funding_script), so a miner cannot substitute a different funding source after finding a nonce — the funding input is committed to before mining begins).The OP_RETURN bytes at vout[2] (
H2includesSHA256d(op_return_script), so the message bytes are committed to before mining; swapping them post-mine invalidates the preimage).
The FT-conservation check on vout[1]¶
The V1 contract’s 145-byte epilogue contains an
OP_CODESCRIPTHASHVALUESUM_OUTPUTS check that enforces FT
conservation on the reward. The fingerprint it hashes against is the
12-byte FT-CSH tail dec0e9aa76e378e4a269e69d (covered in
Radiant FTs are on-chain).
For the V1 mint, this means vout[1] must be the 75-byte
FT-wrapped shape (see the “75-byte FT layout” section of
Radiant FTs are on-chain):
76 a9 14 <miner_pkh:20> 88 ac bd d0 <tokenRef:36> de c0 e9 aa 76 e3 78 e4 a2 69 e6 9d
└── P2PKH (25 B) ──────────┘ └── ref (38 B) ───┘ └── FT-CSH fingerprint (12 B) ─┘
with:
The miner’s 20-byte
pkhin the P2PKH section (so the miner controls the reward).The contract’s
tokenRefin the 36-byteOP_PUSHINPUTREF-bound ref section (binds the reward to the same FT the contract was deployed for).The 12-byte FT-CSH fingerprint that the covenant computes
SHA256(script)against when summing FT-output values.The output value (
satoshisfield) exactly equal tostate.rewardphotons —1 photon = 1 FT unitfor Radiant FTs.
build_dmint_v1_ft_output_script
builds this exact shape. A plain 25-byte P2PKH at vout[1] fails
the covenant: the FT-CSH fingerprint isn’t there to match, and the
conservation sum collapses to zero on the output side while the
input still claims the contract’s reward.
This is the same shape — and the same builder — pyrxd uses for V2
reward outputs after the 0.5.0 R1 fix
(build_dmint_v1_ft_output_script is shared between V1 and V2 via
the _PART_C common section). V1 mints had this right since 0.4.0;
V2 was emitting plain P2PKH and would have been silently rejected
by every V2 contract on chain — caught pre-mainnet by the 0.5.0
red-team audit (the R1 finding in the 0.5.0 changelog).
Footguns the library guards against¶
Patterns the V1 mint code rejects loudly rather than silently producing a covenant-rejected broadcast:
scriptSig pushes derived independently from the preimage.
build_pow_preimagereturns a frozenPowPreimageResultcarrying the preimage plus.input_hash/.output_hash. The scriptSig builder takes those same hashes — there is no public path that splits the preimage halves and the scriptSig pushes into two independent computations. This was the load-bearing M1 fix (0.5.0 breaking change tobuild_mint_scriptsig’s signature).Token-bearing funding UTXO. A wallet that accidentally picks an FT, NFT, or dMint UTXO as
vin[1]would destroy the token.find_dmint_funding_utxoand_build_dmint_v1_mint_txboth reject any funding input whose script contains anOP_PUSHINPUTREF-family opcode (0xd0–0xd8) viais_token_bearing_script. An opcode-aware classifier — not a byte-substring scan, which would misclassify legitimate P2PKH addresses with matching payload bytes.Missing OP_RETURN at vout[2].
build_dmint_v1_mint_preimagerefuses to compute a preimage whenunsigned_tx.outputs[2]is absent or doesn’t start with0x6a. The covenant bindsoutputHashto that exact position; producing a preimage from a wrong-shape tx would waste the mining work.Synthetic-only validation. Round-trip tests through pyrxd’s own builder + verifier are not sufficient — both the M1 scriptSig-divergence and the prior shape-mismatch incident shipped because builder + verifier were tested against each other rather than against captured chain bytes. The
TestCovenantShapesuite pins against two independent mainnet golden vectors (146a4d68…f3candc9fdcd34…e530) to break that loop.V2-shaped mints against V1 contracts (and vice versa).
build_mint_scriptsigtakesnonce_widthas a keyword-onlyLiteral[4, 8]argument — a stray positional4produces a type error, and the wrong nonce width produces a length-validation error before any covenant logic runs. V1 contracts requirenonce_width=4; the 8-byte default (V2) was preserved across the 0.5.0 breaking change for backwards compatibility with the pre-V1 default.
End-to-end claim flow¶
The canonical V1 mint sequence
(examples/dmint_claim_demo.py):
from pyrxd.glyph.dmint import (
build_dmint_mint_tx,
build_dmint_v1_mint_preimage,
build_mint_scriptsig,
find_dmint_funding_utxo,
mine_solution,
)
# 1. Pick a contract UTXO (use find_dmint_contract_utxos against
# the deploy reveal txid; see V1 dMint deploys page).
contract_utxo = ...
# 2. Find a plain-RXD funding UTXO at the miner's address.
funding_utxo = await find_dmint_funding_utxo(
client, miner_address, needed=contract_utxo.state.reward + 10_000_000,
)
# 3. Build the unsigned 4-output tx with a placeholder nonce.
result = build_dmint_mint_tx(
contract_utxo=contract_utxo,
nonce=b"\x00" * 4,
miner_pkh=miner_pkh,
current_time=0, # V1 has no DAA
funding_utxo=funding_utxo,
op_return_msg=b"hello world", # required for canonical 4-output shape
)
# 4. Compute the preimage AND the scriptSig hashes from the
# now-finalised tx outputs — single source of truth.
pow_result = build_dmint_v1_mint_preimage(contract_utxo, funding_utxo, result.tx)
# 5. Mine.
nonce = mine_solution(pow_result.preimage, contract_utxo.state.target, nonce_width=4)
# 6. Splice the real scriptSig in. Same .input_hash / .output_hash
# as the preimage was built from.
real_scriptsig = build_mint_scriptsig(
nonce, pow_result.input_hash, pow_result.output_hash, nonce_width=4,
)
result.tx.inputs[0].unlocking_script = Script(real_scriptsig)
# 7. Sign the funding input (vin[1]) with the miner's key.
# 8. Broadcast.
The two-phase build (placeholder scriptSig, finalise outputs, compute preimage, mine, splice real scriptSig) is unavoidable: the preimage depends on the funding script and the OP_RETURN script, both of which are output-side fields fixed before mining; the scriptSig is the input-side field that mining produces. The preimage helper takes the unsigned tx so this ordering is enforced at the API level — you cannot ask for a preimage before the outputs are finalised.