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

Program & PDA reference

This is a reference page — for behavior and pricing, see the topic pages and Economics. It’s here for readers who want to see exactly what’s on-chain.

Program IDs

ProgramAddress
Mail programMaiLyqjRuHp8SSQHjiLMPmhBcuLitSta4YdoTiibXu4
Alias programALiasg6qDnwcY8HfyeC1AjXRFjyqpXxW4omtwF1i125q
Domain programDmaiNcmXsPw2juV9JoZSC47V5epAysQi3DJVk3fiBuUv

The domain program carries the whole domain registry — domain lifecycle, DNSSEC-proof authorization/reclaim, and the domain marketplace — split out of the mail program (which originally hosted those instructions; see the retired discriminants below). The postoffice singleton stays a mail-program account: the alias and domain programs read it cross-program for the fees, the root KSK fingerprint, and the delegate gate.

PDA seeds

Seed constantBytesOwning program
POSTOFFICE_SEEDpostofficemail
MAIL_MESSAGE_SEEDemailmessagemail
PUB_ENCRYPTION_KEY_SEEDencryption_keymail
FROMBOX_SEEDfromboxmail
PARTICIPANT_BEACON_SEEDparticipant_beaconmail
PENDING_MAILBOX_CLOSE_SEEDpending_mailbox_closemail
SENDER_REPUTATION_SEEDsender_reputationmail
MAIL_DOMAIN_SEEDmaildomaindomain
PENDING_DEACTIVATION_SEEDpending_deactivationdomain
PENDING_RECLAIM_SEEDpending_reclaimdomain
PROOF_WITNESS_SEEDproof_witnessdomain
DOMAIN_LISTING_SEEDdomain_listingdomain
SENDER_ATTESTATION_SEEDsender_attestationdomain
ALIAS_ESCROW_SEEDalias_escrowalias
ALIAS_LISTING_SEEDalias_listingalias
ALIAS_BID_SEEDalias_bidalias
DOMAIN_ALIAS_SEEDdomain_aliasalias

Mailbox and alias accounts don’t use a named seed constant — they derive directly from the owner’s wallet address (mailbox) or the alias name’s blake3 hash (alias); the participant beacon combines PARTICIPANT_BEACON_SEED with the owner’s wallet address, so each wallet carries at most one beacon and it can never collide with the bare-seeded mailbox. The sender-reputation account — the record behind reputation-scaled first-contact pricing — likewise combines SENDER_REPUTATION_SEED with the sender’s wallet address: one cumulative-spend record per wallet, owned by the mail program. The mailbox close timelock’s transient pending account combines PENDING_MAILBOX_CLOSE_SEED with that same wallet address, and the prefix is doubly load-bearing there: the Mailbox PDA is bare-seeded on the address, and PendingMailboxClose, PendingDeactivation and PendingReclaim all serialize to the same eight bytes — so a size-filtered account scan cannot tell the three apart and only the derivation can. The alias-transfer escrow and alias-listing accounts combine ALIAS_ESCROW_SEED / ALIAS_LISTING_SEED with that same blake3 alias hash; the domain-listing account combines DOMAIN_LISTING_SEED with the domain name’s blake3 hash, and the deactivation timelock’s transient pending-deactivation account likewise combines PENDING_DEACTIVATION_SEED with the domain hash. Seeding a listing on the asset itself means each alias or domain can carry at most one listing at a time, structurally. The verified-sender attestation combines SENDER_ATTESTATION_SEED with the domain’s blake3 hash and the attested wallet address — two variable seeds, so a domain carries one attestation per wallet, any number of wallets. Every domain-side account — MailDomain itself, the listing, the two timelock accounts, the proof-witness buffer, and the sender attestation — derives under and is owned by the domain program.

MailInstruction variants

Each variant is the on-chain instruction a sithbit command ultimately submits:

