Mint a Glyph FT¶
Goal: issue your own fungible token on Radiant mainnet, from zero to a broadcastable reveal transaction, using pyrxd 0.8.0.
This tutorial walks the canonical two-transaction flow:
Commit tx — places the metadata-hashlock script on chain.
Reveal tx — spends the commit and emits a single 75-byte FT output carrying your entire premine supply. The commit outpoint (
commit_txid:commit_vout) — not the reveal txid — is the permanent token reference embedded in that output.
The runnable reference is
examples/ft_deploy_premine.py.
This page walks the same flow step-by-step. By default everything is a
dry run — no transaction is broadcast unless you explicitly opt in.
Before you start¶
You should already be comfortable with the first-transaction tutorial patterns: a funded WIF key, ElectrumX, fee math. If you’ve also minted a Glyph NFT before, this is the same commit/reveal shape with two differences:
the protocol vector is
[FT]([1]), not[NFT]([2]);the reveal output is the 75-byte FT lock instead of the 63-byte NFT singleton, and it carries a premine amount (photons) instead of a single 1-photon NFT token.
The 75-byte FT script shape and the consensus rules behind it are explained in Radiant FTs are on-chain — keep that page open in another tab. This tutorial does not re-derive the script layout.
You will need:
a WIF private key for a Radiant address holding enough RXD to cover the commit, the reveal, the premine, and fees;
pyrxd 0.8.0 installed (
pip install "pyrxd>=0.8.0");an ElectrumX endpoint (the example defaults to a public radiant4people node).
Step 1: design the token¶
Decide three things up front. None of them can be changed after the deploy:
Field |
Example |
Notes |
|---|---|---|
|
|
Human-readable name, free-form string. |
|
|
Ticker symbol; convention is uppercase, ≤16 chars. |
|
|
Display precision only — consensus is always “1 photon = 1 FT unit.” See note below. |
Premine amount |
|
Integer FT units = photons in the reveal’s FT output. Must be ≥ 546 (dust limit). |
Note
decimals is a display hint for wallets, not a consensus quantity.
Radiant’s FT conservation rule operates on photons. A token with
decimals=2 and a premine of 1_000_000 photons displays as
10_000.00 units in supporting wallets but is still exactly
1,000,000 photons on chain.
Step 2: build the metadata¶
GlyphMetadata is the payload that gets CBOR-encoded into the reveal
scriptSig. For a plain FT, the only required protocol marker is
GlyphProtocol.FT:
import time
from pyrxd.glyph import GlyphMetadata, GlyphProtocol
metadata = GlyphMetadata(
protocol=[GlyphProtocol.FT], # [1] — plain fungible token
name="My Token",
ticker="MTK",
description="My Token — issued via pyrxd",
decimals=0,
attrs={"issued_at": str(int(time.time()))},
)
Important
Set protocol=[GlyphProtocol.FT] for a plain FT. Use
[GlyphProtocol.FT, GlyphProtocol.DMINT] only if you want a dMint
distribution token (parallel mining contracts) — that flow is covered
in V1 dMint deploys. Plain FTs and
dMint FTs share the 75-byte output shape but use different deploy
methods. This tutorial covers plain FT only.
Step 3: derive the commit script¶
GlyphBuilder.prepare_commit takes the metadata plus the owner’s PKH
(20-byte hash160 of the public key) and returns the commit locking
script, the CBOR bytes you’ll need at reveal time, and the payload
hash committed into the script:
from pyrxd.glyph import GlyphBuilder
from pyrxd.glyph.builder import CommitParams
from pyrxd.keys import PrivateKey
from pyrxd.security.types import Hex20
private_key = PrivateKey(GLYPH_WIF) # your funded WIF
pub = private_key.public_key()
address = pub.address()
pkh = Hex20(pub.hash160())
builder = GlyphBuilder()
commit_result = builder.prepare_commit(
CommitParams(
metadata=metadata,
owner_pkh=pkh,
change_pkh=pkh,
funding_satoshis=0, # not load-bearing for plain FT
)
)
# commit_result.commit_script — bytes; place at vout[0] of the commit tx
# commit_result.cbor_bytes — bytes; save these for the reveal scriptSig
# commit_result.payload_hash — bytes32; committed inside commit_script
prepare_commit reads metadata.protocol: because NFT (2) is not
in the list, the commit script is built as the FT-shaped commit
(OP_1 / OP_NUMEQUALVERIFY ref-count check) rather than the NFT
singleton shape.
Warning
Save commit_result.cbor_bytes somewhere durable before broadcasting
the commit. The reveal scriptSig has to push the exact same CBOR
bytes you committed to — regenerating from the metadata is fine for a
deterministic build, but the example script writes cbor_hex plus
commit_txid to /tmp/ft_deploy_resume.json so a crashed reveal can
be resumed without recomputing.
Step 4: broadcast the commit tx¶
The commit tx is an ordinary spend of P2PKH UTXOs that produces two outputs:
vout[0]—commit_result.commit_scriptwith a value large enough to cover the reveal’s fee and the premine output. The reference example targets ≈ `(REVEAL_SIZE × MIN_FEE_RATE × 1.2) + PREMINE_AMOUNT200_000` photons for comfortable headroom.
vout[1]— change back to your address as plain P2PKH.
Construction details (P2PKH unlock template, fee model,
Transaction.sign()) match the demo and are not specific to FT —
see build_commit_tx in
examples/ft_deploy_premine.py.
Note
DRY_RUN by default. The reference example sets
DRY_RUN = os.environ.get("DRY_RUN", "1") != "0". The commit tx is
built and signed but is only broadcast when you set DRY_RUN=0.
This is deliberate — running a script that prints transaction hex is
safe; broadcasting requires explicit opt-in.
After broadcasting, wait for the commit to confirm before building the reveal. The reference example sleeps 90 seconds; production code should poll the node or wait for a block.
Step 5: build the reveal¶
This is the only FT-specific call in the deploy. Pass the confirmed commit outpoint, the CBOR bytes saved at commit time, the recipient PKH (you, for a self-premine), and the integer premine amount:
reveal_scripts = builder.prepare_ft_deploy_reveal(
commit_txid=commit_txid,
commit_vout=commit_vout, # 0 in the reference shape
commit_value=commit_value, # photons in the commit output
cbor_bytes=commit_result.cbor_bytes,
premine_pkh=pkh,
premine_amount=1_000_000, # your token supply, in photons
)
# reveal_scripts.locking_script — 75 bytes, the FT lock; goes at vout[0]
# reveal_scripts.scriptsig_suffix — 'gly' + CBOR; goes after sig + pubkey
# reveal_scripts.premine_amount — set vout[0].value to exactly this
The returned locking_script is the canonical 75-byte FT shape:
P2PKH (25 bytes) → OP_STATESEPARATOR → OP_PUSHINPUTREF <ref> (38 bytes)
→ the 12-byte FT-CSH conservation epilogue dec0e9aa76e378e4a269e69d.
See Radiant FTs are on-chain
for the byte-by-byte breakdown — the bytes prepare_ft_deploy_reveal
emits match that layout exactly.
Warning
premine_amount < 546 is rejected at build time as below the dust
limit (most mempool policies refuse the reveal otherwise). If you
want a smaller supply, pick a different token model (NFT, or scale up
with decimals).
The reveal tx spends vout[0] of the commit and produces one
output — vout[0] carries the entire premine to premine_pkh. The
commit outpoint (commit_txid, commit_vout) — not the reveal
txid — is the permanent token reference: it is the ref
prepare_ft_deploy_reveal embeds in the FT locking script, and every
future transfer of this FT will encode the same 36 bytes inside its
output script.
Two practical points the reference example handles for you:
scriptSig layout. The reveal-input scriptSig is the normal P2PKH
<sig> <pubkey>push pair followed byscriptsig_suffix. The example wraps this inft_reveal_unlock_template.Two-pass fee. Because the FT output’s value equals
premine_amountexactly (1 photon = 1 FT unit), there is no change output to absorb fee imprecision. The example signs a trial tx, measures its byte length, computes the fee, and verifiescommit_value - premine_amount - fee >= 0before signing the final tx.
Step 6: broadcast and verify¶
With DRY_RUN=0 and a confirmed commit, broadcast the reveal. On
acceptance you have:
Token ref: the commit outpoint
{commit_txid}:{commit_vout}— not the reveal txid — is the 36-byte ref encoded into every FT UTXO of this token. This is the ref you use to look up or transfer the token; getting it wrong means you can’t find your own supply.Supply: exactly
premine_amountphotons, living atreveal_scripts.locking_scriptat the address derived frompremine_pkh.
Sanity-check the result with the inspect tool:
$ pyrxd glyph inspect <reveal_txid> --fetch
Transaction: <reveal_txid>
size: 268 bytes
inputs: 1
outputs: 1
Outputs:
vout 0 type=ft sats=1000000
ref=<commit_txid>:<commit_vout>
owner_pkh=<your_pkh>
The ref line is the commit outpoint, not the reveal txid — that
is the token’s permanent identity. type=ft confirms the locking
script matches the 75-byte FT shape and
its embedded ref points at itself — i.e. this is a freshly deployed
FT. Any wallet implementing the standard FT classifier
(is_ft_script + extract_ref_from_ft_script) will recognise it
without an indexer.
What’s next¶
Send some tokens. Once the reveal confirms, you can transfer units of the token using
GlyphBuilder.build_ft_transfer_tx. The load-bearing prerequisite — filtering wallet UTXOs to the correct 75-byte FT shape and the right token ref — is explained at length in Radiant FTs are on-chain and demonstrated end-to-end inexamples/ft_transfer_demo.py.Distribute by mining instead of premining. If you want supply emitted by parallel proof-of-work contracts rather than handed to a single address up front, that’s a dMint deploy (
protocol=[FT, DMINT]) — see V1 dMint deploys. It’s a different builder method (prepare_dmint_deploy) and a three-tx flow, not the two-tx flow on this page.Migrating from pyrxd 0.4.x? Plain-FT deploy is unchanged in 0.8.0; only the V1 dMint mint path has breaking signature changes. See Migrate from 0.4 to 0.5 if you have a custom miner.