Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The Postmaster

The postoffice is a singleton on-chain account that administers the whole SithBit deployment. Since the delegation cutover its admin surface is split in two:

  • The standing delegate — a wallet address recorded on the postoffice. It holds the operational powers: authorizing and deactivating domains, tuning the protocol fees, publishing the root KSK fingerprint, and fee-free bulk alias reservation. This is the key an operator uses day to day, and the one services like domain-sithbit keep hot.
  • The postmaster — the owner. It is not a pubkey on-chain: the postoffice stores only a 32-byte Merkle commitment root over a hidden set of keys derived in an offline key ceremony. Nobody can read the postmaster’s keys off the chain, because they are never written there. The ownership powers — sweeping the postoffice revenue, rotating the delegate, and handing ownership itself over — are exercised by revealing one ceremony key with a Merkle membership proof.

Every ownership operation is rotate-on-use: the revealed key spends the entire committed generation, and the same instruction installs the next generation’s root. A revealed key is never accepted twice.

Note: almost everything in this topic is an operator/administrator action, not something an everyday mailbox owner does. If you’re just sending and receiving mail, you can skip this page — it’s here because understanding who administers your domain is part of understanding the system.

For how to hold the ceremony seeds safely — and what survives losing one — see the postmaster custody runbook.

Checking status

Anyone can check the current fees and the postoffice without signing anything — the fee getters are public, read-only commands under postoffice (the delegate only sets fees; reading them is ungated):

sithbit postoffice fee stamp        # per-stamp protocol fee
sithbit postoffice fee domain       # domain-authorization fee
sithbit postoffice fee alias        # alias claim / transfer fees + the premium short-name schedule
sithbit postoffice fee settlement   # settlement basis-point rates (operator share, stamp-purchase rate)
sithbit postoffice fee attestation  # one-time verified-sender attestation fee
sithbit postoffice fee pin-lease    # one-time pinning-lease creation fee + minimum deposit
sithbit postoffice postmaster       # postoffice address, delegate, commitment status

sithbit postoffice postmaster reports the postoffice address, the standing delegate’s wallet, and whether an ownership commitment root is installed (the root itself is opaque — it reveals nothing about the keys behind it). It is read-only and ships in the default CLI build; the state-changing subcommands below are not (see the note at the end of this page). The same query is also reachable as sithbit postmaster get in an admin build (see the note below), since it lives on the postmaster command tree too.

Initializing the postoffice

sithbit postmaster init \
  --seed <seed0.json> --seed <seed1.json> [--seed …] \
  [--keys-per-seed <K>] \
  [--keypair <delegate keypair>] \
  [--skip-preflight]

A one-time step run once per network deployment, right after the on-chain programs are first deployed. The signing keypair (--keypair) pays, becomes the standing delegate, and the ceremony seed files (at least two, in a fixed order) derive the genesis commitment root that ships in the instruction — no postmaster pubkey is ever recorded. --keys-per-seed is a ceremony parameter: every later ownership operation must present the same value, so record it alongside the seeds.

A fresh init also claims the global postmaster alias for the delegate, atomically in the same transaction: instruction 1 creates the postoffice (recording the delegate), so instruction 2’s alias claim fee is waived by the delegate fee waiver by construction — the claim costs only the alias account’s rent. Mail addressed to postmaster resolves to the operator from the deployment’s first block. The claim has three outcomes:

  • Unclaimed (the normal fresh-deployment case) — registered to the delegate in the init transaction.
  • Already held by the delegate — an idempotent skip: init proceeds and notes the alias already belongs to it.
  • Held by anyone else — a loud refusal naming the holder, and nothing is submitted, not even the postoffice init: the deployment must never come up without its postmaster name. Recover by acquiring the alias (a transfer from the holder) or having the holder close it (sithbit alias close), then re-run init.

A reinit attempt (the postoffice account already exists) sends the init instruction alone, so the chain’s authoritative refusal surfaces instead of an alias complaint. One standing limitation to know: the alias program has no reserved words, so the atomic claim narrows the squat window to the gap between program deploy and postmaster init — it cannot close it. Run init promptly after deploying the programs.

The delegate’s operational powers