InstructionEmitted by
SendMailmail send
DeleteMailmail delete
CreateMailboxmailbox create (with --for, a sponsored create: the payer must be the named domain’s authority, and the owner rides the instruction data)
UpdateMailboxmailbox update
CreateFrombox frombox stamp / frombox update (create-if-absent). Carries the purchaser’s optional max_price_lamports slippage ceiling, checked against the effective first-contact price; over-ceiling refuses with custom error 107 PriceExceedsMax
UpdateFromboxfrombox update
AddStampsfrombox stamp — same optional max_price_lamports ceiling, here checked against the frombox’s stored required_postage (error 107)
InitPostoffice postmaster init
InstallCommitmentpostmaster commitment (rides the variant position TransferPostmaster held before the delegation cutover — same discriminant, new ownership semantics)
SetMailboxKeymailbox key set
WithdrawPostofficepostmaster withdraw
CloseFromboxfrombox close
CloseMailbox(disabled since v0.26.0 — refuses with custom error 94 InstantCloseDisabled; discriminant 16 kept so indexers resolve history)
CloseKeymailbox key close
SetStampFeepostmaster fee stamp
RefundMailmail refund (recipient refuses; postage + rent go back to the sender)
SetDomainFeepostmaster fee domain (a postoffice mutation, so it stays mail-side despite the name)
SetAliasFeepostmaster fee alias
SetAliasTierFeespostmaster fee alias-tiers (the four premium claim fees for 1–4 character names; each slot capped, over-cap refuses with custom error 98 AliasTierFeeAboveCap)
SetRootKskpostmaster ksk set (likewise a postoffice mutation — the anchor the domain program reads cross-program)
ClaimBountymail claim-bounty
RefundBountymail refund-bounty
RotateDelegatepostmaster delegate
AdminCloseAccount (discriminant 38)postmaster reclaim (delegate-signed devnet/reset tool, --features reclaim); refuses a target holding value above its rent-exempt minimum with custom error 106 AdminCloseEscrowPresent
CreateParticipantBeacon (39)participant-pool opt-in (item 43); owner-signed, requires the wallet’s mailbox — CLI authoring lands with item 44
UpdateParticipantBeacon (40)wholesale profile rewrite (tags + sealed-detail CID; None clears the CID)
CloseParticipantBeacon (41)opt-out: closes the beacon, refunding its rent to the owner
RequestCloseMailbox (42)mailbox close — opens the close timelock; mailbox stays open, no rent refunded
CancelCloseMailbox (43)mailbox close --cancel — drops the pending record, refunding its rent
FinalizeCloseMailbox (44)mailbox close --finalize — past the timelock, closes the mailbox; the pending account’s rent refunds to the owner and the mailbox’s rent to its recorded funder (a 4th funder account is required when it differs from the owner)
SetSettlementBps (46)postmaster fee settlement (both settlement bps rates in one instruction: the operator share of settled value, over-cap refuses with custom error 99 OperatorShareBpsAboveCap, and the stamp-purchase fee rate, error 100 StampFeeBpsAboveCap; a zero rate stores the “unset” sentinel and resolves to its protocol default)
SetSenderAttestationFee (47)postmaster fee attestation (the flat one-time verified-sender attestation fee; over-cap refuses with custom error 101 SenderAttestationFeeAboveCap, and a zero fee stores the “unset” sentinel and resolves to the protocol default; the setter grows a legacy postoffice account to the 184-byte layout in place)
SetReputationFloor (48)postmaster fee reputation-floor (the floor of reputation-scaled first-contact pricing, in bps of a mailbox’s default postage; over-cap refuses with custom error 102 ReputationFloorAboveCap, a zero rate stores the “unset” sentinel and resolves to the protocol default, and the setter grows a legacy postoffice account to the 192-byte layout in place)
CreatePinLease (49)mail lease create — mints the per-(CID, holder) pinning-lease account with the deposit escrowed above its rent (below-minimum refuses with custom error 104 PinLeaseDepositBelowMinimum); the program hashes the passed message’s CID into the lease derivation itself, so a mismatched lease address refuses with 17 InvalidDerivedAccount, and the one-time fee splits with the recipient’s domain authority at the operator share
ClosePinLease (50)mail lease close — holder-signed drain returning deposit + rent; carries the CID hash rather than a message id, so it works after the message account itself has settled and deallocated
SetPinLeaseFee (51)postmaster fee pin-lease (the one-time lease creation fee; over-cap refuses with custom error 103 PinLeaseFeeAboveCap, a zero fee stores the “unset” sentinel and resolves to the protocol default, and the setter grows a legacy postoffice account to the 200-byte layout in place)
ReclaimFromboxStamps (52) frombox reclaim — the sender’s withdrawal of its own unspent prepaid postage. Payload-free: the program recomputes the “from” hash from the signer’s address bytes, so reproducing the frombox derivation is the authorization and no separate authority field exists. Drains the balance above rent and zeroes the stamp count, leaving the account alive on its rent; a frombox keyed on an email string hashes text no wallet key can reproduce, so it stays recipient-managed

Retired mail-side domain discriminants

The sixteen domain-registry instructions originally lived in the mail program and moved wholesale to the domain program. Their variants stay in the MailInstruction enum — the borsh discriminant is wire ABI, so removing or reordering them would renumber every later instruction — but the mail program no longer carries their processors. Sending one of these discriminants to the mail program is rejected with error 84 (InstructionMoved, “This instruction has moved to the domain program”):

