Reference
Errors and events
Every custom error and event across both contracts, with what triggers each one and what a client should do about it.
Both contracts use custom errors rather than revert strings. They are cheaper and they give a client something precise to match on.
MailRegistry errors
| Error | Triggered when | What the client should do |
|---|---|---|
NotOwner() | An owner-only method was called by someone else | Hide the control entirely |
NameTaken() | The name already resolves to a wallet | Offer alternatives |
NameIsReserved() | The name is on the reserved list | Explain it is not available for registration |
InvalidName() | Charset, length or hyphen placement is wrong | Validate before submitting |
AlreadyRegistered() | This wallet already holds a name | Offer release or transfer instead |
NotRegistered() | The action needs an existing account | Send them to registration |
InsufficientFee(uint256 required) | Value sent is below the premium fee | Show required |
FeeAboveMax(uint256 required) | The fee exceeds the caller's maxFee | The price moved; re-quote before retrying |
NoPendingTransfer() | Accepting a transfer that does not exist | Refresh state |
TransferExpired() | Past TRANSFER_WINDOW | Ask the sender to initiate again |
BadPubkey() | Zero, top-bit set, or a small-order point | Re-derive; never publish an unscreened key |
InvalidRecipient() | Transfer target is not valid | Check the address |
ZeroAddress() | A zero address was supplied | Validate input |
BatchTooLarge() | Over MAX_RESERVE_BATCH | Chunk the call |
NameInGrace(address prevOwner, uint64 freeAt) | Inside another owner's release grace | Show when it frees up |
WithdrawFailed() | Fee withdrawal transfer failed | Retry to a different address |
RefundFailed() | Overpayment refund failed | Avoid contract wallets that reject value |
MailBox errors
| Error | Triggered when | What the client should do |
|---|---|---|
SenderNotRegistered() | The sender has no account | Register first |
RecipientNotRegistered() | The target has no published key | Explain they must join before they can receive |
Blocked() | The recipient blocked this sender | No price clears this |
WrongStamp(uint256 required) | Value sent does not match the requirement | Re-read requiredStamp and retry |
StampAboveMax(uint256 required) | Requirement exceeds the caller's maxStamp | The price moved; confirm the new one |
PriceOutOfRange(uint256 min, uint256 max) | Price outside minStamp to MAX_STAMP | Clamp in the interface |
BadRegistry() | Registry address is not a valid registry | Deployment misconfiguration |
EnvelopeTooLarge() | Over MAX_ENVELOPE_BYTES | Warn on size before sealing |
BatchTooLarge() | Over MAX_BATCH | Chunk the call |
NoSuchStamp() | No escrow row for that mailId | Refresh |
StampAlreadySettled() | Already refunded, claimed or reclaimed | Refresh; likely a double submission |
NotStampRecipient() | Caller is not the recipient | Hide the control |
NotStampSender() | Caller is not the sender | Hide the control |
StampNotExpired() | Settling before 30 days | Show the unlock time |
StampExpired() | Refunding after 30 days | By design: time never refunds a spammer |
RecipientStillReachable() | Reclaim attempted while the recipient is reachable | Reclaim is not a general timeout |
RecipientBlockedYou() | Claim attempted while blocking the sender | Unblock or refund instead |
NothingToWithdraw() | Credit balance is zero | Disable the control |
WithdrawFailed() | Credit withdrawal failed | Try withdrawCreditsTo |
InsufficientGas() | Not enough gas left to attempt a payout safely | Raise the gas limit |
MailRegistry events
| Event | Emitted when |
|---|---|
Registered(wallet, name, pubkey, keyVersion, epoch) | A name is minted |
KeyRotated(wallet, oldPubkey, newPubkey, keyVersion) | An encryption key is replaced |
TransferInitiated(name, from, to, expiresAt) | A handover starts |
TransferCancelled(name, from) | A pending handover is revoked |
TransferAccepted(name, from, to, pubkey, epoch) | A handover completes |
NameReleased(wallet, name, freeAt, epoch) | A name is given up; grace begins |
NameReserved(name, flag) | Reservation set or cleared |
ReservedWhileRegistered(name, holder) | Reservation hit an existing holder, who keeps it |
ReserveSkipped(name, reason) | A batch entry was skipped |
PricesSet(price3, price4) | Premium pricing changed |
OwnershipTransferStarted / Cancelled / Transferred | Contract ownership handover |
Withdrawn(to, amount) | Fees withdrawn |
MailBox events
| Event | Emitted when |
|---|---|
Mail(from, to, mailId, convoId, inReplyTo, stampValue, envelope) | A message is sent. The whole inbox is built from this one event. |
StampPriceSet(wallet, price) | A recipient changes their price |
StampRefunded(mailId, recipient, sender, amount) | Escrow returned |
StampClaimed(mailId, recipient, sender, amount) | Escrow taken |
StampReclaimed(mailId, recipient, sender, amount) | Sender recovered escrow |
BatchSettled(caller, settledCount, totalAmount) | A batch call finished, reporting what actually moved |
PaymentCredited(to, amount) | A push payment failed and was credited instead |
CreditsWithdrawn(to, amount) | Credits pulled |
SenderAllowed(recipient, sender, until) | Allowlist entry set |
SenderBlocked(recipient, sender, blocked) | Block set or cleared |
Indexing notes
Building an inbox
Filter Mail on the to topic for received mail and
the from topic for sent mail. convoId is the keccak
of the sorted address pair, derivable from the two topics, so it is not
indexed separately. stampValue is the value escrowed
at write time, not a live balance: it may already have been settled
later in the same transaction. Read stamps(mailId) for current
state.