The delegate signs the operational admin instructions; a non-delegate signer is refused with on-chain error 66 (NotDelegate):

  • All domain operationsdomain create, the two-step deactivation (request, cancel, finalize), domain close, and domain transfer. These land on the domain program, which checks the delegate against the postoffice cross-program — the postoffice itself stays a mail-program account.
  • Fee tuningpostmaster fee stamp, fee domain, fee alias, fee alias-tiers (the four premium claim fees for 1–4 character names, set together in lamports for 1/2/3/4 characters), fee settlement (the two settlement basis-point rates set together: the operator share of settled value and the stamp-purchase fee rate — the bps arm of the hybrid per-stamp fee; a zero rate resets to its protocol default), fee attestation (the one-time verified-sender attestation fee; zero likewise resets to the default), fee reputation-floor (the floor of reputation-scaled first-contact pricing, in basis points of a mailbox’s default postage; zero likewise resets to the default), and fee pin-lease (the one-time pinning-lease creation fee; zero likewise resets to the default), each value bounded by a hardcoded on-chain cap (see Economics), so even a compromised delegate cannot price the protocol out of reach.
  • The root KSK fingerprintpostmaster ksk set (and the read-only postmaster ksk get), the DNSSEC trust anchor for authorize-by-proof.
  • Fee-free bulk alias reservation — the per-alias claim fee is waived when the delegate signs a multi-alias alias create, premium 1–4 character names included: reserve short names for rent alone and resell them on the marketplace.

The delegate is deliberately a hot-capable key: it can run inside domain-sithbit for self-service domain onboarding, because nothing it signs can move postoffice funds or touch the ownership commitment.

Ownership operations

The three ownership instructions share one CLI shape: the ceremony seed files re-derive everything — the signing chain key, its membership proof against the current on-chain root, and the next generation’s root to install. The CLI is stateless; the current generation is recovered by scanning candidate roots against the chain, so there is no counter file to keep. A stale or invalid proof is refused with on-chain error 67 (InvalidCommitmentProof).

--seed <seed0.json> --seed <seed1.json> [--seed …]   # every seed, ceremony order
[--keys-per-seed <K>]     # must match the value init used (default 2)
[--key-index <INDEX>]     # which committed key signs (default 0)
[--generation <G>]        # force a generation (tests; normally scanned)

Installing a successor commitment (ownership handover)

sithbit postmaster commitment \
  --seed <seed0.json> --seed <seed1.json> \
  [--keypair <fee payer>] [--skip-preflight]

This replaces the old postmaster transfer (it rides the same instruction slot on-chain): instead of pointing the postoffice at a new owner pubkey, it installs a successor commitment root. To hand the deployment to a new owner, the new owner runs their own ceremony and the current owner installs the resulting root — from then on only the new owner’s seeds can prove ownership. The --keypair here only pays the transaction fee; it needs no admin standing.

Withdrawing protocol fees

sithbit postmaster withdraw [destination] \
  --seed <seed0.json> --seed <seed1.json> \
  [--keypair <fee payer>] [--skip-preflight]

Sweeps the postoffice balance above its rent-exempt minimum — the accumulated domain, alias, and stamp protocol fees — to destination (defaults to the fee payer’s address). Ownership-gated: the delegate collects fees into the postoffice but can never take them out.

Rotating the delegate

sithbit postmaster delegate <new_delegate> \
  --seed <seed0.json> --seed <seed1.json> \
  [--keypair <fee payer>] [--skip-preflight]

Repoints the operational powers at a new wallet — the recovery path for a lost or compromised delegate key, and the scheduled-rotation path for a healthy one. The all-zero address is refused (it is the on-chain “no delegate” sentinel).

Because every one of these three operations spends the revealed generation and installs the next one, running any of them advances the generation for all of them — the next ownership operation, whichever it is, proves against the new root. The CLI recovers the new generation from the chain automatically.

postmaster reclaim — wipe and start fresh

Sometimes you want to throw a whole deployment away and start over — most often on devnet or a local validator, where the on-chain state is fixture data you’re done with. reclaim is the tool for that: in one pass it drains every rent-empty account all three programs own and hands the rent back. Accounts still holding value are deliberately out of its reach — see It refuses accounts that still hold value.

sithbit postmaster reclaim \
  [--rent-recipient <address>] \
  [--keypair <delegate keypair>] \
  [-y] \
  [--skip-preflight]