Retired discriminantsInstructions
9–12CreateDomain, DeactivateDomain, CloseDomain, TransferDomainAuthority
22–24RequestDeactivateDomain, CancelDeactivateDomain, FinalizeDeactivateDomain
26–28AuthorizeDomainByProof, WriteProofWitness, CloseProofWitness
31–33ListDomain, BuyDomain, CancelDomainListing
35–37RequestReclaimByProof, FinalizeReclaimByProof, CancelReclaimByProof

SetDomainFee (17) and SetRootKsk (25) are not in the retired set: despite their domain-flavored names they mutate the postoffice — a mail-program account — and stay mail-side.

DomainInstruction variants

The domain program’s enum, borsh discriminants 0–18. The CLI surface did not change with the split — the same sithbit domain commands now submit these to the domain program, with account-meta lists byte-identical to the retired mail-side twins (discriminants 17–18, the verified-sender attestation pair, postdate the split and never had mail-side twins):

InstructionEmitted by
CreateDomain domain create
DeactivateDomaindomain deactivate --false (instant reactivation)
CloseDomaindomain close
TransferDomainAuthoritydomain transfer
RequestDeactivateDomaindomain deactivate (opens the two-step timelock)
CancelDeactivateDomaindomain deactivate --cancel
FinalizeDeactivateDomaindomain deactivate --finalize
AuthorizeDomainByProofdomain authorize (permissionless DNSSEC-proof authorization)
WriteProofWitnessdomain authorize / domain reclaim (stages the RRSIG-chain witness buffer)
CloseProofWitnessdomain authorize --close-witness (reclaims a leftover witness buffer)
ListDomaindomain sell
BuyDomaindomain buy
CancelDomainListingdomain sell --cancel
RequestReclaimByProofdomain reclaim (opens the reclaim timelock by DNSSEC proof)
FinalizeReclaimByProofdomain reclaim --finalize — permissionless to crank, but the pending record stores the wallet that funded the request and the rent refund is pinned to it, so a stranger turning the crank cannot capture the requester’s deposit
CancelReclaimByProofdomain reclaim --cancel
AdminCloseAccount (discriminant 16)postmaster reclaim (delegate-signed devnet/reset tool, --features reclaim); same value guard as its mail-side twin — over-rent targets refuse with error 106 AdminCloseEscrowPresent
AttestSender (17)domain attest-sender (permissionless DNSSEC-proof verified-sender attestation; the attested wallet rides the payload, no MailDomain account is involved, and the one-time fee lands on the postoffice)
RevokeSenderAttestation (18)domain revoke-attestation (holder-signed close of the (domain, wallet) attestation; the PDA re-derives from the signer, so a non-holder never reaches it, and the rent refunds to the attested wallet)

Domain instructions authenticate against the mail program’s postoffice read cross-program: the delegate gate, the authorization fee, and the root KSK all come from that account, and fees still settle into it — the split moved the registry, not the treasury.

Postoffice admin gates

Since the delegation cutover the postoffice stores no postmaster pubkey — only the standing delegate wallet and a 32-byte ownership commitment root (see The Postmaster). The admin instructions — across all three programs — split accordingly:

  • Delegate-gated (operational): every domain-program instruction including CreateDomain (the deactivation two-step, CloseDomain, TransferDomainAuthority, …), the mail-side fee setters (SetStampFee, SetDomainFee, SetAliasFee, SetAliasTierFees, SetSettlementBps, SetSenderAttestationFee, SetReputationFloor, SetPinLeaseFee — every postmaster fee command), SetRootKsk, the alias program’s fee-waived bulk reservation. A non-delegate signer is refused with code 66 (NotDelegate). The three AdminCloseAccount reclaim twins are delegate-gated too, with their own refusal code (AdminCloseUnauthorized) — and, on top of the authority check, a value guard: a target holding lamports above its rent-exempt minimum is refused with code 106 (AdminCloseEscrowPresent), so the tool reaps only rent-empty leftover state and can never seize escrowed postage, a bounty, or a live bid.
  • Ownership-gated (chain-key proof): InstallCommitment, WithdrawPostoffice, and RotateDelegate. The signer is a revealed ceremony chain key presenting a Merkle membership path against the current commitment root, and the instruction installs the successor generation’s root (rotate-on-use). A proof that does not verify is refused with code 67 (InvalidCommitmentProof).

One naming footnote for error readers: the account-slot error PostmasterAccountInfo (code 9) now labels the delegate/chain-signer account slot — the variant name is kept for ABI stability (the enum’s numeric position is the on-chain error code, so variants are never renamed in place).

