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 | |
PIN_LEASE_SEED | pin_lease | |
SPONSOR_COUNTER_SEED | sponsor_counter | |
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 |
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 pinning lease’s
account combines PIN_LEASE_SEED with the blake3 hash of the leased
message’s CID and the holder wallet address — two variable seeds, so
a wallet can hold at most one lease per CID, any number of CIDs; it is
owned by the mail program. A domain’s sponsor counter combines
SPONSOR_COUNTER_SEED with the MailDomain account address (the value a
mailbox’s domain field stores), and derives under and is owned by the
mail program even though the MailDomain itself belongs to the domain
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 and
PendingDeactivation both serialize to the same eight bytes (a bare
requested_at: i64) — so a size-filtered account scan cannot tell those
two apart and only the derivation can; PendingReclaim also carries the
proof-established authority and the rent payer, so it serializes to
72 bytes and a size-filtered scan already sets it apart from the other
two. 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.
Pre-funded PDA addresses
Anyone can transfer lamports to a PDA address before the program creates
an account there, which leaves a system-owned account with no data at
that address. The mail, domain and alias programs treat such an address as empty: an
account counts as existing only when it is owned by the program and holds
data. A create over a pre-funded address transfers only the shortfall to
the account’s target balance, then allocates and assigns it, signed with
the PDA seeds — so the payer pays nothing when the parked lamports already
cover the rent, and any lamports above the target stay in the account and
leave with it when it closes. The one exception is the first BidAlias: the
bidder always pays rent plus their bid in full whatever the address already
holds, and the parked lamports stay in the escrow as surplus.
So a stranger’s transfer never blocks a request, and never makes a cancel or finalize report the wrong error:
- Requests and creates proceed:
RequestCloseMailbox,RequestDeactivateDomain,RequestReclaimByProof,ListDomain,WriteProofWitness,AttestSender,CreateDomain,AuthorizeDomainByProof,CreateAlias,OfferTransferAlias,ListAlias,SellAliasand a firstBidAlias. - Guards ignore it: the open-listing, pending-offer and
pending-request slots of
CloseDomain,TransferDomainAuthority,RequestDeactivateDomain,ListDomain,BuyDomain,OfferTransferAlias,ListAlias,SellAliasandCloseAlias. - “Nothing pending” errors stay accurate:
CancelCloseMailboxandFinalizeCloseMailboxreportNoPendingMailboxClose;CancelDeactivateDomainandFinalizeDeactivateDomainreportNoPendingDeactivation;CancelReclaimByProofandFinalizeReclaimByProofreportNoPendingReclaim;CancelDomainListingandBuyDomainreportNoListingForDomain(also for a listing slot owned by another program).
An alias bid escrow therefore holds at least rent plus the standing high bid: the first bidder pays rent plus their bid in full even over parked lamports, an outbid bidder is refunded exactly their recorded bid, and whatever a stranger parked returns to the winner with the rent at settlement.
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 or its sponsor delegate, the owner rides the instruction data, and the domain’s sponsor counter rides as a writable account after the MailDomain) |
UpdateMailbox | mailbox update (the owner’s activation of the mailbox: for a sponsored mailbox the owner has not yet activated, the account list ends with the mailbox’s stored MailDomain and that domain’s sponsor counter, see Sponsored-mailbox activation accounts) |
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 — refuses with custom error 94 InstantCloseDisabled; discriminant 16 kept so indexers resolve historical transactions) |
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 on the CLI and the program — a launch build compiles the handler out and refuses the instruction with custom error 108 AdminCloseDisabled); a reclaim-capable build still refuses a target holding value above its rent floor with custom error 106 AdminCloseEscrowPresent, and a target named as its own rent recipient with custom error 117 AdminCloseSelfRecipient |
CreateParticipantBeacon (39) | campaign create — participant-pool opt-in (item 43); owner-signed, requires the wallet’s mailbox |
UpdateParticipantBeacon (40) | campaign update — wholesale profile rewrite (tags + sealed-detail CID; None clears the CID) |
CloseParticipantBeacon (41) | campaign close — 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 (the request is also the owner’s activation of the mailbox, so its mailbox account is writable and a sponsored, not-yet-activated mailbox adds the activation pair) |
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 16 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 |
ReclaimDormantMailbox (53) | mailbox reclaim-dormant — the recorded funder of a sponsored mailbox closes it and takes back its whole balance once the owner has neither activated it nor received mail and MAILBOX_DORMANCY_GRACE_SECS have passed since its created_at. Payload-free; the account list is under Sponsored-mailbox activation accounts and the ordered refusals under Marketplace error codes |
Sponsored-mailbox activation accounts
A mailbox counts as unclaimed in its domain’s sponsor counter while a
sponsor funded it (funder ≠ owner) and the owner has not activated it.
The owner’s first UpdateMailbox or RequestCloseMailbox is the
activation: it sets owner_activated and, for such a mailbox, releases it
from the counter. That update or request therefore ends its account list
with a pair after the fixed slots:
- the
MailDomainaccount at the mailbox’s stored domain address (readonly) — forUpdateMailboxthe domain before the update applies, not the new domain being set; - that domain’s sponsor-counter PDA (writable).
| Instruction | Account list |
|---|---|
UpdateMailbox | owner (signer) · system program · mailbox (writable) · new domain’s MailDomain (readonly, only when the update sets a non-zero domain) · stored domain’s MailDomain (readonly) · sponsor counter (writable) |
RequestCloseMailbox (42) | owner (signer, writable) · mailbox (writable, it records the activation) · pending close (writable) · system program · stored domain’s MailDomain (readonly) · sponsor counter (writable) |
ReclaimDormantMailbox (53) | funder (signer, writable) · owner (readonly) · mailbox (writable) · MailDomain at the mailbox’s domain (readonly) · sponsor counter (writable) |
An owner-created mailbox, or one the owner already activated, reads no
account behind the fixed slots. A sponsored, unactivated mailbox whose pair
is missing is refused with custom error 115 SponsorCounterAccountInfo.
The pair is checked by address alone: the MailDomain must sit at the
mailbox’s stored domain address and the counter must be the mail-program-owned
PDA seeded on it. The MailDomain’s data is never read, so the release
still works after the domain account has closed. The release lowers the
counter’s unclaimed count by one, saturating at zero, and only for a mailbox
whose created_at is at or after the counter’s reset_at. Both are whole
chain seconds, so a mailbox created in the same second as an
authority-change reset, but before it, is still released and takes one count
from the new authority; this is accepted. The sithbit CLI and the webmail
Mailbox pane append the pair automatically.
ReclaimDormantMailbox takes the same pair and releases through the same
rule; its refusals and their order are listed under
Marketplace error codes.
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 (20) 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–19. 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, and 19, SetSponsorPolicy, 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 (also clears the domain’s sponsor delegate) |
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 (also clears the domain’s sponsor delegate) |
CancelDomainListing | domain sell --cancel |
RequestReclaimByProof | domain reclaim (opens the reclaim timelock by DNSSEC proof) |
FinalizeReclaimByProof | domain reclaim --finalize (also clears the domain’s sponsor delegate) — 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 on the CLI and the program); same gating and value guard as its mail-side twin — launch builds refuse with error 108 AdminCloseDisabled, over-rent targets with error 106 AdminCloseEscrowPresent, a target named as its own rent recipient with error 117 AdminCloseSelfRecipient |
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) |
SetSponsorPolicy (19) | domain sponsor-policy — the domain’s current authority overwrites its sponsor_cap, sponsor_rate_per_epoch and sponsor_delegate (zero means unlimited for the cap and rate; an all-zero delegate means none). Authority-signed like ListDomain: the postmaster and the sponsor delegate are refused with IllegalAuthority, and is_active is not consulted |
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): the domain-program administration
including
CreateDomain(the deactivation two-step,CloseDomain,TransferDomainAuthority, …; the authority-signed marketplace andSetSponsorPolicyinstructions and the permissionless proof paths are the exceptions), 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 they exist only in builds compiled with thereclaimfeature; a launch build refuses the instruction outright with code 108 (AdminCloseDisabled). On top of the authority check, a value guard: a target holding lamports above its rent floor — the higher of the live Rent-sysvar minimum and the SDK-default minimum,program_common::admin_close_minimum_balance— 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. A target named as its own rent recipient is refused with code 117 (AdminCloseSelfRecipient) before any lamports move. - 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 — refuses with custom error 85 UnilateralTransferDisabled; discriminant kept so historical transactions remain decodable) |
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 on the CLI and the program); same gating and value guard as its mail-side twin — launch builds refuse with error 108 AdminCloseDisabled, over-rent targets with error 106 AdminCloseEscrowPresent, a target named as its own rent recipient with error 117 AdminCloseSelfRecipient |
Removed domain-scoped alias discriminants
Discriminants 13–15 were RegisterDomainAlias, RemoveDomainAlias, and
UpdateDomainAlias — a per-domain alias namespace, removed outright because
it let a domain authority capture mail for a global alias holder resident on
that domain (see the threat model).
Unlike the retired mail-side domain
discriminants above, these were
deleted rather than kept as tombstones: they were the tail of
AliasInstruction, so removing them renumbered nothing. Their error codes
81 (DomainAliasAccountInfo), 82 (DomainAuthorityMismatch), and
83 (NoDomainAlias) do remain in SithBitError — those sit mid-enum,
where the numeric position is the on-chain code, so deleting them would
renumber every error below.
Trap for the next append. The next variant added to
AliasInstructioninherits discriminant 13. A replayed pre-removalRegisterDomainAliastransaction would then decode as that new instruction. The payloads differ, so borsh will almost certainly reject it — but confirm no such transaction can still be replayed against a live cluster before appending, or burn 13–15 with placeholder variants first.
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. Each listing type has its own fixed-size borsh
layout, so each listing’s rent is a constant and replacing a listing in
place never changes it: DomainListing is 56 bytes; AliasListing
carries the same holder/price_lamports/created_at/expires_at
fields plus a sale-mode flag and the auction-only fields below, for a
fixed ALIAS_LISTING_LEN of 105 bytes:
| Field | Bytes | In | Meaning |
|---|---|---|---|
holder | 32 | both | 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 | both | For a fixed-price listing, the price any buyer pays; for an alias auction, the reserve/opening price a first bid must meet. |
created_at | 8 | both | Unix time the listing begins; anchors the binding window. |
expires_at | 8 | both | Unix time the listing lapses; purchase (or, for an alias auction, bidding) is legal through this instant, refused after. |
mode | 1 | alias only | Which ListingMode the listing runs — fixed-price or auction; zeroed fields below stay unused for a fixed-price listing. |
antisnipe_window_secs | 8 | alias only | Anti-snipe reset window for an auction: a bid landing within this many seconds of expires_at pushes it out by the same amount. Zero for a fixed-price listing. |
high_bid_lamports | 8 | alias only | The standing high bid on an auction listing; zero before the first bid and for a fixed-price listing. |
high_bidder | 32 | alias only | The standing high bidder on an auction listing; zero before the first bid and for a fixed-price listing. |
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 — 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), 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 not hold a live listing for the instruction to proceed (see
Pre-funded PDA addresses). BuyDomain carries the mirror-image guard:
a read-only pending-deactivation slot at the tail of its account
list, which must not hold a live request — 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 |
|---|---|
SetSponsorPolicy (19) | authority (signer, readonly) · mail domain (writable) |
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; the advertised participation price is its default postage |
| 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 or its sponsor delegate 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 16 (InvalidDerivedAccount) — codes clients can match on.
Pinning leases added a missing-account code alongside the two fee codes noted inline above, the money-path hardening added two more — one guarding the admin reclaim tool, one the purchaser’s slippage ceiling — and the reclaim compile-out added the launch-build refusal:
| Code | Variant | Message |
|---|---|---|
| 105 | PinLeaseAccountInfo | Failed to retrieve pin-lease account information |
| 106 | AdminCloseEscrowPresent | The admin-close target still holds escrow above its rent floor |
| 107 | PriceExceedsMax | The stamp price exceeds the purchaser’s maximum |
| 108 | AdminCloseDisabled | Admin-close is not compiled into this build (reclaim feature off) |
Code 106 is the reclaim-capable build’s value guard: in all three
programs AdminCloseAccount refuses any target whose balance sits above its
rent floor, program_common::admin_close_minimum_balance — the higher of
the live Rent-sysvar minimum and the SDK-default minimum, so an account
funded at the SDK default before a downward rent step still reads as
rent-only — 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.
That guard cannot make the tool safe against live accounts, though: a
live mailbox, alias, or domain normally holds exactly its rent-exempt
minimum, at or below that floor and indistinguishable on-chain from
leftover state. Code 108 is the
answer to that: a build compiled without the reclaim feature carries no
admin-close handler at all and refuses the instruction outright, so a launch
build simply has no instruction that can destroy a user’s account
(see the reclaim lifecycle).
Code 107 is the purchaser’s side of the 106 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.
The own-address alias guard added one more:
| Code | Variant | Message |
|---|---|---|
| 109 | AddressAliasNotOwner | An address-shaped alias belongs only to the wallet it names |
Code 109 fires wherever an address-shaped alias (32 to 44 lowercased
base58 characters) would end up held by, or staged for sale to, a wallet it
doesn’t name: CreateAlias by a payer that isn’t that wallet,
AcceptTransferAlias by another recipient, BuyAlias by another buyer,
SettleAuction onto another high bidder, and the ListAlias/SellAlias
staging refusal, which is unconditional — the wallet an address-shaped
alias names can never list or auction it. The same predicate waives
CreateAlias’s claim fee when the payer registers their own address; see
Economics — Alias holders.
The sponsored-mailbox guards and the dormant-mailbox reclaim added these:
| Code | Variant | Message |
|---|---|---|
| 110 | SponsorCapExceeded | The domain has reached its cap of unclaimed sponsored mailboxes |
| 111 | SponsorRateExceeded | The domain has reached its sponsored-create rate for this epoch |
| 112 | MailboxNotDormant | The mailbox is not dormant: the owner activated it or it has received mail |
| 113 | DormancyGraceNotElapsed | The mailbox dormancy grace window has not yet elapsed |
| 114 | NotMailboxFunder | Signer is not the mailbox’s recorded funder |
| 115 | SponsorCounterAccountInfo | Failed to retrieve the domain’s sponsor-counter account |
| 116 | ReclaimRequiresSponsoredMailbox | Only a sponsored mailbox can be reclaimed as dormant |
A sponsored CreateMailbox raises codes 110 and 111 when the
domain’s sponsor counter has reached the sponsor_cap or the
sponsor_rate_per_epoch recorded on its MailDomain (a zero setting means
unlimited), and code 115 when the sponsor-counter account is missing
from the slot after the MailDomain account. ReclaimDormantMailbox
checks, in order: 114 when the funder account did not sign or is not
the mailbox’s recorded funder, 116 when the mailbox’s owner funded it
(an owner closes through RequestCloseMailbox and FinalizeCloseMailbox),
112 when the owner activated it or it has received mail, 113 before
MAILBOX_DORMANCY_GRACE_SECS have passed since its created_at, and
115 when the MailDomain / sponsor-counter pair is missing.
The admin-close self-recipient guard raises one more:
| Code | Variant | Message |
|---|---|---|
| 117 | AdminCloseSelfRecipient | The admin-close target cannot be its own rent recipient |
Code 117 is raised by AdminCloseAccount in all three programs when the
target account and the rent-recipient account are the same address. The
check runs after the authority and ownership checks and before the
code 106 value guard, so the target survives unchanged.