It enumerates every program-owned account for all three program IDs (a getProgramAccounts scan of the mail, alias, and domain programs), then batch-closes them with each program’s own delegate-signed AdminCloseAccount instruction (a program can only drain accounts it owns, so each of the three carries its own reclaim twin) — mailboxes, fromboxes, domains, aliases, published-key accounts, the lot. The postoffice singleton is closed last, deliberately: every other close authenticates against it, so it has to outlive them. Each closed account’s rent is refunded to --rent-recipient, which defaults to the signing keypair’s own address.

It refuses accounts that still hold value

AdminCloseAccount closes an account only when its balance is at or below its rent-exempt minimum. A target holding anything above that is refused on-chain with custom error 106 (AdminCloseEscrowPresent), in all three programs.

That guard is what keeps a wipe tool from being a drain: a frombox holds a sender’s prepaid postage, a message account holds escrowed postage and any reply bounty, an alias-bid account holds a live auction bid. Without the check, the standing delegate could sweep all of it into --rent-recipient — and on a real deployment that is other people’s money, not fixture data.

The practical consequence on devnet is that a reclaim pass over live fixture state will skip funded accounts rather than reporting a clean sweep. To retire those, settle or close them through their normal paths first (mail delete to settle messages, frombox close or frombox reclaim to empty fromboxes, alias bid --cancel to return bids), then run reclaim to collect the rent-empty remainder.

It is the standing delegate that signs

AdminCloseAccount is gated on a standing-delegate signature checked against the mail postoffice — the same delegate that runs the operational commands above. Since the delegation cutover there is no postmaster pubkey on-chain, so for the purposes of this instruction the “postmaster” simply is the standing delegate. A signer that isn’t the recorded delegate is refused on-chain with AdminCloseUnauthorized; the --keypair you pass must therefore be the delegate key. Being the delegate is necessary but not sufficient — the value guard above applies to the delegate too.

Why the launch build doesn’t have this command

reclaim is a compile-time footgun, and it is fenced off as one. The AdminCloseAccount instruction lets the standing delegate destroy any account any of the three programs owns — anyone’s mailbox, any domain, the postoffice itself — and pocket that account’s rent. That is enormous power to leave sitting in a live deployment’s admin key.

So the command is gated behind a second Cargo feature, reclaim, layered on top of postmaster (the code is compiled only under all(postmaster, reclaim)). The intended lifecycle is:

  • Pre-launch, build the CLI and the on-chain programs reclaim-capable, so you can reset devnet/testnet freely while you iterate.
  • At mainnet launch, cut a build with the reclaim feature off. The command disappears from the CLI and — because the on-chain AdminCloseAccount handler is itself feature-gated out of the shipped bytecode — the whole close-anything path leaves the programs entirely. A reclaim-free launch build simply has no instruction that can delete a user’s account out from under them.

Because it is destructive, reclaim will not run silently:

  • On an unknown or mainnet cluster it demands a typed confirmation phrase — you must type reclaim mainnet exactly. This guardrail is never bypassable by -y; there is no unattended way to wipe mainnet.
  • On devnet or localnet it takes a light y/N prompt instead, and there -y does skip it, so scripted test resets stay ergonomic.

Data accounts vs. the program account

Reclaim exists because closing the program and draining its data are two different jobs, and a fresh start needs both:

  • solana program close <id> + redeploy resets the program’s bytecode and reclaims the program account’s own rent. It does not touch the data PDAs — the mailboxes, aliases, domains, and fromboxes the program owns. Those live at addresses derived from the program ID, so a redeploy at the same vanity ID re-reads all the old state as if nothing happened.
  • reclaim does the other half: it drains those data PDAs (refunding their rent) but leaves the program bytecode in place.

A genuine clean slate therefore requires both: run reclaim to empty the data accounts, then close and redeploy the programs. Either step alone leaves the deployment half-reset.

It prints the program-close steps — it does not run them

After the data reclaim finishes, reclaim prints the remaining steps — the solana program close <id> commands and the cargo build-sbf / solana program deploy redeploy sequence for all three programs — and stops. It never runs them. Closing a program account is irreversible and, at the same vanity ID, hands the deployer a one-way choice; the CLI deliberately leaves that final, unrecoverable action to a human who has read what it printed and is ready to paste the commands.

Note: the entire sithbit postmaster command tree — including the read-only get — is gated behind the CLI’s postmaster feature, which is not part of the default feature set. Build the CLI with cargo build -p mail-client --features postmaster (or --all-features) to get it. The default build still exposes the read-only query, as sithbit postoffice postmaster — the same underlying lookup as postmaster get.