AliasInstruction variants

InstructionEmitted by
CreateAliasalias create (and automatically by mailbox create) — charges the length-tiered claim fee: the premium tier for 1–4 character names, the flat fee from 5 up; waived for the delegate
TransferAlias(disabled since v0.7.0 — refuses with custom error 85 UnilateralTransferDisabled; discriminant kept for history)
CloseAliasalias close
OfferTransferAliasalias transfer init (--fee defaults to 0 — a free hand-off)
AcceptTransferAliasalias transfer accept
CancelTransferAliasalias transfer cancel
ListAliasalias sell
BuyAliasalias buy
CancelAliasListingalias sell --cancel
SellAliasalias sell --auction (opens an ascending-bid auction)
BidAliasalias bid
SettleAuctionalias settle-auction
AdminCloseAccount (discriminant 12)postmaster reclaim (delegate-signed devnet/reset tool, --features reclaim); same value guard as its mail-side twin — over-rent targets refuse with error 106 AdminCloseEscrowPresent
RegisterDomainAliasalias register-domain (domain-scoped aliases)
RemoveDomainAliasalias remove-domain
UpdateDomainAliasalias update-domain

Marketplace listing accounts

An open marketplace listing is staged in a program-owned account — AliasListing (alias program) or DomainListing (domain program) — created by ListAlias/ListDomain and closed when the listing completes. Both share one fixed 56-byte borsh layout, so a listing’s rent is a constant and replacing a listing in place never changes it:

FieldBytesMeaning
holder32The seller — the alias’s current holder, or the domain’s current authority; paid on purchase, and the only address that may cancel.
price_lamports8The fixed price any buyer pays.
created_at8Unix time the listing was staged; anchors the binding window.
expires_at8Unix time the listing lapses; purchase is legal through this instant, refused after.

Six pre-existing instructions grew a read-only listing account at the tail of their account lists to guard against conflicting state: CloseAlias and CloseDomain refuse while a listing is open (reaping the asset would strand the listing’s rent), OfferTransferAlias refuses while a listing is open (an alias can’t carry both a private transfer offer and an open listing — and since v0.7.0 the offer is the only alias transfer path), TransferDomainAuthority refuses while a listing is open (repointing a name under a live listing would leave a stale holder recorded as the seller, able to collect the sale proceeds — the holder must cancel first; the alias-side TransferAlias carried the same guard until it was disabled outright in v0.7.0), and RequestDeactivateDomain refuses while a listing is open (a deactivation timelock finalizing under a live listing would flip the domain’s state under the buyer — the authority must cancel, or the sale complete, first). In each case the listing slot must be an uninitialized PDA for the instruction to proceed. BuyDomain carries the mirror-image guard: a read-only pending-deactivation slot at the tail of its account list, which must be uninitialized — a purchase is refused while a deactivation timelock is in flight. BuyDomain also re-checks, against the mail-domain account it already carries, that the domain is still active at the moment of purchase: an inactive domain stays listable (the flag is stable and plainly readable on-chain), but the sale settles only once the domain is reactivated — so a deactivation finalized under a live listing (reachable only in pre-guard history) can never sell a dead name. BuyAlias needs no extra slot for its guard: it re-checks the listing’s recorded holder against the alias account it already carries.

Marketplace account lists

The full account lists of the guard-reshaped mutation instructions, in slot order (the CLI’s builders pin these byte-for-byte):

InstructionAccounts, in slot order
TransferDomainAuthority (4)delegate (signer, writable) · postoffice (readonly) · mail domain (writable) · domain listing (readonly)
RequestDeactivateDomain (7)delegate (signer, writable) · postoffice (readonly) · mail domain (readonly) · system program (readonly) · pending deactivation (writable) · rent payer (signer, writable) · domain listing (readonly)
BuyDomain (9)buyer (signer, readonly) · system program (readonly) · mail domain (writable) · domain listing (writable) · current authority (writable) · postoffice (writable) · price payer (signer, writable) · pending deactivation (readonly) · pending reclaim (readonly)
TransferAlias (5)holder (signer, writable) · alias (writable) · postoffice (writable) · system program (writable) · alias listing (readonly)
BuyAlias (7)buyer (signer, readonly) · system program (readonly) · alias (writable) · alias listing (writable) · current holder (writable) · postoffice (writable) · price payer (signer, writable)

A self-funded buy passes the buyer again in the price-payer slot; the runtime merges the duplicate metas into one writable signer.

Marketplace error codes

SithBitError variants convert to ProgramError::Custom(code), with the variant’s numeric position as the on-chain code (the enum is append-only for exactly this reason). The marketplace tail:

