Identity and names
How names are minted, priced, transferred, released and versioned, and the rules that make an accidental or malicious loss of a name hard rather than easy.
A bMail address is a name in MailRegistry bound to a wallet
and to an X25519 public key. One wallet holds at most one name, and one name
resolves to exactly one wallet.
Name rules
a-z, digits 0-9, and hyphennameHash keys everythingAlreadyRegistered if the wallet already holds oneMeasuring price on the de-hyphenated length is deliberate. Without it,
a-b would be a four-character name and escape the premium tier that
ab falls into, which is the same two-character brand for practical
purposes.
Pricing
| Visible length | Cost | Reasoning |
|---|---|---|
| 5 and up | Gas only | The ordinary case. The gas-only promise applies to real users. |
| 4 | price4 | Short names are scarce; a fee funds the protocol instead of a squatter. |
| 3 | price3 | As above, priced higher. |
| 1 to 2 | Rejected | InvalidName |
register takes a maxFee ceiling and refunds any
overpayment. Both matter: the owner can change prices, so a registration sent
without a ceiling could be front-run into paying more than intended.
function register(
string calldata name,
bytes32 x25519Pubkey,
uint256 maxFee
) external payable
The name lifecycle
TRANSFER_WINDOW, RELEASE_GRACE and
OWNERSHIP_WINDOW.Transfers are two-step and they expire
A handover needs an action from both wallets. The sender calls
initiateTransfer(to); the recipient calls
acceptTransfer(name, x25519Pubkey) with their own encryption key.
Nothing moves until the second call lands.
Two properties make this safe rather than merely inconvenient:
- It expires. After seven days the pending transfer is dead. A stale approval cannot be redeemed years later by a wallet that has since been compromised.
- It can be cancelled.
cancelTransfer()revokes it immediately.
A single-call transfer means one malicious signature drains your identity the same way a token approval drains a wallet. Requiring the receiving side to act, and letting the approval lapse, removes that class of attack.
Releasing a name
releaseName() gives up a name but does not immediately throw it
to the wolves. For seven days only the previous owner may re-register it. Anyone
else attempting to claim it in that window is rejected with
NameInGrace(prevOwner, freeAt), which reports who holds the
exclusive window and when it ends.
This exists because releasing is easy to do by accident, and a released name is exactly the kind of thing an automated squatter watches for.
Reserved names
The owner can mark names reserved in batches of up to 256. A reserved name
cannot be registered, and critically it cannot be acquired through
acceptTransfer either. An early version checked reservation only in
register, which left the transfer path as a way around it.
Reservation is honest about its limits. If a name is reserved after someone
already holds it, the contract emits
ReservedWhileRegistered(name, holder) rather than seizing it. An
existing holder is never dispossessed.
skeleton() folds visually similar characters so a client can
warn that rn resembles m. It is deliberately not
enforced on chain. An earlier revision did enforce it and would have
permanently blocked ordinary names like mall, fall
and ball while still failing to catch every lookalike.
Keys and versions
Your encryption key is separate from your wallet key and can be rotated without losing your name or your history.
An early revision stored key history inside the account struct, which meant releasing a name wiped it and a later registration would overwrite version 1 with a different key. Any mail sealed to the original key became permanently unreadable. Both mappings now live outside the struct for that reason.
Public key validation
isValidPubkey rejects a published key if it is zero, has the top
bit set, or is one of six known small-order Curve25519 points. Those points force
the shared secret to a fixed value, which would let anyone decrypt every message
sealed to that key. This is checked when publishing a key and, separately, when
opening an incoming envelope's ephemeral key.