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
| Program | Address |
|---|---|
| Mail program | MaiLyqjRuHp8SSQHjiLMPmhBcuLitSta4YdoTiibXu4 |
| Alias program | ALiasg6qDnwcY8HfyeC1AjXRFjyqpXxW4omtwF1i125q |
| Domain program | DmaiNcmXsPw2juV9JoZSC47V5epAysQi3DJVk3fiBuUv |
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 constant | Bytes | Owning program |
|---|---|---|
POSTOFFICE_SEED | postoffice | |
MAIL_MESSAGE_SEED | emailmessage | |
PUB_ENCRYPTION_KEY_SEED | encryption_key | |
FROMBOX_SEED | frombox | |
PARTICIPANT_BEACON_SEED | participant_beacon | |
PENDING_MAILBOX_CLOSE_SEED | pending_mailbox_close | |
SENDER_REPUTATION_SEED | sender_reputation | |
MAIL_DOMAIN_SEED | maildomain | domain |
PENDING_DEACTIVATION_SEED | pending_deactivation | domain |
PENDING_RECLAIM_SEED | pending_reclaim | domain |
PROOF_WITNESS_SEED | proof_witness | domain |
DOMAIN_LISTING_SEED | domain_listing | domain |
SENDER_ATTESTATION_SEED | sender_attestation | domain |
ALIAS_ESCROW_SEED | alias_escrow | alias |
ALIAS_LISTING_SEED | alias_listing | alias |
ALIAS_BID_SEED | alias_bid | alias |
DOMAIN_ALIAS_SEED | domain_alias | alias |
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:
| Instruction | Emitted by |
|---|---|
SendMail | mail send |
DeleteMail | mail delete |
CreateMailbox | mailbox create (with --for, a sponsored create: the payer must be the named domain’s authority, and the owner rides the instruction data) |
UpdateMailbox | mailbox 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 |
UpdateFrombox | frombox update |
AddStamps | frombox stamp — same optional max_price_lamports ceiling, here checked against the frombox’s stored required_postage (error 107) |
InitPostoffice | postmaster init |
InstallCommitment | postmaster commitment (rides the variant position TransferPostmaster held before the delegation cutover — same discriminant, new ownership semantics) |
SetMailboxKey | mailbox key set |
WithdrawPostoffice | postmaster withdraw |
CloseFrombox | frombox close |
CloseMailbox | (disabled since v0.26.0 — refuses with custom error 94 InstantCloseDisabled; discriminant 16 kept so indexers resolve history) |
CloseKey | mailbox key close |
SetStampFee | postmaster fee stamp |
RefundMail | mail refund (recipient refuses; postage + rent go back to the sender) |
SetDomainFee | postmaster fee domain (a postoffice mutation, so it stays mail-side despite the name) |
SetAliasFee | postmaster fee alias |
SetAliasTierFees | postmaster fee alias-tiers (the four premium claim fees for 1–4 character names; each slot capped, over-cap refuses with custom error 98 AliasTierFeeAboveCap) |
SetRootKsk | postmaster ksk set (likewise a postoffice mutation — the anchor the domain program reads cross-program) |
ClaimBounty | mail claim-bounty |
RefundBounty | mail refund-bounty |
RotateDelegate | postmaster 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 discriminants | Instructions |
|---|---|
| 9–12 | CreateDomain, DeactivateDomain, CloseDomain, TransferDomainAuthority |
| 22–24 | RequestDeactivateDomain, CancelDeactivateDomain, FinalizeDeactivateDomain |
| 26–28 | AuthorizeDomainByProof, WriteProofWitness, CloseProofWitness |
| 31–33 | ListDomain, BuyDomain, CancelDomainListing |
| 35–37 | RequestReclaimByProof, 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):
| Instruction | Emitted by |
|---|---|
CreateDomain | domain create |
DeactivateDomain | domain deactivate --false (instant reactivation) |
CloseDomain | domain close |
TransferDomainAuthority | domain transfer |
RequestDeactivateDomain | domain deactivate (opens the two-step timelock) |
CancelDeactivateDomain | domain deactivate --cancel |
FinalizeDeactivateDomain | domain deactivate --finalize |
AuthorizeDomainByProof | domain authorize (permissionless DNSSEC-proof authorization) |
WriteProofWitness | domain authorize / domain reclaim (stages the RRSIG-chain witness buffer) |
CloseProofWitness | domain authorize --close-witness (reclaims a leftover witness buffer) |
ListDomain | domain sell |
BuyDomain | domain buy |
CancelDomainListing | domain sell --cancel |
RequestReclaimByProof | domain reclaim (opens the reclaim timelock by DNSSEC proof) |
FinalizeReclaimByProof | domain 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 |
CancelReclaimByProof | domain 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— everypostmaster feecommand),SetRootKsk, the alias program’s fee-waived bulk reservation. A non-delegate signer is refused with code 66 (NotDelegate). The threeAdminCloseAccountreclaim 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, andRotateDelegate. 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
| Instruction | Emitted by |
|---|---|
CreateAlias | alias 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) |
CloseAlias | alias close |
OfferTransferAlias | alias transfer init (--fee defaults to 0 — a free hand-off) |
AcceptTransferAlias | alias transfer accept |
CancelTransferAlias | alias transfer cancel |
ListAlias | alias sell |
BuyAlias | alias buy |
CancelAliasListing | alias sell --cancel |
SellAlias | alias sell --auction (opens an ascending-bid auction) |
BidAlias | alias bid |
SettleAuction | alias 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 |
RegisterDomainAlias | alias register-domain (domain-scoped aliases) |
RemoveDomainAlias | alias remove-domain |
UpdateDomainAlias | alias 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:
| Field | Bytes | Meaning |
|---|---|---|
holder | 32 | The seller — the alias’s current holder, or the domain’s current authority; paid on purchase, and the only address that may cancel. |
price_lamports | 8 | The fixed price any buyer pays. |
created_at | 8 | Unix time the listing was staged; anchors the binding window. |
expires_at | 8 | Unix 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):
| Instruction | Accounts, 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:
| Code | Variant | Message |
|---|---|---|
| 57 | ListingPriceZero | A listing’s price must be positive; zero-price hand-offs use the transfer paths |
| 58 | ListingExpiryInvalid | A listing’s expiry must be in the future |
| 59 | ListingStillBinding | The listing is still in its binding window |
| 60 | NoListingForAlias | No listing is open for this alias |
| 61 | NoListingForDomain | No listing is open for this domain |
| 62 | ListingExpired | The listing has expired |
| 63 | AliasHasPendingListing | The alias has an open listing; cancel it before closing or transferring |
| 64 | DomainHasPendingListing | The domain has an open listing; cancel it before closing, transferring, or deactivating |
| 65 | ListingHolderMismatch | The 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:
| Code | Variant | Message |
|---|---|---|
| 84 | InstructionMoved | This instruction has moved to the domain program |
Raised by the mail program for the sixteen retired domain discriminants.
The participant-beacon tail (item 43):
| Code | Variant | Message |
|---|---|---|
| 86 | ParticipantBeaconAccountInfo | Failed to retrieve participant-beacon account info |
| 87 | ParticipantBeaconAlreadyExists | The wallet already has a participant beacon |
| 88 | ParticipantBeaconNotInitialized | No participant beacon exists for this wallet |
| 89 | ParticipantBeaconRequiresMailbox | A participant beacon requires the wallet’s mailbox to exist |
| 90 | DetailCidTooLong | The 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:
| Code | Variant | Message |
|---|---|---|
| 91 | MailboxCloseAlreadyPending | A close is already pending for this mailbox |
| 92 | NoPendingMailboxClose | No close is pending for this mailbox |
| 93 | MailboxCloseTimelockNotElapsed | The mailbox-close timelock has not yet elapsed |
| 94 | InstantCloseDisabled | Instant 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:
| Code | Variant | Message |
|---|---|---|
| 95 | SponsoredMailboxRequiresDomain | A sponsored mailbox (payer ≠ owner) must name a domain |
| 96 | UnauthorizedDomainSponsor | Only the named domain’s on-chain authority may sponsor a mailbox for another owner |
| 97 | FunderAccountInfo | Failed 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):
| Code | Variant | Message |
|---|---|---|
| 101 | SenderAttestationFeeAboveCap | The sender-attestation fee exceeds its protocol cap |
Reputation-scaled first-contact pricing appended one more, the floor setter’s cap twin:
| Code | Variant | Message |
|---|---|---|
| 102 | ReputationFloorAboveCap | The 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:
| Code | Variant | Message |
|---|---|---|
| 105 | PinLeaseAccountInfo | The pin-lease account is missing from the instruction |
| 106 | AdminCloseEscrowPresent | The admin-close target still holds escrow above its rent-exempt minimum |
| 107 | PriceExceedsMax | The 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.