CodeVariantMessage
57ListingPriceZeroA listing’s price must be positive; zero-price hand-offs use the transfer paths
58ListingExpiryInvalidA listing’s expiry must be in the future
59ListingStillBindingThe listing is still in its binding window
60NoListingForAliasNo listing is open for this alias
61NoListingForDomainNo listing is open for this domain
62ListingExpiredThe listing has expired
63AliasHasPendingListingThe alias has an open listing; cancel it before closing or transferring
64DomainHasPendingListingThe domain has an open listing; cancel it before closing, transferring, or deactivating
65ListingHolderMismatchThe listing holder no longer owns the listed name

BuyDomain’s pending-deactivation refusal reuses code 29 (DeactivationAlreadyPending, “A deactivation is already pending for this domain”) — the same error RequestDeactivateDomain and ListDomain raise; the error enum is append-only, so guards reuse existing codes where one fits. On the same principle, RequestDeactivateDomain’s open-listing refusal reuses code 64 (DomainHasPendingListing), and BuyDomain’s inactive-domain refusal reuses code 25 (InactiveDomain, “Domain is not registered or is deactivated”).

All three programs share the single SithBitError enum, so a given code means the same thing whichever program raised it. The program split appended one variant:

CodeVariantMessage
84InstructionMovedThis instruction has moved to the domain program

Raised by the mail program for the sixteen retired domain discriminants.

The participant-beacon tail (item 43):

CodeVariantMessage
86ParticipantBeaconAccountInfoFailed to retrieve participant-beacon account info
87ParticipantBeaconAlreadyExistsThe wallet already has a participant beacon
88ParticipantBeaconNotInitializedNo participant beacon exists for this wallet
89ParticipantBeaconRequiresMailboxA participant beacon requires the wallet’s mailbox to exist
90DetailCidTooLongThe sealed-detail CID exceeds the maximum length

The mailbox close timelock tail — the mailbox-scoped twins of the domain-deactivation errors, appended as one block:

CodeVariantMessage
91MailboxCloseAlreadyPendingA close is already pending for this mailbox
92NoPendingMailboxCloseNo close is pending for this mailbox
93MailboxCloseTimelockNotElapsedThe mailbox-close timelock has not yet elapsed
94InstantCloseDisabledInstant CloseMailbox is disabled; use RequestCloseMailbox and FinalizeCloseMailbox after the timelock elapses

Code 94 gets its own variant rather than reusing a close error, mirroring item 30’s UnilateralTransferDisabled (85): a client hitting a disabled instruction needs to hear that, not be told to open a pending close it cannot then finalize instantly.

The sponsored-mailbox errors (payer ≠ owner creates and their funder-refund close) follow:

CodeVariantMessage
95SponsoredMailboxRequiresDomainA sponsored mailbox (payer ≠ owner) must name a domain
96UnauthorizedDomainSponsorOnly the named domain’s on-chain authority may sponsor a mailbox for another owner
97FunderAccountInfoFailed to retrieve the funder account the mailbox rent refunds to

The verified-sender attestation appended one variant (its fee-cap twin of codes 98–100, which are noted inline on their setters’ instruction rows above):

CodeVariantMessage
101SenderAttestationFeeAboveCapThe sender-attestation fee exceeds its protocol cap

Reputation-scaled first-contact pricing appended one more, the floor setter’s cap twin:

CodeVariantMessage
102ReputationFloorAboveCapThe reputation floor exceeds its protocol cap

A purchase presenting a present-but-invalid attestation account in the reputation tail is refused rather than silently repriced — wrong owner raises the runtime’s IllegalOwner, an attestation for a different wallet raises code 19 (WrongAddressForInstruction), and a wrong derivation raises code 17 (InvalidDerivedAccount) — codes clients can match on.

Pinning leases added a missing-account code alongside the two fee codes noted inline above, and the money-path hardening added two more — one guarding the admin reclaim tool, one the purchaser’s slippage ceiling:

CodeVariantMessage
105PinLeaseAccountInfoThe pin-lease account is missing from the instruction
106AdminCloseEscrowPresentThe admin-close target still holds escrow above its rent-exempt minimum
107PriceExceedsMaxThe stamp price exceeds the purchaser’s maximum

Code 106 is what makes AdminCloseAccount safe to expose: in all three programs it now refuses any target whose balance sits above its rent-exempt minimum, so the reclaim tool can only reap rent-empty leftover state — never a sender’s escrowed postage, a reply bounty, or a live auction bid. Code 107 is the purchaser’s side of the same principle: the recipient sets the price and may raise it at any moment, so a buyer who supplied a ceiling gets a revert instead of a silent overpayment.