[!WARNING] This open source code is provided for research, experimentation, and developer education only. This code has not been audited, is actively experimental, and may contain bugs, vulnerabilities, or incomplete features. Use at your own risk.
Dotns
Smart contracts for registering .dot names on Polkadot.
DotNS is a naming system for Polkadot. An account can register a .dot name, receive an ERC721 token that represents ownership of that name, attach records to it (addresses, text, content hashes, chat keys), and create subnames beneath it. Two independent issuance paths coexist on the same underlying registrar: a public commit-reveal path for anyone who wants a name, and a Proof-of-Personhood gateway path that issues lite-person and full-person usernames on behalf of verified users. Every piece of state the protocol surfaces is readable through public view functions on the chain itself, so a client needs only a node and a small set of well-known contract addresses to answer any question about the system.
Diagrams
System diagram

Deployment and operations
Deployment notes are in DEPLOYMENTS.md. Network addresses are recorded in deployments/<network>/<chain-id>.json and published with each release.
Cutting a release
A release publishes the contract ABIs and the deployed addresses as GitHub release assets, described in RELEASE_ARTIFACTS.md. It does not deploy anything; deploying contracts to a network is a separate process, described in DEPLOYMENTS.md.
Run Publish Release Package from the Actions tab, pick the branch to release from, and enter the version (v0.5.5). The workflow does the rest: it builds, tests, extracts the ABIs listed in .github/abi-contracts.txt, generates the address and manifest files, creates the release as a draft with every asset attached, verifies the set against what the build produced, and only then publishes. Pushing a matching tag runs the same workflow, so git tag v0.5.5 && git push origin v0.5.5 remains equivalent.
Pre-releases use Publish Beta Package with a suffixed version, v0.5.5-rc1. The version is the release identity; the version field in package.json is unrelated and nothing reads it.
Do not create releases through the GitHub UI's release form, or with gh release create. Both publish immediately, and because this repository has immutable releases enabled, a published release can no longer accept assets: only its title and notes stay editable. A release made that way carries no ABIs at all. The workflow rejects an already-published version before building, so the mistake fails in seconds rather than silently shipping an empty release.
If a run fails partway, re-run it from the Actions tab; the draft is updated rather than duplicated. One case needs a manual step: the upload replaces an asset of the same name but never removes others, so if the contract list changed since the failed run, the draft still carries the assets it no longer expects and the verification step will keep refusing to publish. Delete the draft and re-run. If the version has already been published, use a different one, since its assets cannot be changed.
Economics
Every name admitted to public sale costs the same refundable deposit: 10 DOT at launch. The amount is not fixed in the registration path. It comes from a cost model resolved through the protocol registry under the costModel key, and governance can replace that model without touching the registrar or its storage. The launch model, DotnsFlatPricing, returns one deposit for every length. A length-sensitive scarcity curve, DotnsScarcityPricing, ships alongside it as a candidate for a later version but is not the registered default.
Base length and the digit rule
Pricing and eligibility read a name's base length: the character count once a trailing number is set aside. A name carries no trailing digits or exactly two; one digit, or three or more, is rejected before pricing. The trailing digits come off before the length is measured, so andrew and andrew01 both classify as a six-character name. Base length decides which band a name falls in and who may register it; under the flat model it does not change the amount.
What a name costs
Only names of nine characters or more are on public sale by default. The short-name switch, off at launch, keeps base lengths below nine off the public paid path. Every name on sale costs the flat deposit:
| Base length | Price | On public sale by default |
|---|---|---|
| 6 to 8 | 10 DOT | No, held behind the short-name switch |
| 9 or more | 10 DOT | Yes |
| 5 or fewer | not sold | issued at zero base cost through the reserved path |
Who can register a name
Three bands share the one deposit.
| Base length | Who may register on the public paid path | Price |
|---|---|---|
| 9 or more | anyone, as NoStatus | 10 DOT |
| 6 to 8 | a verified person, and only while the short-name switch is on | 10 DOT |
| 5 or fewer | nobody on the public path | not sold; issued at zero base cost through the reserved path |
Personhood unlocks only the six-to-eight band. A no-digit name there needs full-person verification; a two-digit name needs lite-person verification. It gates who may buy, not the price: the deposit is the same one everyone pays.
Names shorter than nine characters are closed on the public paid path by default. A paid registration below nine reverts until governance opens the short-name market with a single switch. The switch gates the public paid path alone: the personhood gateway issues names of any length without it, and the reserved path does not consult it. It defaults off, so at launch only names of nine characters or more are for sale.
Names of five characters or fewer are never sold on the public path, which rejects a reserved-tier label outright. Such a name enters circulation only through the reserved path, which the contract owner or a whitelisted operator calls to mint an available label at zero base cost with no deposit and no personhood check. There is no treasury: no value moves when a reserved name is issued.
Worked examples
The path decides whether the amount is a refundable deposit, a non-refundable fee, or nothing at all. Every amount below is the flat launch deposit of 10 DOT.
| Name | Base length | Path | Amount | Held as |
|---|---|---|---|---|
gavinwood | 9 | public, own key | 10 DOT | refundable deposit |
gavinwood | 9 | public, someone else pays | 10 DOT | protocol fee |
andrewsays | 10 | public, own key | 10 DOT | refundable deposit |
andrew | 6 | public, own key (switch on, full person) | 10 DOT | refundable deposit |
alicebob42 | 8 | public, own key (switch on, lite person) | 10 DOT | refundable deposit |
| any six-to-eight name | 6 to 8 | personhood gateway grant | none | no deposit, no fee |
andrew, moved to a wallet that cannot clear its band | 6 | transfer | 10 DOT | protocol fee |
Deposits and protocol fees
Registering a name under your own key locks a refundable deposit equal to the name's price. The deposit is bound to the name rather than to you, so it travels with the token on every transfer and unlocks only when the current holder releases the name back to escrow.
A name someone else pays for, and a transfer, pay a non-refundable fee instead. When a third party pays for another wallet's registration, the charge is the same owner-side price, with no separate friction added; it routes to a single protocol fee pot, and the owner's escrow slot is seeded with a zero amount so the release lifecycle stays reachable. A gateway grant carries neither a deposit nor a fee. The protocol fee pot only ever grows: it backs no refunds, and nothing burns, sweeps, or withdraws from it. Refunds draw solely on the separate per-asset reserve that deposits fund. A holder's own deposit is their money held in trust and is never moved into fees.
Transfers re-price at the name's own length
A transfer charges the name's own price, but only in two cases: the recipient cannot clear the name's band, or the move is a personhood downgrade, where the recipient's tier is lower than the sender's. Passing a six-character name to a wallet that could never have registered it costs the name's own price, so there is no cheap way to hand a band-gated name to a party who could not have earned it. A move between two wallets that both clear the band, and a move to the same address, cost nothing. The fee, when one is owed, settles into the protocol fee pot. The deposit, when present, rides with the name: the escrow position rebinds to the new holder rather than refunding, and only releasing the name back to escrow unlocks the locked deposit.
Versioned pricing
The cost model is chosen by governance and swapped, not upgraded. Registering a new model adds it under a fresh version and points the current version at it; earlier versions stay priceable, so a registration already committed against an earlier version settles at the amount it committed to. A commitment binds the version current when it is made, and the reveal reverts if it is presented at a different version, so a model change between commit and reveal cannot move the amount. Governance can also point the current version back at an earlier registered model.
Release lifecycle
Releasing a name starts two independent clocks, and the distinction between them is what makes a released name both recoverable and recyclable.
| Clock | Length | What it gates |
|---|---|---|
withdrawAvailableAt | release + cooldown (15 minutes at launch, at most 1 hour) | When the holder may credit the deposit to themselves through withdraw |
redeemableUntil | release + redeemWindow (1 day at launch, governance may set 1 to 30 days) | When the holder's exclusive claim ends and reclaim opens to anyone |
Both are stamped at release time, so a governance change never moves the clocks on a name already released. Inside the redeem window the name belongs to its previous holder, and DotnsRegistrar.available reports false so no client advertises it as free. The holder has two mutually exclusive options:
redeemreturns the token and moves no value. The position keeps its recipient, asset and amount, so the deposit stays locked and the name lands back in its exact pre-release state, releasable again later on a fresh pair of clocks. This is the undo for an accidental release.withdrawcredits the deposit and forfeits the right to redeem. A holder paid for the name cannot also take it back, otherwise they would hold a name no deposit backs.
Once redeemableUntil passes, reclaim is permissionless through the ordinary commit-reveal path, whether or not the previous holder ever withdrew. If the position still holds value, reclaim settles it: the amount is credited to the previous holder's pull-payment balance and stays claimable with no deadline. The value follows the departing holder; the name does not wait for them. Cross-paid registrations seed zero-amount positions, so those names have nothing to withdraw and nothing to settle.
Cost-model versioning
D and F are fixed for the life of a pricing model. Changing either means deploying a fresh model with the new values and registering it, at which point it becomes the current version; there is no live setter that edits the numbers in place. Governance can also point the current version back at an earlier registered model to roll a change back. An in-flight registration prices at the version it committed to, so a model change between commit and reveal leaves its cost unchanged.
What governance controls
Governance sets D and F to whatever values it chooses. The contracts hold them coherent and nothing more: D above zero, F above zero and no greater than D, and D within a ceiling that keeps the six-character multiplication from overflowing. There is no cap on how high or low D goes and no limit on how fast it moves, so any rate limit or advance notice comes from the governance process rather than from these contracts. Governance also opens or closes the short-name market with the switch, tunes the release cooldown within its one-hour bound, sets the redeem window between 1 and 30 days, and sets the gateway's reservation duration. None of these controls lets governance seize, reassign, or destroy a name anyone already holds.
Refund ledgers
The escrow keeps two separate pull-payment ledgers. One has no cooldown and serves as the fallback when a registration overpayment cannot be pushed back to the sender inline. The other gives every credit its own cooldown clock and holds transfer-fee overpayments. A deposit unlocked by releasing a funded name lands on the no-cooldown ledger; its delay comes instead from the position's own withdrawAvailableAt stamp, so once withdraw lands the credit is immediately pullable. Clients enumerate pending refunds through the escrow's public views, which page under a fixed cap so discovery stays bounded.
Clients that need the exact moment a released name becomes registrable should read redeemableUntil from getReleasePosition rather than polling available.
Contracts
Two controllers sit on top of a single registrar and a single protocol registry. The registrar holds the ERC721 token per name; the registry holds the forward node => (owner, resolver) mapping and subname hierarchy; the resolvers hold per-name records; the protocol registry is the indirection layer through which every contract resolves its siblings at runtime. Controllers are the entry points: they mint names and drive the side effects. Neither controller imports the other. The layers underneath arbitrate collision handling: ERC721 uniqueness on the registrar, and a single reservation table on PopRules that both flows read through.
DotnsRegistrarController
Commit-reveal controller for the public registration path. A caller first submits a commitment hash, waits out the minimum commitment age, then reveals the registration parameters alongside the payment. The controller validates the commitment, routes price and eligibility through PopRules, and orchestrates every side effect of a successful registration: the mint on the registrar, the forward wire-up on the registry, the reverse record on the reverse resolver, the immutable Store write, and any refund owed on overpayment. Acceptable input is a single DNS label of at least the minimum-length policy; shorter labels revert with LabelTooShort. Labels classified as governance-reserved revert with GovernanceReserved; a base stem held by another user reverts with NameReserved. On the cross-payer path the owner's recorded PoP tier must meet the label's required tier, so verified-payer-for-unverified-owner sponsorship is rejected with OwnerStatusInsufficient and the direct-path personhood guarantee carries over to sponsored registrations.
DotnsPopController
Dedicated controller for the Proof-of-Personhood gateway flow. Lives behind its own UUPS proxy with its own storage and is registered on the registrar via addController alongside the commit-reveal controller. Its gated entry points are callable only under a substrate Root origin, which the controller verifies itself by reading originIsRoot from the revive System precompile.
Today the Pop gateway does not write a standalone user-status mapping. It materialises the PoP flow through gateway-issued labels, PoP resolver records, and reservation queue state; user tier checks for public pricing still come from the personhood precompile/context read.
The first, reserveBaseName, mints a lite-person username to a user. The gateway-facing input is a stem.suffix shape: a single DNS label followed by exactly one dot and a digits-only suffix of exactly two digits (for example michal.03). The controller normalises that input by stripping the dot before classification, pricing, and minting, so the on-chain label is always flat (michal.03 becomes michal03). Inputs with more than one dot, no dot, a non-digit suffix, or a suffix length other than two digits are rejected at the boundary. The stem may be any DNS-valid label of at least six characters, not only the 6 to 8 of the public PopLite tier; only governance-reserved stems (five characters or fewer) are rejected. A lite username whose stem is nine characters or longer classifies as NoStatus for public pricing and transfers, so its lite status is an issuance property rather than an economic tier. The call also persists the user's chat key on the PoP resolver and optionally enqueues a reservation for a full-person base name the user intends to claim later.
The second, registerBaseName, mints a full-person username. Whether the call is a claim against a prior lite reservation or a fresh standalone registration is derived from on-chain reservation state; the caller does not choose. The link argument selects the chat-key source: inherit from a prior lite label, or accept a fresh one in the payload. When inheriting, the call also writes the liteLink (full => lite) and fullClaim (lite => full) records on the PoP resolver in the same transaction so downstream consumers can resolve either direction without scanning events.
Each base label carries a head/tail-indexed reservation queue with a capacity of MAX_RESERVATION_QUEUE and a governance-configurable reservationDuration. The queue head is mirrored into PopRules on every head transition (enqueue-from-empty, expiry-driven promotion, non-expiry head removal, claim-wipes-queue), so the public commit-reveal flow sees the same cross-flow lock through its existing PopRules price check. The gateway path is symmetric: registerBaseName consults the live PopRules slot before mint and rejects with NotHolder when another user holds the stem, so PopRules is the single cross-flow authority in both directions. registerBaseName additionally rejects lite-classified labels (those belong on reserveBaseName) and governance-reserved labels with InvalidBaseLabel. Expiry advancement is permissionless: anyone can call expireReservation to garbage-collect a stale head, which is what the pallet does on its own cadence.
Early testnet quirk: LabelStore deployment
Pop-gateway issuances mint the name and persist its label, but LabelStore deployment is deferred for users who have not yet interacted with the protocol from their own address. The current pallet-revive runtime does not let substrate Root deploy contracts on behalf of an account it does not control, so the per-user LabelStore cannot be created at the moment the gateway writes. The controller stamps a pending-claim entry instead, and settlement writes the label into the owner's store, deploying the store on the first write. Settlement is permissionless via settlePendingClaims: the owner settles their own store, or after the claim window anyone settles a given owner's entry and pays the cost. Settlement always writes the label rather than dropping the entry, so a pending name is never stranded. When the runtime supports root-origin contract deployment, the deferred path collapses to a no-op and the issuance flow becomes one transaction end-to-end. This is a runtime limitation, not a protocol design choice.
Operational consequence for transfers: the registrar derives the transfer-floor price by reading the label from the sender's LabelStore. A gateway-issued name whose pending claim is not yet settled has no readable label on the sender side, so _quoteTransferFee returns zero regardless of the recipient's tier. Until the name is settled into a LabelStore, a downward transfer (for example PopFull to NoStatus) does not charge the cross-tier friction it would otherwise owe. Clients that consume gateway-issued names should treat settlement as a prerequisite for accurate transfer-time pricing, not just for label discovery.
DotnsRegistrar
ERC721-backed registrar that mints ownership of label IDs (labelhashes). Minting is restricted to every address in the controllers mapping; the mapping is owner-gated through addController and removeController. Every other contract in the system that needs to check "is this address authorised to drive name state?" consults this mapping rather than keeping a parallel list, which is what lets multiple controllers coexist on the same registrar without per-contract configuration changes.
DotnsRegistry
Forward registry mapping node to (owner, resolver) and supporting subnode creation. When a base name is minted on the registrar, the matching controller wires the node to the new owner through this registry. Privileged node wiring defers to the same controllers mapping on the registrar, so both controllers can write without the registry tracking controllers of its own.
Subnames are created by the base-name owner. A subname carries its own (owner, resolver) and can in turn carry subnames, so the registry is the place the name hierarchy actually lives.
The registry exposes isAuthorised(node, account) as the canonical check for whether an address may manage a node: the stored owner for a subname, or the ERC-721 holder, a single-token approvee, or an operator-for-all on the registrar for a tokenised name. Sibling contracts consult this view so a single registrar-level approval delegates management across the protocol rather than each contract maintaining its own approval list.
PopRules
PoP-aware name classification and pricing. Classification reads the label's stem length (the character count after stripping the trailing digit suffix) and the trailing digit count itself, then maps to one of four tiers: NoStatus (stem of 9+ characters, open to anyone at the cost-model price, with zero or exactly two trailing digits permitted), PopLite (stem of 6-8 characters with exactly two trailing digits, gateway-issued to lite-verified users), PopFull (stem of 6-8 characters with no trailing digits, requires full-person verification), and Reserved (stem of 5 characters or fewer, governed by the protocol). Labels carrying one trailing digit or more than two trailing digits are rejected at the classifier. The classification determines the price and the eligibility gate the commit-reveal controller enforces.
Classification examples and failure modes
The classifier bands on the stem, not the total label length. The stem is the label after removing any trailing digits. The trailing digit count must be zero or exactly two; a one-digit suffix and suffixes longer than two digits are invalid before tier eligibility is considered. The price column gives the flat deposit for a registrable example; a rejected or reserved name has no price.
| Label | Stem | Trailing digits | Classification | Eligible public path | Price | Notes |
|---|---|---|---|---|---|---|
| alice12 | alice | 2 | Reserved | Whitelist only | Not sold; issued at 0 | The stem is five characters, so the two-digit suffix does not make it PopLite. |
| andrew01 | andrew | 2 | PopLite | Pop gateway only | 10 DOT | Valid lite shape: six-character stem plus system-supplied two-digit suffix. Priced on the public paid path only while the short-name switch is on; the gateway grant is free. |
| alicebob42 | alicebob | 2 | PopLite | Pop gateway only | 10 DOT | Eight-character stem plus two digits; total length is ten. Gateway grant is free. |
| andrew | andrew | 0 | PopFull | PopFull user | 10 DOT | Canonical full-person base name; priced only while the short-name switch is on. |
| andrew1 | andrew | 1 | Rejected | None | n/a | One trailing digit has no protocol meaning. |
| andrewsays | andrewsays | 0 | NoStatus | Anyone | 10 DOT | Base length 10; the amount is the flat refundable deposit. |
| andrewsays01 | andrewsays | 2 | NoStatus | Anyone | 10 DOT | Long stem remains NoStatus even with a two-digit suffix, and prices at the same flat deposit. |
| andrew123 | andrew | 3 | Rejected | None | n/a | More than two trailing digits is invalid. |
| andrew.01 | n/a | n/a | Rejected by public label validator | None | n/a | Dots are not valid in the public flat label. The Pop gateway accepts stem.suffix and normalises it to stemsuffix. |
| Andrew01 | n/a | n/a | Rejected by canonical label validator | None | n/a | Labels must be lowercase ASCII DNS labels. |
Tier assignment is read on every pricing call, not stored: PopRules queries the alias-accounts personhood precompile at DotnsConstants.PERSONHOOD with the dotns context (bytes32("dotns")), and translates the returned status byte into a PopStatus (0=NoStatus, 1=PopLite, 2=PopFull). Unknown tier bytes collapse to NoStatus, so a future precompile addition fails closed rather than silently being treated as a higher tier. There is no on-chain self-attestation; users obtain personhood off-chain through the People-chain ring proof and the alias-accounts pallet propagates the result via XCM.
Classification is not the same thing as effective holder context. A long label such as andrewsays is always a NoStatus-tier label by shape, but it may be held by a PopFull, PopLite, or NoStatus account. Consumers that need to know what rules apply to that live name should combine three reads: classify the label, query the registrar owner, then query the owner's dotns-context PoP status through the precompile or gateway-written state. The escrow position is the economic qualifier: if the token has an active release position with a non-zero amount, it came through the refundable NoStatus deposit path; if no such deposit exists, a verified holder can own the same long label without it being deposit-backed. In other words, andrewsays does not become a PopFull-tier label when a PopFull user owns it, but the owner can still be PopFull for transfer pricing, reverse resolution, and UI display.
Whitelisting is the exception path for users or organisations that need to register without satisfying the live PoP tier check. DotNS still does not accept self-attestation: the contracts only consume PoP status from the personhood precompile, and a user cannot set or prove their own status inside DotNS. Instead, the public registrar controller has an owner-managed whitelist for registerReserved, which bypasses the PoP pricing gate for approved addresses while still using the normal commit-reveal and availability checks.
To request a whitelist entry, open a Whitelist Request issue in this repository. The issue is labelled whitelist-request by the template and must include the address, address type, target network (Paseo V2 or Paseo Review), and a clear description of why the PoP bypass is needed. A maintainer can approve the request by applying whitelist-approved, after which the workflow checks account mapping on the selected network and executes the on-chain whitelist transaction. Whitelisting does not register a name, reserve a label, or bypass ownership rules; it only allows the approved address to use the reserved registration path without a PoP status.
For the operator-side mechanics, granting the whitelist-operator role, granting it to many operators, and whitelisting one or many addresses through the dotNS SDK CLI, the hosted app, or cast, see the Whitelisting section in DEPLOYMENTS.md.
PopRules also holds the cross-flow reservation table for base names. Two write paths share one mapping keyed by the bare stem. The first is used by the commit-reveal controller during a lite registration: it classifies the incoming label, strips the trailing digits, and writes the bare stem. The second is used by the PoP controller on every reservation-queue head transition: it takes a bare stem directly and rejects the update when the slot is held by a different user, so the caller's local queue bookkeeping never silently diverges from the PopRules state.
Two read paths, priceWithCheck and priceWithoutCheck, are what the public flow consults. Both strip trailing digits before looking up the reservation, so any live entry on a bare stem blocks registrations of any variant under that stem for the reservation window (12 weeks by default).
DotnsReverseResolver
Reverse records mapping an address to its primary name, with two write paths. The first, setReverseName, is the controller-only seeder: when a direct reserved registration lands and the registrant has no existing primary, the commit-reveal controller calls this entry point so a subsequent reserved registration does not silently overwrite the primary. Writes through this path are restricted to the addresses registered under CONTROLLER and REGISTRAR on the protocol registry. The second, claimReverseRecord, is the self-service path open to any current name owner: the caller hands in a label and the resolver checks that registrar.ownerOf(namehash(label)) equals the caller before overwriting their reverse entry. Past ownership is never sufficient; the registrar is the single source of truth for the gate.
Reads are open but fail-closed: nameOf(address) re-validates current ownership against the registrar before returning the stored name, so an address that has transferred their primary away resolves to the empty string until they claim a new name they currently hold. The protocol still best-effort clears reverse entries on transfer (cheaper reads, no behavioural change), but the security guarantee is the fail-closed read, not the eager clear.
DotnsContentResolver
Stores contenthash and text records per node. This is where external content links (for example IPFS hashes) and arbitrary key-value text records (for example social handles, verification metadata) live. Writes accept the node owner, any address the registry recognises as authorised for the node through isAuthorised (the ERC-721 holder, a single-token approvee, or an operator-for-all on the registrar), or an operator approved directly on this resolver; reads are open. The registry-recognised path lets a registrar-level name admin manage records without a separate grant, while the resolver-local operator is a narrower record-only delegation that confers no power over ownership or transfer. Authority is evaluated against the current owner on every write, so transferring the name reassigns write access automatically.
Choosing a delegation mechanism:
| Goal | Use | Why |
|---|---|---|
| Delegate full control of one name, including the right to transfer it, automatically revoked on sale | registrar approve(operator, tokenId) | Single-token approval. ERC-721 clears it on every transfer, so it cannot follow the name to a buyer. |
| Delegate full control of all your names, current and future, including transferring them | registrar setApprovalForAll(operator, true) | The name-admin role. Persists until revoked and spans every name you hold. It grants transfer power, so grant it only to fully trusted managers; this is the approval marketplaces and escrows require. |
| Delegate record edits only, with no power over ownership or transfer | content resolver setApprovalForAll(operator, true) | The narrowest grant. Resolver-local and record-scoped: the operator can set text and contenthash but cannot transfer the name or change its owner. |
Revoke any grant with the inverse call (approve(address(0), tokenId) or setApprovalForAll(operator, false)). Because every write re-reads the current owner, a transfer drops all delegates the prior owner had set.
DotnsResolver
Stores forward-resolution address records per node. This is the conventional "name to address" lookup: a client has a .dot name and wants to know the Ethereum address behind it. Writes require node ownership; reads are open.
DotnsPopResolver
Per-node resolver for records produced by the Proof-of-Personhood flow. Three record kinds. The chat key is ECDH public-key bytes keyed by node; it is written by the PoP controller during a lite reservation and during any claim path that inherits from a prior lite entry, and is what gives verified users an on-chain discovery channel for end-to-end encrypted messaging. The lite link answers "which lite username did this full name claim from?" and is keyed by the full-person node. The full claim is the reverse direction: it answers "which full name did this lite user claim?" and is keyed by the lite labelhash. The forward and reverse links are written by the same call, so they stay in lockstep; downstream consumers that look up by lite username (Nova's pallet, for one) resolve the full name without scanning events.
Writer authorisation is dynamic: the PoP controller address is fetched from the protocol registry on every write. Rotating the PoP controller is a single set call on the protocol registry with no resolver upgrade required.
DotnsProtocolRegistry
On-chain lookup table mapping well-known bytes32 keys (declared in DotnsConstants) to contract addresses. Every DotNS contract resolves its siblings through this registry at runtime.
Without it, each contract would store direct addresses to every contract it calls. An upgrade that changes one address would require a separate owner transaction for every contract that references it. The protocol registry reduces this to one: update the key in the registry, and every caller picks up the new address on its next call. The indirection also means a governance-driven rotation of, say, the PoP controller does not break any consumer that has already been deployed.
The registered keys include REGISTRAR, CONTROLLER, REGISTRY, REVERSE_RESOLVER, RESOLVER, CONTENT_RESOLVER, POP_RULES, STORE_FACTORY, POP_CONTROLLER, POP_RESOLVER, NAME_ESCROW, and MULTICALL3.
Multicall3
Generic arbitrary-target batching helper using the standard Multicall3 interface. It is protocol infrastructure rather than a dotNS-specific authorisation surface: anyone can call it, and each target contract still enforces its own permissions.
For read batching, Multicall3 lets clients collect several results from the same block through one call. For write batching, callers must remember that target contracts observe Multicall3 as the caller, not the original externally owned account. That means public owner-gated dotNS writes should not be routed through Multicall3 unless the target flow explicitly supports that caller model.
StoreFactory, LabelStore, and UserStore
Stores are the per-user storage layer. They exist because two query paths the rest of the system needs are not answerable from anywhere else: "what names has this address ever held?" cannot be served by resolvers (keyed per-node) or the registry (live ownership only, no history), and "what user-controlled records does this address publish?" has nowhere on a resolver to live since the data is not bound to any one name. Each address gets at most one of each store, forever, and the factory is the single source of truth for which store belongs to which user.
LabelStore is the protocol-managed half. The registrar and the controller set write a label entry once and the slot is permanently locked. The invariant is labels only: every per-name record category (reverse, content, forward address, chat key, lite link) goes to a dedicated resolver, and the Store stays the durable per-owner registration ledger. Because entries are append-only, transferring a name writes a fresh entry on the recipient and leaves the sender's locked entry in place, so LabelStore doubles as the address's lifetime-of-ownership ledger while the registry continues to answer for live ownership.
UserStore is the user-claimed half. The bound owner is the only writer and prior values are snapshotted into a per-key history. It exists so that user-controlled records that do not belong to a name have a home that bills the user's own contract rather than polluting a shared resolver. The labels-only invariant is preserved by the split: nothing user-written ever lands on the protocol-managed side.
Deployments
Addresses are recorded per network in deployments/<network>/<chain-id>.json, and published with each release as deployments.json. See DEPLOYMENTS.md for how a deployment is run and RELEASE_ARTIFACTS.md for what a release contains.
Build and test
Builds and tests are run with Foundry. Fork tests use the local Paseo Asset Hub adapter described in DEPLOYMENTS.md; ordinary unit, fuzz, and invariant tests run against Foundry's in-process EVM.
Security
Before deploying it for real use cases, you are responsible for:
- Reviewing the code yourself, we publish a reference, not a hardened production build
- Checking that the dependencies are up to date and free of known vulnerabilities
- Securing your own fork or deployment environment (keys, secrets, network configuration)
- Tracking the latest tagged release/commits for security fixes; older releases are not backported (exceptions might apply)
For Parity's security disclosure process, and Bug Bounty program, feel free to visit: https://parity.io/bug-bounty
Known limitations
The protocol carries a handful of constraints worth knowing before deploying or building against it. Most stem from the current pallet-revive runtime rather than from protocol design, and collapse to a no-op once the runtime gains the corresponding capability. KNOWN_ISSUES.md is the consolidated reference; each issue is also described in full where the relevant contract is documented below.
- Deferred LabelStore deployment (runtime). See DotnsPopController.
- Transfer fee is zero until the store is settled (runtime). See DotnsPopController.
- No standalone user-status mapping (current implementation). See DotnsPopController.
License
Licensed under the MIT License. See LICENSE. External interface definitions under contracts/external/ retain their upstream licences (the SPDX header in each file is authoritative). Security policy and disclosure: see SECURITY.md.
Contents
DotnsRoleManager
Inherits: AccessControlUpgradeable, OwnableUpgradeable, IDotnsRoleManager
Title: Dotns Role Manager
Shared owner-administered role layer for DotNS contracts with operational roles.
Consuming contracts define their supported role set in @custom:function _isSupportedRole. The owner remains the only account that can grant or revoke roles; role holders receive only the operational permissions each consuming contract explicitly gates with
Notes:
-
function: _checkRoleOrOwner.
-
security-contact: admin@parity.io
Functions
_dotnsRoleManagerInit
Initialises the OpenZeppelin access-control state for consuming contracts.
Must be called during the consuming contract initialiser.
function _dotnsRoleManagerInit() internal onlyInitializing;
setRole
Grants or revokes an operational role.
Only the owner can manage roles (otherwise @custom:reverts OwnableUnauthorizedAccount);
role must be one of the roles recognised by the consuming contract (otherwise
Note:
reverts: UnsupportedRole); account must not be the zero address (otherwise
function setRole(bytes32 role, address account, bool enabled) external override onlyOwner;
Parameters
| Name | Type | Description |
|---|---|---|
role | bytes32 | Role identifier declared in DotnsConstants. |
account | address | Account whose role membership is updated. |
enabled | bool | Whether the role should be granted or revoked. |
grantRole
Grants role to account.
If account had not been already granted role, emits a {RoleGranted}
event.
Requirements:
- the caller must have
role's admin role.
function grantRole(
bytes32 role,
address account
)
public
override(AccessControlUpgradeable, IAccessControl)
onlyOwner;
revokeRole
Revokes role from account.
If account had been granted role, emits a {RoleRevoked} event.
Requirements:
- the caller must have
role's admin role.
function revokeRole(
bytes32 role,
address account
)
public
override(AccessControlUpgradeable, IAccessControl)
onlyOwner;
supportsInterface
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(AccessControlUpgradeable)
returns (bool supported);
_checkRoleOrOwner
Reverts unless the caller is the owner or holds role.
Consuming contracts use this for operational paths where the owner keeps super-user access and role holders receive a narrower permission; any other caller is rejected with @custom:reverts NotRoleOrOwner.
function _checkRoleOrOwner(bytes32 role) internal view;
_setRole
Grants or revokes a supported role for account.
role must be recognised by the consuming contract (otherwise
Notes:
-
reverts: UnsupportedRole) and
accountmust be non-zero (otherwise -
reverts: InvalidRoleAccount). Delegates to OpenZeppelin's
_grantRoleor_revokeRole, which emit @custom:emits IAccessControl.RoleGranted on grant and -
emits: IAccessControl.RoleRevoked on revoke.
function _setRole(bytes32 role, address account, bool enabled) internal;
_isSupportedRole
Returns whether role is recognised by the consuming contract.
Implemented by each consuming contract so unsupported role identifiers fail closed.
function _isSupportedRole(bytes32 role) internal view virtual returns (bool supported);
IDotnsRoleManager
Inherits: IAccessControl
Title: DotNS Role Manager
Shared owner-administered role API for DotNS contracts with operational roles.
Role identifiers are declared in DotnsConstants. Ownership remains the source of
super-user authority: the owner grants and revokes supported roles, while role holders
receive only the operational permissions each consuming contract recognises.
Note: security-contact: admin@parity.io
Functions
setRole
Grants or revokes an operational role.
Only the owner can manage roles (otherwise @custom:reverts OwnableUnauthorizedAccount);
role must be one of the roles recognised by the consuming contract (otherwise
Notes:
-
reverts: UnsupportedRole);
accountmust not be the zero address (otherwise -
reverts: InvalidRoleAccount). Emits @custom:emits IAccessControl.RoleGranted on grant and @custom:emits IAccessControl.RoleRevoked on revoke.
function setRole(bytes32 role, address account, bool enabled) external;
Parameters
| Name | Type | Description |
|---|---|---|
role | bytes32 | Role identifier declared in DotnsConstants. |
account | address | Account whose role membership is updated. |
enabled | bool | Whether the role should be granted or revoked. |
Errors
NotRoleOrOwner
Thrown when a caller is neither the contract owner nor a holder of role.
error NotRoleOrOwner(address caller, bytes32 role);
UnsupportedRole
Thrown when role management is attempted for a role the contract does not use.
error UnsupportedRole(bytes32 role);
InvalidRoleAccount
Thrown when role management is attempted for the zero address.
error InvalidRoleAccount(address account);
Contents
Create3Factory
Title: Dotns CREATE3 Factory
Permissionless wrapper around Solady's audited CREATE3 library.
Anyone may call @custom:function deploy. CREATE3 addresses are a pure function of
(factory_address, salt) — the caller never enters the derivation — so caller-gating
would not strengthen address determinism. Salt-squatting griefing is in-scope for the
caller, not the factory: salts in the DotNS namespace (CREATE3_SALT_NAMESPACE in
BaseDeployer) get bumped end-to-end if a slot is ever found occupied. Keeping the
factory permissionless removes the cross-chain ownership-coordination burden and lets
CI keys, ops keys, and recovery flows all coexist without ownership transfers.
Note: security-contact: admin@parity.io
Functions
receive
Accepts native transfers so callers can pre-fund the factory if they want
constructors that take msg.value.
The dotNS pipeline never forwards native value; this exists purely so the factory is compatible with payable-constructor callers from other consumers.
receive() external payable;
deploy
Deploys initCode at the deterministic address derived from salt.
Permissionless: any caller may invoke. The deployed address is
CREATE3.predictDeterministicAddress(salt) and is independent of msg.sender. The
call reverts if initCode is empty (@custom:reverts EmptyInitCode) or if the
predicted slot is already occupied (Solady raises its own revert on collision). Emits
Note: emits: Deployed on success. Native value attached to the call is forwarded to the new contract's constructor.
function deploy(
bytes32 salt,
bytes calldata initCode
)
external
payable
returns (address deployed);
Parameters
| Name | Type | Description |
|---|---|---|
salt | bytes32 | CREATE3 salt; combined with this factory's address to derive the target. |
initCode | bytes | Concatenation of creation bytecode and ABI-encoded constructor args. |
Returns
| Name | Type | Description |
|---|---|---|
deployed | address | Address of the newly deployed contract. |
predict
Returns the address that @custom:function deploy would target for salt.
Pure projection of (factory_address, salt) — does not check whether the address
is currently occupied. Callers that need occupancy info should follow up with an
addr.code.length check.
function predict(bytes32 salt) external view returns (address predicted);
Parameters
| Name | Type | Description |
|---|---|---|
salt | bytes32 | CREATE3 salt to look up. |
Returns
| Name | Type | Description |
|---|---|---|
predicted | address | Address at which a @custom:function deploy call with this salt would instantiate. |
Events
Deployed
Emitted on every successful CREATE3 deployment through this factory.
salt and deployed are indexed so consumers can filter by either. initCodeHash
is published alongside the deployment so downstream tooling can pin codehash
expectations against the on-chain artefact without re-fetching bytecode.
event Deployed(
bytes32 indexed salt, address indexed deployed, bytes32 initCodeHash, uint256 value
);
Parameters
| Name | Type | Description |
|---|---|---|
salt | bytes32 | The CREATE3 salt used to derive the deployed address. |
deployed | address | Address at which the contract was instantiated. |
initCodeHash | bytes32 | keccak256 of the init code passed to @custom:function deploy. |
value | uint256 | Native value forwarded to the constructor (zero for the dotNS pipeline). |
Errors
EmptyInitCode
Thrown when @custom:function deploy is called with an empty initCode blob.
CREATE3 with empty init code would deploy a zero-byte contract at the predicted address; the factory rejects the call early so callers fail fast on a malformed artefact lookup rather than land a useless deployment.
error EmptyInitCode();
Contents
DotnsNameEscrow
Inherits: Initializable, UUPSUpgradeable, OwnableUpgradeable, ReentrancyGuardTransient, ERC165Upgradeable, IERC721Receiver, IDotnsNameEscrow
Title: Dotns Name Escrow
Holds refundable deposits for registered names and manages the release/reclaim lifecycle. @custom:security-contact admin@parity.io
Constants
MAX_RELEASED_PAGE_SIZE
Maximum page size for releasedTokens pagination.
uint256 public constant MAX_RELEASED_PAGE_SIZE = 200
MAX_REFUND_PAGE_SIZE
Maximum page size for pendingRefunds pagination and batch claims.
uint256 public constant MAX_REFUND_PAGE_SIZE = 200
MAX_COOLDOWN
Upper bound on the configurable release-cooldown.
The cooldown gates only the release-to-withdraw delay, not the long-lived deposit lock
and not the reclaim boundary (see redeemWindow), so it is intentionally kept short.
Capping at one hour also keeps the cast to uint64 well below the saturation point at
every plausible block timestamp.
uint256 public constant MAX_COOLDOWN = 1 hours
MAX_REDEEM_WINDOW
Upper bound on the configurable redeem window.
The redeem window is a different quantity from the cooldown: it is the period after
release in which only the previous holder may act, and it gates reclaim rather than
withdrawal. The bound limits how long policy can hold a released name out of
circulation, and keeps the cast to uint64 in release well below saturation.
uint256 public constant MAX_REDEEM_WINDOW = 30 days
MIN_REDEEM_WINDOW
Lower bound on the configurable redeem window.
A window short enough to elapse before its holder can plausibly notice the release offers no protection at all, and one of zero length turns every release into an immediate hand-off to whoever is watching. The floor keeps the window long enough to span a holder being asleep or away for a day, so the guarantee survives any setting the owner is able to choose.
uint256 public constant MIN_REDEEM_WINDOW = 1 days
State Variables
protocolRegistry
The protocol registry for resolving sibling contract addresses.
IDotnsProtocolRegistry public protocolRegistry
cooldown
Delay after release before the deposit withdrawal may be credited.
Forces a delay between release and withdraw. It does not bound reclaim: the
release-to-reclaim boundary is redeemWindow, a separate and longer quantity. Also
supplies the per-entry clock for time-locked refund credits, which is why raising it
would slow every refund path and not just the deposit one.
uint256 public cooldown
tokenReserved
Total amount of a specific asset reserved across all positions.
Keyed by asset so future ERC20 support can track per-token liabilities independently;
address(0) represents the native token and is the only asset currently accepted.
mapping(address asset => uint256 amount) public tokenReserved
_positions
Per-token escrow position storing recipient, amount, lifecycle flags and cooldown.
mapping(uint256 tokenId => ReleasePosition position) private _positions
_releasedTokens
Ordered set of tokens currently in escrow custody, used for paginated enumeration.
uint256[] private _releasedTokens
_releasedIndexPlusOne
Reverse lookup into _releasedTokens (one-based) for O(1) remove-by-swap.
mapping(uint256 tokenId => uint256 indexPlusOne) private _releasedIndexPlusOne
protocolFees
Cumulative balance of non-refundable protocol fees; only accumulates.
Credited by cross-paid registration fees and transfer fees. Never debited: protocol fees do not back refunds, which draw solely on the per-asset reserve.
uint256 public protocolFees
_pendingWithdrawals
Pull-payment ledger storing each recipient's claimable refund balance.
Per-recipient isolation ensures a failing or reentrant receiver cannot block other
users' withdrawals. Used as the fallback path for registration overpayments whose
direct push back to msg.sender failed (because the caller is a contract that
rejects incoming value).
mapping(address recipient => uint256 amount) private _pendingWithdrawals
_refundEntries
Time-locked refund ledger keyed by entryId.
Every credit allocates a fresh entryId so per-entry cooldowns are independent and drip-feed credits cannot reset an existing entry's clock.
mapping(uint256 entryId => RefundEntry entry) private _refundEntries
_entriesByRecipient
Per-recipient list of pending entryIds for paginated enumeration and batch claim.
mapping(address recipient => uint256[] entryIds) private _entriesByRecipient
_entryIndexPlusOne
Reverse lookup into _entriesByRecipient (one-based) for O(1) remove-by-swap.
mapping(uint256 entryId => uint256 indexPlusOne) private _entryIndexPlusOne
_nextEntryId
Monotonic counter assigning entryIds to new refund credits.
uint256 private _nextEntryId
redeemWindow
Period after release during which only the previous holder may act.
Distinct from cooldown. Inside this window the holder may redeem the name back
and nobody else may take it (available reports false); once it elapses reclaim
becomes permissionless and any unwithdrawn deposit is credited to the recipient
rather than stranded. Appended after the pre-existing variables and paid for out of
__gap, so the layout of everything above is untouched.
uint256 public redeemWindow
__gap
Reserved storage space to allow for layout changes in the future. A new variable appended above must shrink this array by the same number of slots so the contract's total storage footprint stays fixed.
uint256[49] private __gap
Functions
onlyController
Restricts calls to the configured registrar controller.
modifier onlyController() ;
onlyRegistrar
Restricts calls to the configured registrar from the protocol registry.
modifier onlyRegistrar() ;
constructor
Note: oz-upgrades-unsafe-allow: constructor
constructor() ;
initialize
Initialises the name escrow.
Runs once behind the proxy; subsequent calls trigger @custom:reverts
InvalidInitialization via the initializer modifier. registry must be non-zero,
otherwise @custom:reverts InvalidAsset; cooldownSeconds is forwarded to
Note:
function: updateCooldown, which rejects a zero value (@custom:reverts
InvalidCooldown) and any value above @custom:constant MAX_COOLDOWN (@custom:reverts
CooldownTooLong), and emits @custom:emits CooldownUpdated as part of seeding the
initial cooldown. redeemWindowSeconds is forwarded to @custom:function
updateRedeemWindow, which rejects any value below @custom:constant MIN_REDEEM_WINDOW
(@custom:reverts RedeemWindowTooShort) or above @custom:constant MAX_REDEEM_WINDOW
(@custom:reverts RedeemWindowTooLong), and emits @custom:emits RedeemWindowUpdated.
function initialize(
IDotnsProtocolRegistry registry,
uint256 cooldownSeconds,
uint256 redeemWindowSeconds
)
external
initializer;
Parameters
| Name | Type | Description |
|---|---|---|
registry | IDotnsProtocolRegistry | Protocol registry used to resolve registrar and controller addresses. |
cooldownSeconds | uint256 | Delay after release before the deposit withdrawal may be credited. |
redeemWindowSeconds | uint256 | Period after release in which only the previous holder may act. |
updateCooldown
Updates the cooldown duration for future releases.
Owner-only. Affects only releases recorded after this call; positions already released
keep the withdrawAvailableAt snapshot taken at their release time. newCooldown
must be non-zero, otherwise @custom:reverts InvalidCooldown, and must not exceed the
contract's MAX_COOLDOWN upper bound, otherwise @custom:reverts CooldownTooLong; the
bound keeps the release-to-reclaim window short and protects the uint64 cast in
release from truncation. Emits @custom:emits CooldownUpdated with the prior and new
values.
function updateCooldown(uint256 newCooldown) public override onlyOwner;
updateRedeemWindow
Updates the redeem window for future releases.
Owner-only. Affects only releases recorded after this call; positions already released
keep the redeemableUntil snapshot taken at their release time. newRedeemWindow must
fall within MIN_REDEEM_WINDOW and MAX_REDEEM_WINDOW inclusive, otherwise
Note:
reverts: RedeemWindowTooShort or @custom:reverts RedeemWindowTooLong. The floor
keeps the window long enough to be worth having, so no setting the owner can choose
leaves a holder without a usable chance to recover an accidental release; the ceiling
limits how long a released name can be held out of circulation and protects the
uint64 cast in release from truncation. Emits @custom:emits RedeemWindowUpdated with
the prior and new values.
This is also the post-upgrade seeding hook: pair it with upgradeToAndCall so an
upgraded proxy never runs with an unseeded window.
function updateRedeemWindow(uint256 newRedeemWindow) public override onlyOwner;
getReleasePosition
Returns the escrow state for a token.
function getReleasePosition(uint256 tokenId)
external
view
override
returns (ReleasePosition memory position);
releasedTokenCount
Returns the number of tokens currently held by escrow pending reclaim or withdrawal.
function releasedTokenCount() external view override returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Number of released tokens not yet reclaimed. |
reserves
Returns total amount of assets liabilities reserved for withdrawals.
function reserves(address asset) external view returns (uint256 amount);
Parameters
| Name | Type | Description |
|---|---|---|
asset | address | Asset address. address(0) denotes native token. |
releasedTokens
Returns a bounded paginated slice of released token identifiers.
limit must be non-zero and at most MAX_RELEASED_PAGE_SIZE, otherwise
Note: reverts: InvalidPageSize.
function releasedTokens(
uint256 start,
uint256 limit
)
external
view
override
returns (uint256[] memory tokenIds);
Parameters
| Name | Type | Description |
|---|---|---|
start | uint256 | Start index into the released-token set. |
limit | uint256 | Maximum number of token identifiers to return. |
deposit
Records an asset deposit position for a token.
Only the configured controller may call this, otherwise @custom:reverts NotController.
params.amount must equal msg.value, otherwise @custom:reverts InvalidAmount; a
zero amount is accepted so cross-payer and free-tier registrations can still seed a
position that the release lifecycle can advance. Only native deposits are accepted
today, so a non-zero params.asset triggers @custom:reverts AssetNotSupported, and
a zero params.recipient triggers @custom:reverts InvalidRecipient. The slot for
params.tokenId must be empty (sentinel: position.recipient == address(0)):
a previously funded position triggers @custom:reverts PositionAlreadyFunded, and a
position already in the released phase triggers @custom:reverts AlreadyReleased.
Emits @custom:emits NativeDepositRecorded once the deposit is booked.
function deposit(DepositParams calldata params) external payable override onlyController;
creditOverpayment
Credits msg.value to recipient's pull-payment ledger so the caller can later
pull the balance with @custom:func claimWithdrawal.
Only the configured controller may call this, otherwise @custom:reverts NotController.
recipient must be non-zero (@custom:reverts InvalidRecipient) and msg.value must
be non-zero (@custom:reverts InvalidAmount). Used by the registrar controller to
refund overpayment without pushing native value into a potentially reverting
contract receiver. Emits @custom:emits OverpaymentRefunded once the credit lands.
function creditOverpayment(address recipient) external payable override onlyController;
Parameters
| Name | Type | Description |
|---|---|---|
recipient | address | Address whose pending balance should grow by msg.value. |
depositProtocolFee
Records a cross-paid registration fee into the protocol fee pot.
Only the configured controller may call this, otherwise @custom:reverts NotController.
msg.value must be non-zero, otherwise @custom:reverts InvalidAmount. Emits
Note:
emits: CrossTierFeePaid with isRegistration = true once the fee is booked.
function depositProtocolFee(ProtocolFeeDepositParams calldata params)
external
payable
override
onlyController;
chargeTransferFee
Charges the transfer fee and rebinds the token's escrow position to the new holder.
Only the configured registrar may call this, otherwise @custom:reverts NotRegistrar. When a fee is owed, the attached value must cover it or @custom:reverts InsufficientValue. Whenever a position exists for the token and the NFT is leaving its prior recipient, the position recipient is rebound to the new holder so the deposit (when funded) and the lifecycle marker (when zero-amount) both follow the NFT. The escrow does not refund anyone at transfer time; the only path back to the locked deposit is for the current holder to release into escrow and wait the cooldown. Emits @custom:emits CrossTierFeePaid (non-registration) when a non-zero fee is credited to the protocol fee pot, and credits any surplus value to the payer on the time-locked refund ledger via @custom:emits RefundCredited.
function chargeTransferFee(ChargeTransferFeeParams calldata params)
external
payable
override
onlyRegistrar
returns (uint256 charged);
Returns
| Name | Type | Description |
|---|---|---|
charged | uint256 | Amount actually credited to the protocol fee pot. |
release
Releases a token into escrow and starts the withdrawal cooldown.
First step of the phased lifecycle. The caller must be the current NFT holder and the
current position recipient (the field is rebound to the holder on every transfer that
moves the name off the prior recipient), otherwise @custom:reverts NotRefundRecipient.
Approved operators cannot release on behalf of the holder because the recipient field
is keyed to the holder, not to any approval set, which keeps the deposit refund tied
to the on-chain owner. The slot for tokenId must already hold a position (sentinel:
position.recipient != address(0)); an unseeded slot triggers @custom:reverts
DepositNotConfigured, and a position already in the released phase triggers
Note:
reverts: AlreadyReleased. Zero-amount positions are still releasable so every
minted name has a reachable lifecycle. The escrow must additionally be approved to
move the NFT, otherwise @custom:reverts EscrowNotApproved. Emits @custom:emits
NameReleased once the NFT is moved into custody.
Release stamps two independent clocks. withdrawAvailableAt (release + cooldown)
opens the deposit withdrawal; redeemableUntil (release + redeemWindow) closes the
holder's exclusive redeem phase and opens permissionless reclaim. Both are snapshots
so later policy changes never move an in-flight position. A release attempted while
redeemWindow is unseeded triggers @custom:reverts RedeemWindowNotConfigured.
function release(uint256 tokenId) external override nonReentrant;
withdraw
Credits the refundable deposit for a released token to the recipient's pending balance.
Second step of the phased lifecycle. The position must already be released
(@custom:reverts NotReleased otherwise) and not yet claimed (@custom:reverts
AlreadyClaimed on re-entry). Only the current position recipient (the address that
released the name, which mirrored the NFT holder at that moment) may call this,
otherwise @custom:reverts NotRefundRecipient, and block.timestamp must have reached
withdrawAvailableAt, otherwise @custom:reverts WithdrawalTooEarly. Refunds are backed
entirely by the per-asset tokenReserved pool; if that reserve is short,
Note: reverts: InsufficientFunds. Protocol fees never back a refund. Funds are not transferred here, only credited to the pull-payment ledger. Emits @custom:emits RefundWithdrawn once the credit lands.
function withdraw(uint256 tokenId) external override nonReentrant;
_settleDeposit
Moves a position's outstanding deposit onto the recipient's pull-payment balance.
Shared by @custom:function withdraw, where the recipient pulls the deposit themselves,
and by @custom:function reclaim, where a third party takes the name and the deposit is
settled on the departing holder's behalf. Both credit the same ledger and neither
transfers value, so the accounting is identical and lives here once. The per-asset
tokenReserved pool backs the refund in full, and @custom:reverts InsufficientFunds
when it cannot cover the amount owed. Emits @custom:emits RefundWithdrawn.
A zero-amount position is a no-op: it writes nothing and emits nothing, which keeps the
free-registration lifecycle free of meaningless ledger entries and events.
function _settleDeposit(
ReleasePosition storage position,
uint256 tokenId,
address recipient
)
private;
Parameters
| Name | Type | Description |
|---|---|---|
position | ReleasePosition | Storage pointer to the position being settled. |
tokenId | uint256 | |
recipient | address | Address credited with the deposit. Always the position recipient. |
claimWithdrawal
Pulls the caller's accumulated pending refund balance.
Final step of the phased lifecycle. Pull-payment isolation: each recipient owns an independent ledger entry, so a failing or reentrant receiver cannot block other users' withdrawals. The caller must have a positive pending balance, otherwise
Note: reverts: NoPendingWithdrawal; a failing native transfer triggers
function claimWithdrawal() external override nonReentrant returns (uint256 amount);
Returns
| Name | Type | Description |
|---|---|---|
amount | uint256 | Native amount transferred to the caller. |
pendingWithdrawal
Returns the pending refund balance owed to recipient.
function pendingWithdrawal(address recipient) external view override returns (uint256 amount);
Returns
| Name | Type | Description |
|---|---|---|
amount | uint256 | Native amount currently credited to recipient and pullable via claimWithdrawal. |
claimRefund
Pulls a single time-locked refund entry.
Caller must be the entry's recipient (@custom:reverts NotRefundRecipient otherwise),
the entry must exist (@custom:reverts NoSuchRefundEntry on a deleted or unknown id),
and block.timestamp must have reached availableAt (@custom:reverts
RefundLocked otherwise). The entry is deleted before the transfer; a failing native
transfer triggers @custom:reverts RefundFailed. Emits @custom:emits RefundClaimed.
function claimRefund(uint256 entryId) external override nonReentrant returns (uint256 amount);
Parameters
| Name | Type | Description |
|---|---|---|
entryId | uint256 | Identifier of the entry to claim. |
Returns
| Name | Type | Description |
|---|---|---|
amount | uint256 | Native amount transferred to the caller. |
claimRefundsBatch
Pulls multiple time-locked refund entries in one call.
Atomic: any invalid entry in the batch (wrong recipient, missing, or locked) reverts
the entire call. The batch size is bounded by MAX_REFUND_PAGE_SIZE
(@custom:reverts InvalidPageSize otherwise). Aggregates the per-entry amounts and
transfers once; on transfer failure @custom:reverts RefundFailed. Emits
Note: emits: RefundClaimed once per entry.
function claimRefundsBatch(uint256[] calldata entryIds)
external
override
nonReentrant
returns (uint256 totalAmount);
Parameters
| Name | Type | Description |
|---|---|---|
entryIds | uint256[] | List of entry identifiers to claim. |
Returns
| Name | Type | Description |
|---|---|---|
totalAmount | uint256 | Sum of the credited amounts transferred to the caller. |
pendingRefundCount
Returns the number of pending refund entries owed to recipient.
function pendingRefundCount(address recipient) external view override returns (uint256 count);
pendingRefundIds
Returns up to limit pending refund entry ids for recipient, starting at
offset.
Limit must be in (0, MAX_REFUND_PAGE_SIZE], otherwise @custom:reverts
InvalidPageSize.
function pendingRefundIds(
address recipient,
uint256 offset,
uint256 limit
)
external
view
override
returns (uint256[] memory entryIds);
pendingRefunds
Returns up to limit pending refund entries for recipient, paired with their
entry ids.
Limit must be in (0, MAX_REFUND_PAGE_SIZE], otherwise @custom:reverts
InvalidPageSize.
function pendingRefunds(
address recipient,
uint256 offset,
uint256 limit
)
external
view
override
returns (uint256[] memory entryIds, RefundEntry[] memory entries);
refundEntry
Returns a single refund entry by id, or a zero-filled struct if the id is unknown.
function refundEntry(uint256 entryId)
external
view
override
returns (RefundEntry memory entry);
_creditRefund
Internal helper: allocate a new entryId and credit a refund to recipient.
Assigns the next monotonic entryId, stores the entry, appends to the recipient's
enumeration array, and emits @custom:emits RefundCredited. The cooldown is read from the
configured cooldown storage value; @custom:constant MAX_COOLDOWN bounds it so the cast to
uint64 cannot truncate for any plausible block timestamp.
function _creditRefund(
address recipient,
uint256 amount,
uint256 tokenId
)
internal
returns (uint256 entryId);
_removeRefundEntry
Internal helper: delete a refund entry and swap-pop its slot in the recipient's enumeration array.
function _removeRefundEntry(uint256 entryId, address recipient) internal;
reclaim
Transfers a released token whose redeem window has elapsed to a new owner.
Hands the NFT back to the controller for re-registration. Only the configured
controller may call this, otherwise @custom:reverts NotController, and the position
must be released with redeemableUntil reached, otherwise @custom:reverts
NotReclaimable. Emits @custom:emits NameReclaimed once custody is transferred.
Reclaim does not require the deposit to have been withdrawn first. If the position
still holds value, this call settles it: the amount is debited from tokenReserved
(@custom:reverts InsufficientFunds if the reserve is short) and credited to the
previous recipient's pull-payment balance, claimable through @custom:function
claimWithdrawal with no deadline. That is what keeps a name recyclable when its
previous holder never returns: the value follows them, the name does not wait for them.
Emits @custom:emits RefundWithdrawn on settlement.
function reclaim(
uint256 tokenId,
address newOwner
)
external
override
onlyController
nonReentrant;
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | |
newOwner | address | Address of the new registrant taking over the name. |
isReclaimable
Returns whether a token may currently be reclaimed out of escrow custody.
public rather than external so reclaim can gate on it without a self-call, which
is what keeps the condition in one place instead of two.
function isReclaimable(uint256 tokenId) public view override returns (bool reclaimable);
Returns
| Name | Type | Description |
|---|---|---|
reclaimable | bool | True when @custom:function reclaim would succeed for tokenId. |
redeem
Returns a released token to its previous holder during the redeem window.
The undo for an accidental release, and the reason the redeem window exists. Only the
position recipient may call this (@custom:reverts NotRefundRecipient otherwise), the
position must be released and not yet withdrawn, and block.timestamp must still be
below redeemableUntil; a position failing any of those is not redeemable and
Note: reverts: NotRedeemable. No value moves. The position keeps its recipient, asset and amount, so the deposit stays locked exactly as it was before the release and the name returns to its pre-release state, releasable again later on a fresh pair of clocks. Excluding withdrawn positions is deliberate: a holder who has already pulled the deposit would otherwise recover the name without it being deposit-backed, breaking the one-deposit- per-live-name bound. The choice is therefore exclusive — take the value back, or take the name back. Emits @custom:emits NameRedeemed once custody returns.
function redeem(uint256 tokenId) external override nonReentrant;
onERC721Received
Whenever an {IERC721} tokenId token is transferred to this contract via
{IERC721-safeTransferFrom}
by operator from from, this function is called.
It must return its Solidity selector to confirm the token transfer.
If any other value is returned or the interface is not implemented by the recipient, the
transfer will be
reverted.
The selector can be obtained in Solidity with IERC721Receiver.onERC721Received.selector.
function onERC721Received(
address,
address,
uint256 tokenId,
bytes calldata
)
external
view
override
returns (bytes4 selector);
supportsInterface
function supportsInterface(bytes4 interfaceId) public view override returns (bool supported);
version
Returns implementation version.
function version() external pure virtual returns (string memory versionString);
Returns
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
_registrar
Returns the configured registrar from the protocol registry.
function _registrar() internal view returns (IDotnsRegistrar registrar);
_onlyController
Restricts calls to the configured controller from the protocol registry.
function _onlyController() internal view;
_onlyRegistrar
Restricts calls to the configured registrar from the protocol registry.
function _onlyRegistrar() internal view;
_addReleasedToken
Adds a token to the released-token set if absent.
function _addReleasedToken(uint256 tokenId) internal;
_removeReleasedToken
Removes a token from the released-token set if present.
function _removeReleasedToken(uint256 tokenId) internal;
_authorizeUpgrade
Function that should revert when msg.sender is not authorized to upgrade the contract.
Called by
{upgradeToAndCall}.
Normally, this function will use an xref:access.adoc[access control] modifier such as
{Ownable-onlyOwner}.
function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;
IDotnsNameEscrow
Title: Dotns Name Escrow Interface
Escrows refundable deposits for registered names and manages the release lifecycle.
Note: security-contact: admin@parity.io
Functions
reserves
Returns total amount of assets liabilities reserved for withdrawals.
function reserves(address asset) external view returns (uint256 amount);
Parameters
| Name | Type | Description |
|---|---|---|
asset | address | Asset address. address(0) denotes native token. |
getReleasePosition
Returns the escrow state for a token.
function getReleasePosition(uint256 tokenId)
external
view
returns (ReleasePosition memory position);
releasedTokenCount
Returns the number of tokens currently held by escrow pending reclaim or withdrawal.
function releasedTokenCount() external view returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Number of released tokens not yet reclaimed. |
releasedTokens
Returns a bounded paginated slice of released token identifiers.
limit must be non-zero and at most MAX_RELEASED_PAGE_SIZE, otherwise
Note: reverts: InvalidPageSize.
function releasedTokens(
uint256 start,
uint256 limit
)
external
view
returns (uint256[] memory tokenIds);
Parameters
| Name | Type | Description |
|---|---|---|
start | uint256 | Start index into the released-token set. |
limit | uint256 | Maximum number of token identifiers to return. |
deposit
Records an asset deposit position for a token.
Only the configured controller may call this, otherwise @custom:reverts NotController.
params.amount must equal msg.value, otherwise @custom:reverts InvalidAmount; a
zero amount is accepted so cross-payer and free-tier registrations can still seed a
position that the release lifecycle can advance. Only native deposits are accepted
today, so a non-zero params.asset triggers @custom:reverts AssetNotSupported, and
a zero params.recipient triggers @custom:reverts InvalidRecipient. The slot for
params.tokenId must be empty (sentinel: position.recipient == address(0)):
a previously funded position triggers @custom:reverts PositionAlreadyFunded, and a
position already in the released phase triggers @custom:reverts AlreadyReleased.
Emits @custom:emits NativeDepositRecorded once the deposit is booked.
function deposit(DepositParams calldata params) external payable;
depositProtocolFee
Records a cross-paid registration fee into the protocol fee pot.
Only the configured controller may call this, otherwise @custom:reverts NotController.
msg.value must be non-zero, otherwise @custom:reverts InvalidAmount. Emits
Note:
emits: CrossTierFeePaid with isRegistration = true once the fee is booked.
function depositProtocolFee(ProtocolFeeDepositParams calldata params) external payable;
creditOverpayment
Credits msg.value to recipient's pull-payment ledger so the caller can later
pull the balance with @custom:func claimWithdrawal.
Only the configured controller may call this, otherwise @custom:reverts NotController.
recipient must be non-zero (@custom:reverts InvalidRecipient) and msg.value must
be non-zero (@custom:reverts InvalidAmount). Used by the registrar controller to
refund overpayment without pushing native value into a potentially reverting
contract receiver. Emits @custom:emits OverpaymentRefunded once the credit lands.
function creditOverpayment(address recipient) external payable;
Parameters
| Name | Type | Description |
|---|---|---|
recipient | address | Address whose pending balance should grow by msg.value. |
chargeTransferFee
Charges the transfer fee and rebinds the token's escrow position to the new holder.
Only the configured registrar may call this, otherwise @custom:reverts NotRegistrar. When a fee is owed, the attached value must cover it or @custom:reverts InsufficientValue. Whenever a position exists for the token and the NFT is leaving its prior recipient, the position recipient is rebound to the new holder so the deposit (when funded) and the lifecycle marker (when zero-amount) both follow the NFT. The escrow does not refund anyone at transfer time; the only path back to the locked deposit is for the current holder to release into escrow and wait the cooldown. Emits @custom:emits CrossTierFeePaid (non-registration) when a non-zero fee is credited to the protocol fee pot, and credits any surplus value to the payer on the time-locked refund ledger via @custom:emits RefundCredited.
function chargeTransferFee(ChargeTransferFeeParams calldata params)
external
payable
returns (uint256 charged);
Returns
| Name | Type | Description |
|---|---|---|
charged | uint256 | Amount actually credited to the protocol fee pot. |
protocolFees
Returns the cumulative protocol fee balance, non-refundable and accumulating.
function protocolFees() external view returns (uint256 balance);
Returns
| Name | Type | Description |
|---|---|---|
balance | uint256 | Current protocol fee balance, in wei. |
release
Releases a token into escrow and starts the withdrawal cooldown.
First step of the phased lifecycle. The caller must be the current NFT holder and the
current position recipient (the field is rebound to the holder on every transfer that
moves the name off the prior recipient), otherwise @custom:reverts NotRefundRecipient.
Approved operators cannot release on behalf of the holder because the recipient field
is keyed to the holder, not to any approval set, which keeps the deposit refund tied
to the on-chain owner. The slot for tokenId must already hold a position (sentinel:
position.recipient != address(0)); an unseeded slot triggers @custom:reverts
DepositNotConfigured, and a position already in the released phase triggers
Note:
reverts: AlreadyReleased. Zero-amount positions are still releasable so every
minted name has a reachable lifecycle. The escrow must additionally be approved to
move the NFT, otherwise @custom:reverts EscrowNotApproved. Emits @custom:emits
NameReleased once the NFT is moved into custody.
Release stamps two independent clocks. withdrawAvailableAt (release + cooldown)
opens the deposit withdrawal; redeemableUntil (release + redeemWindow) closes the
holder's exclusive redeem phase and opens permissionless reclaim. Both are snapshots
so later policy changes never move an in-flight position. A release attempted while
redeemWindow is unseeded triggers @custom:reverts RedeemWindowNotConfigured.
function release(uint256 tokenId) external;
withdraw
Credits the refundable deposit for a released token to the recipient's pending balance.
Second step of the phased lifecycle. The position must already be released
(@custom:reverts NotReleased otherwise) and not yet claimed (@custom:reverts
AlreadyClaimed on re-entry). Only the current position recipient (the address that
released the name, which mirrored the NFT holder at that moment) may call this,
otherwise @custom:reverts NotRefundRecipient, and block.timestamp must have reached
withdrawAvailableAt, otherwise @custom:reverts WithdrawalTooEarly. Refunds are backed
entirely by the per-asset tokenReserved pool; if that reserve is short,
Note: reverts: InsufficientFunds. Protocol fees never back a refund. Funds are not transferred here, only credited to the pull-payment ledger. Emits @custom:emits RefundWithdrawn once the credit lands.
function withdraw(uint256 tokenId) external;
claimWithdrawal
Pulls the caller's accumulated pending refund balance.
Final step of the phased lifecycle. Pull-payment isolation: each recipient owns an independent ledger entry, so a failing or reentrant receiver cannot block other users' withdrawals. The caller must have a positive pending balance, otherwise
Notes:
-
reverts: NoPendingWithdrawal; a failing native transfer triggers
-
reverts: RefundFailed. Emits @custom:emits WithdrawalClaimed once the transfer succeeds.
function claimWithdrawal() external returns (uint256 amount);
Returns
| Name | Type | Description |
|---|---|---|
amount | uint256 | Native amount transferred to the caller. |
pendingWithdrawal
Returns the pending refund balance owed to recipient.
function pendingWithdrawal(address recipient) external view returns (uint256 amount);
Returns
| Name | Type | Description |
|---|---|---|
amount | uint256 | Native amount currently credited to recipient and pullable via claimWithdrawal. |
reclaim
Transfers a released token whose redeem window has elapsed to a new owner.
Hands the NFT back to the controller for re-registration. Only the configured
controller may call this, otherwise @custom:reverts NotController, and the position
must be released with redeemableUntil reached, otherwise @custom:reverts
NotReclaimable. Emits @custom:emits NameReclaimed once custody is transferred.
Reclaim does not require the deposit to have been withdrawn first. If the position
still holds value, this call settles it: the amount is debited from tokenReserved
(@custom:reverts InsufficientFunds if the reserve is short) and credited to the
previous recipient's pull-payment balance, claimable through @custom:function
claimWithdrawal with no deadline. That is what keeps a name recyclable when its
previous holder never returns: the value follows them, the name does not wait for them.
Emits @custom:emits RefundWithdrawn on settlement.
function reclaim(uint256 tokenId, address newOwner) external;
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | |
newOwner | address | Address of the new registrant taking over the name. |
isReclaimable
Returns whether a token may currently be reclaimed out of escrow custody.
True once the position is released and its redeem window has elapsed. Whether the
deposit was ever withdrawn makes no difference: @custom:function reclaim settles any
outstanding amount as part of the transfer.
Both @custom:function reclaim and @custom:function IDotnsRegistrar.available derive
their answer from isReclaimable, so a name is advertised as registrable exactly when
registering it would succeed. Consumers should call isReclaimable rather than
rebuilding the condition from @custom:function getReleasePosition.
function isReclaimable(uint256 tokenId) external view returns (bool reclaimable);
Returns
| Name | Type | Description |
|---|---|---|
reclaimable | bool | True when @custom:function reclaim would succeed for tokenId. |
redeem
Returns a released token to its previous holder during the redeem window.
The undo for an accidental release, and the reason the redeem window exists. Only the
position recipient may call this (@custom:reverts NotRefundRecipient otherwise), the
position must be released and not yet withdrawn, and block.timestamp must still be
below redeemableUntil; a position failing any of those is not redeemable and
Note: reverts: NotRedeemable. No value moves. The position keeps its recipient, asset and amount, so the deposit stays locked exactly as it was before the release and the name returns to its pre-release state, releasable again later on a fresh pair of clocks. Excluding withdrawn positions is deliberate: a holder who has already pulled the deposit would otherwise recover the name without it being deposit-backed, breaking the one-deposit- per-live-name bound. The choice is therefore exclusive — take the value back, or take the name back. Emits @custom:emits NameRedeemed once custody returns.
function redeem(uint256 tokenId) external;
updateCooldown
Updates the cooldown duration for future releases.
Owner-only. Affects only releases recorded after this call; positions already released
keep the withdrawAvailableAt snapshot taken at their release time. newCooldown
must be non-zero, otherwise @custom:reverts InvalidCooldown, and must not exceed the
contract's MAX_COOLDOWN upper bound, otherwise @custom:reverts CooldownTooLong; the
bound keeps the release-to-reclaim window short and protects the uint64 cast in
release from truncation. Emits @custom:emits CooldownUpdated with the prior and new
values.
function updateCooldown(uint256 newCooldown) external;
updateRedeemWindow
Updates the redeem window for future releases.
Owner-only. Affects only releases recorded after this call; positions already released
keep the redeemableUntil snapshot taken at their release time. newRedeemWindow must
fall within MIN_REDEEM_WINDOW and MAX_REDEEM_WINDOW inclusive, otherwise
Note:
reverts: RedeemWindowTooShort or @custom:reverts RedeemWindowTooLong. The floor
keeps the window long enough to be worth having, so no setting the owner can choose
leaves a holder without a usable chance to recover an accidental release; the ceiling
limits how long a released name can be held out of circulation and protects the
uint64 cast in release from truncation. Emits @custom:emits RedeemWindowUpdated with
the prior and new values.
This is also the post-upgrade seeding hook: pair it with upgradeToAndCall so an
upgraded proxy never runs with an unseeded window.
function updateRedeemWindow(uint256 newRedeemWindow) external;
cooldown
Delay after release before the deposit withdrawal may be credited.
function cooldown() external view returns (uint256 duration);
Returns
| Name | Type | Description |
|---|---|---|
duration | uint256 | Current cooldown in seconds. |
redeemWindow
Period after release during which only the previous holder may act.
function redeemWindow() external view returns (uint256 duration);
Returns
| Name | Type | Description |
|---|---|---|
duration | uint256 | Current redeem window in seconds. |
claimRefund
Pulls a single time-locked refund entry.
Caller must be the entry's recipient (@custom:reverts NotRefundRecipient otherwise),
the entry must exist (@custom:reverts NoSuchRefundEntry on a deleted or unknown id),
and block.timestamp must have reached availableAt (@custom:reverts
RefundLocked otherwise). The entry is deleted before the transfer; a failing native
transfer triggers @custom:reverts RefundFailed. Emits @custom:emits RefundClaimed.
function claimRefund(uint256 entryId) external returns (uint256 amount);
Parameters
| Name | Type | Description |
|---|---|---|
entryId | uint256 | Identifier of the entry to claim. |
Returns
| Name | Type | Description |
|---|---|---|
amount | uint256 | Native amount transferred to the caller. |
claimRefundsBatch
Pulls multiple time-locked refund entries in one call.
Atomic: any invalid entry in the batch (wrong recipient, missing, or locked) reverts
the entire call. The batch size is bounded by MAX_REFUND_PAGE_SIZE
(@custom:reverts InvalidPageSize otherwise). Aggregates the per-entry amounts and
transfers once; on transfer failure @custom:reverts RefundFailed. Emits
Note: emits: RefundClaimed once per entry.
function claimRefundsBatch(uint256[] calldata entryIds) external returns (uint256 totalAmount);
Parameters
| Name | Type | Description |
|---|---|---|
entryIds | uint256[] | List of entry identifiers to claim. |
Returns
| Name | Type | Description |
|---|---|---|
totalAmount | uint256 | Sum of the credited amounts transferred to the caller. |
pendingRefundCount
Returns the number of pending refund entries owed to recipient.
function pendingRefundCount(address recipient) external view returns (uint256 count);
pendingRefundIds
Returns up to limit pending refund entry ids for recipient, starting at
offset.
Limit must be in (0, MAX_REFUND_PAGE_SIZE], otherwise @custom:reverts
InvalidPageSize.
function pendingRefundIds(
address recipient,
uint256 offset,
uint256 limit
)
external
view
returns (uint256[] memory entryIds);
pendingRefunds
Returns up to limit pending refund entries for recipient, paired with their
entry ids.
Limit must be in (0, MAX_REFUND_PAGE_SIZE], otherwise @custom:reverts
InvalidPageSize.
function pendingRefunds(
address recipient,
uint256 offset,
uint256 limit
)
external
view
returns (uint256[] memory entryIds, RefundEntry[] memory entries);
refundEntry
Returns a single refund entry by id, or a zero-filled struct if the id is unknown.
function refundEntry(uint256 entryId) external view returns (RefundEntry memory entry);
Events
NativeDepositRecorded
Emitted when a native-token deposit is recorded.
event NativeDepositRecorded(uint256 indexed tokenId, uint256 amount);
NameReleased
Emitted when a token is released into escrow.
event NameReleased(
uint256 indexed tokenId,
address indexed recipient,
address indexed asset,
uint256 amount,
uint256 withdrawAvailableAt,
uint256 redeemableUntil
);
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | |
recipient | address | Refund recipient snapshotted at release time. |
asset | address | Deposit asset. address(0) denotes native token. |
amount | uint256 | |
withdrawAvailableAt | uint256 | Earliest withdrawal timestamp. |
redeemableUntil | uint256 | Timestamp at which the redeem window closes and reclaim opens. |
RefundWithdrawn
Emitted when a refund is credited to the recipient's pending balance.
event RefundWithdrawn(
uint256 indexed tokenId, address indexed recipient, address indexed asset, uint256 amount
);
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | |
recipient | address | |
asset | address | Refund asset. address(0) denotes native token. |
amount | uint256 |
WithdrawalClaimed
Emitted when a recipient pulls their accumulated pending refund balance.
event WithdrawalClaimed(address indexed recipient, uint256 amount);
RefundCredited
Emitted when a refund is credited to the time-locked refund ledger.
event RefundCredited(
address indexed recipient,
uint256 indexed entryId,
uint256 amount,
uint64 availableAt,
uint256 indexed tokenId
);
Parameters
| Name | Type | Description |
|---|---|---|
recipient | address | Address that may claim the entry once availableAt has elapsed. |
entryId | uint256 | Newly-assigned identifier for the credited entry. |
amount | uint256 | Native value credited. |
availableAt | uint64 | Earliest block timestamp at which the recipient may claim. |
tokenId | uint256 | Token associated with the credit, retained for traceability. |
RefundClaimed
Emitted when a recipient claims a single refund entry.
event RefundClaimed(address indexed recipient, uint256 indexed entryId, uint256 amount);
Parameters
| Name | Type | Description |
|---|---|---|
recipient | address | Address that pulled the entry. |
entryId | uint256 | Identifier of the claimed entry, now deleted. |
amount | uint256 | Native value transferred to recipient. |
NameReclaimed
Emitted when a released token is reclaimed by a new owner via registration.
event NameReclaimed(
uint256 indexed tokenId, address indexed previousRecipient, address indexed newOwner
);
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | |
previousRecipient | address | Address that received the refund for the prior registration. |
newOwner | address |
CooldownUpdated
Emitted when the cooldown duration for future releases is updated.
event CooldownUpdated(uint256 indexed currentCooldown, uint256 indexed newCooldown);
RedeemWindowUpdated
Emitted when the redeem window for future releases is updated.
event RedeemWindowUpdated(uint256 indexed currentRedeemWindow, uint256 indexed newRedeemWindow);
NameRedeemed
Emitted when a released token is redeemed by its previous holder.
The counterpart to @custom:emits NameReleased: custody returns to recipient and the
deposit stays locked, so no value event accompanies this.
event NameRedeemed(uint256 indexed tokenId, address indexed recipient);
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | |
recipient | address | Address the NFT was returned to, which is also the position recipient. |
CrossTierFeePaid
Emitted when a cross-paid fee is paid into the protocol fee pot.
event CrossTierFeePaid(
uint256 indexed tokenId,
address indexed payer,
address indexed recipient,
uint256 amount,
bool isRegistration
);
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | |
payer | address | Original msg.sender whose value funded the fee. |
recipient | address | |
amount | uint256 | |
isRegistration | bool | True when emitted from depositProtocolFee; false from chargeTransferFee. |
OverpaymentRefunded
Emitted when overpayment is refunded to the payer.
event OverpaymentRefunded(address indexed payer, uint256 amount);
Errors
NotController
Thrown when the caller is not the configured registrar controller.
error NotController(address caller);
NotRegistrar
Thrown when the caller is not the configured registrar.
error NotRegistrar(address caller);
InvalidRecipient
Thrown when the supplied refund recipient is invalid (e.g. zero address).
error InvalidRecipient();
InsufficientValue
Thrown when the attached call value is insufficient to cover the computed charge.
error InsufficientValue();
InsufficientFunds
Thrown when the per-asset reserve cannot cover the refund owed.
error InsufficientFunds(uint256 tokenId, uint256 owed, uint256 available);
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | |
owed | uint256 | |
available | uint256 | Reserve balance available for the asset. |
AssetNotSupported
Thrown when assets being deposited are not supported by the escrow.
error AssetNotSupported(address asset);
InvalidPageSize
Thrown when the configured page size is invalid.
error InvalidPageSize(uint256 limit);
InvalidCooldown
Thrown when the configured cooldown is invalid.
error InvalidCooldown();
CooldownTooLong
Thrown when the supplied cooldown exceeds the contract's configured upper bound.
error CooldownTooLong(uint256 supplied, uint256 maxAllowed);
Parameters
| Name | Type | Description |
|---|---|---|
supplied | uint256 | Cooldown value the caller asked for. |
maxAllowed | uint256 | Upper bound enforced by the contract. |
RedeemWindowNotConfigured
Thrown by release when the redeem window has never been seeded.
A configuration fault rather than a bad argument: the caller supplied nothing, and the deployment is missing a policy value. Fails the release closed rather than collapsing the holder's exclusive redeem phase to zero length, which would hand the name to whoever is watching the moment it is released. Cleared by calling
Note: function: updateRedeemWindow.
error RedeemWindowNotConfigured();
RedeemWindowTooShort
Thrown when the supplied redeem window is below the contract's configured lower bound.
error RedeemWindowTooShort(uint256 supplied, uint256 minAllowed);
Parameters
| Name | Type | Description |
|---|---|---|
supplied | uint256 | Redeem window value the caller asked for. |
minAllowed | uint256 | Lower bound enforced by the contract. |
RedeemWindowTooLong
Thrown when the supplied redeem window exceeds the contract's configured upper bound.
error RedeemWindowTooLong(uint256 supplied, uint256 maxAllowed);
Parameters
| Name | Type | Description |
|---|---|---|
supplied | uint256 | Redeem window value the caller asked for. |
maxAllowed | uint256 | Upper bound enforced by the contract. |
InvalidAmount
Thrown when the supplied amount is invalid.
error InvalidAmount();
InvalidAsset
Thrown when the supplied ERC20 asset is invalid.
error InvalidAsset();
PositionAlreadyFunded
Thrown when a deposit position is already funded.
error PositionAlreadyFunded(uint256 tokenId);
DepositNotConfigured
Thrown when no deposit is configured for the token.
error DepositNotConfigured(uint256 tokenId);
AlreadyReleased
Thrown when the token has already been released.
error AlreadyReleased(uint256 tokenId);
NotReleased
Thrown when the token has not been released.
error NotReleased(uint256 tokenId);
AlreadyClaimed
Thrown when the refund has already been claimed.
error AlreadyClaimed(uint256 tokenId);
NotReclaimable
Thrown when a token is not in a reclaimable state.
Reclaimable means released with the redeem window elapsed. A released token still inside its window is deliberately not reclaimable: that window belongs to the previous holder. Whether the deposit was withdrawn is irrelevant, because reclaim settles any unwithdrawn amount itself.
error NotReclaimable(uint256 tokenId);
NotRedeemable
Thrown when a token is not in a redeemable state.
Redeemable means released, not yet withdrawn, and still inside the redeem window. A withdrawn position is excluded on purpose: the holder has already taken the deposit value out, so returning the name as well would leave it unbacked.
error NotRedeemable(uint256 tokenId);
EscrowNotApproved
Thrown when escrow is not approved to transfer the token.
error EscrowNotApproved(uint256 tokenId);
NotRefundRecipient
Thrown when the caller is not the refund recipient.
error NotRefundRecipient(address caller, uint256 tokenId);
WithdrawalTooEarly
Thrown when withdrawal is attempted before cooldown has elapsed.
error WithdrawalTooEarly(uint256 tokenId, uint256 availableAt, uint256 currentTime);
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | |
availableAt | uint256 | Earliest withdrawal timestamp. |
currentTime | uint256 | Current block timestamp. |
RefundFailed
Thrown when a refund transfer fails.
error RefundFailed(uint256 tokenId);
NoPendingWithdrawal
Thrown when claimWithdrawal() is called but the caller has no pending balance.
error NoPendingWithdrawal();
NoSuchRefundEntry
Thrown when a refund entry is referenced but does not exist.
error NoSuchRefundEntry(uint256 entryId);
RefundLocked
Thrown when a refund entry is claimed before its availableAt cooldown has
elapsed.
error RefundLocked(uint256 entryId, uint64 availableAt);
NotAcceptedTransfer
Thrown when escrow receives an ERC721 transfer from a non-registrar source.
error NotAcceptedTransfer(address caller);
UnsolicitedDeposit
Thrown when escrow receives a registrar-sourced ERC721 transfer that does not correspond to a live release. Blocks holders who try to push a token into custody by calling @custom:function safeTransferFrom directly without going through @custom:function release, which would otherwise trap the NFT and any deposit permanently.
error UnsolicitedDeposit(uint256 tokenId);
Structs
DepositParams
Parameters for recording a deposit position.
The refund recipient is seeded at deposit time but is not locked: it rebinds to the current NFT holder on every transfer that moves the name off the prior recipient, so the deposit follows the name rather than the original payer. Only the current holder can release into escrow and pull the refund.
struct DepositParams {
uint256 tokenId;
address asset;
uint256 amount;
address recipient;
}
Properties
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | |
asset | address | Deposit asset. The zero address denotes the native token. |
amount | uint256 | |
recipient | address | Initial refund recipient; rebound to the current NFT holder on transfer. |
ProtocolFeeDepositParams
Parameters for recording a cross-paid registration fee into the protocol fee pot.
The pot is non-refundable and only accumulates; it never backs a refund. payer is
preserved purely for event accounting since the fee itself is non-refundable.
struct ProtocolFeeDepositParams {
uint256 tokenId;
address payer;
address recipient;
}
Properties
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | |
payer | address | Original msg.sender of the controller's register call. |
recipient | address | The NFT registrant the fee was paid on behalf of. |
ChargeTransferFeeParams
Inputs for charging transfer friction and rebinding the escrow position.
The fee charged is the name's own price returned by @custom:function PopRules.transferFloor, settled to the protocol fee pot. The deposit, when present, travels with the NFT: the position is rebound to the recipient so the new holder is the only address that can later release into escrow and unlock the locked value. There is no transfer-time refund path.
struct ChargeTransferFeeParams {
uint256 tokenId;
uint256 transferFee;
address payer;
address to;
}
Properties
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | Token whose escrow position is charged and rebound to the recipient. |
transferFee | uint256 | The name's own curve price on a downward or cross-reach transfer. |
payer | address | Original sender of the registrar transfer entrypoint. |
to | address | NFT recipient. Becomes the new position recipient whenever a position exists. |
ReleasePosition
Canonical escrow state for a token.
Tracks the phased lifecycle as two flags: released flips on release (NFT in escrow,
cooldown started); claimed flips on withdraw (refund credited to the pull-payment
ledger). The position is deleted on reclaim, freeing the slot for re-registration.
struct ReleasePosition {
address recipient;
address asset;
uint256 amount;
uint64 withdrawAvailableAt;
bool released;
bool claimed;
uint64 redeemableUntil;
}
Properties
| Name | Type | Description |
|---|---|---|
recipient | address | |
asset | address | Deposit asset. address(0) denotes native token. |
amount | uint256 | |
withdrawAvailableAt | uint64 | Earliest timestamp at which withdrawal is permitted. |
released | bool | |
claimed | bool | |
redeemableUntil | uint64 | Timestamp at which the holder's exclusive redeem window closes and permissionless reclaim opens. Appended last so every pre-existing field keeps its byte offset across the upgrade; it packs into the trailing slot alongside withdrawAvailableAt, released and claimed without consuming a new one. |
RefundEntry
Time-locked refund entry produced when the protocol owes a recipient value outside the registration-overpayment path.
Every credit creates a fresh entry with its own availableAt; later credits do not
reset earlier entries' clocks. Recipients claim entries individually or in batches.
struct RefundEntry {
address recipient;
uint256 amount;
uint64 availableAt;
uint256 tokenId;
}
Properties
| Name | Type | Description |
|---|---|---|
recipient | address | Address that may claim this entry once availableAt has elapsed. |
amount | uint256 | Native value credited. |
availableAt | uint64 | Earliest block timestamp at which the recipient may claim. |
tokenId | uint256 | Token this entry was produced for, retained for traceability. |
Contents
Contents
IPersonhood
Title: IPersonhood - Proof of Personhood Precompile
Query personhood status of an account.
Available at address 0x000000000000000000000000000000000a010000. The precompile reads from the alias-accounts pallet which stores per-context alias mappings backed by ring membership proofs. Ring roots are received from the People chain via XCM pub/sub. Example usage: IPersonhood personhood = IPersonhood(0x000000000000000000000000000000000a010000); IPersonhood.PersonhoodInfo memory info = personhood.personhoodStatus(someAddress, bytes32("dotns")); if (info.status == 2) { // full person ... }
Note: security-contact: admin@parity.io
Functions
personhoodStatus
Returns personhood info for an account within a specific application context.
function personhoodStatus(
address account,
bytes32 context
)
external
view
returns (PersonhoodInfo memory info);
Parameters
| Name | Type | Description |
|---|---|---|
account | address | The address to query. |
context | bytes32 | A 32-byte application identifier. Each application picks a fixed constant (e.g. bytes32("dotns")). The same person receives a different contextAlias in each context, preventing cross-application linkability. |
Returns
| Name | Type | Description |
|---|---|---|
info | PersonhoodInfo | The personhood info struct. All fields are zero when the account has no personhood. |
Structs
PersonhoodInfo
Personhood information for an account in a given context.
status tiers are defined incrementally: 0=None, 1=Lite, 2=Full. If we add more types in the future, the existing ones remain unchanged.
struct PersonhoodInfo {
uint8 status;
bytes32 contextAlias;
}
Properties
| Name | Type | Description |
|---|---|---|
status | uint8 | The personhood verification tier. |
contextAlias | bytes32 | Context-specific 32-byte pseudonym derived from ring membership proof. Unique per person per context, preventing cross-application linkability. Zero when status is None. |
Contents
ISystem
Title: ISystem
Minimal subset of revive's System precompile, vendored from
polkadot-sdk's substrate/frame/revive/uapi/sol/ISystem.sol.
Exposed at @custom:address SYSTEM_ADDR on every revive runtime that opts the
precompile in.
Kept intentionally minimal. New methods should be added on demand rather than mirrored wholesale, so the audit surface stays small and any upstream change is a deliberate review event.
Functions
callerIsRoot
Returning true iff the immediate caller's substrate origin is Root.
Reverts on a RuntimeOrigin::Signed(_) or non-Root origin.
function callerIsRoot() external view returns (bool);
originIsRoot
Returning true iff the transaction-level substrate origin is Root.
Reads the stack origin rather than the immediate caller, so it holds through a UUPS
proxy's delegatecall frame where callerIsRoot returns false. Returns false, rather
than reverting, on a non-Root origin.
function originIsRoot() external view returns (bool);
Contents
- DotnsCostModelRegistry
- DotnsFlatPricing
- DotnsScarcityPricing
- IDotnsCostModelRegistry
- IDotnsPricing
- IPopRules
- PopRules
DotnsCostModelRegistry
Inherits: Ownable, IDotnsCostModelRegistry
Title: DotNS Cost Model Registry
Keeps every registered cost model addressable by version and tracks the current one.
Holds only pointers, so it stays a plain owner-gated contract. PopRules resolves it once
through DotnsConstants.COST_MODEL and prices the current version for fresh reads and a
specific version for in-flight registrations.
Note: security-contact: admin@parity.io
State Variables
modelOf
mapping(uint256 version => IDotnsPricing model) public override modelOf
currentVersion
Returns the version currently serving fresh pricing.
uint256 public override currentVersion
Functions
constructor
Sets the owner permitted to register models.
constructor(address owner_) Ownable(owner_);
Parameters
| Name | Type | Description |
|---|---|---|
owner_ | address | Address that governs the model set. |
register
Registers a model and makes it current.
Owner-only. Keys the model by its own version, so a version can be registered once;
a repeat triggers @custom:reverts AlreadyRegistered. Moves the current pointer to the
new version and emits @custom:emits CostModelRegistered.
function register(IDotnsPricing model) external override onlyOwner;
Parameters
| Name | Type | Description |
|---|---|---|
model | IDotnsPricing | The cost model to register. |
setCurrentVersion
Points the current version at an already-registered model.
Owner-only. Reverts to a previously registered version without redeploying it, so
governance can roll fresh pricing back to an earlier curve. @custom:reverts
UnknownVersion when no model is registered for version. Emits @custom:emits
CurrentModelSet.
function setCurrentVersion(uint256 version) external override onlyOwner;
Parameters
| Name | Type | Description |
|---|---|---|
version | uint256 | The already-registered version to make current. |
current
Returns the current model.
function current() external view override returns (IDotnsPricing model);
Returns
| Name | Type | Description |
|---|---|---|
model | IDotnsPricing | The model serving the current version. |
priceForBaseLength
Prices a base length at the current version.
function priceForBaseLength(uint256 baseLength)
external
view
override
returns (uint256 weiPrice);
Parameters
| Name | Type | Description |
|---|---|---|
baseLength | uint256 | Digit-stripped length of the label being priced. |
Returns
| Name | Type | Description |
|---|---|---|
weiPrice | uint256 | Registration cost in wei at the current version. |
priceForBaseLengthAtVersion
Prices a base length at a specific version.
@custom:reverts UnknownVersion when no model is registered for version.
function priceForBaseLengthAtVersion(
uint256 version,
uint256 baseLength
)
external
view
override
returns (uint256 weiPrice);
Parameters
| Name | Type | Description |
|---|---|---|
version | uint256 | The version to price against. |
baseLength | uint256 | Digit-stripped length of the label being priced. |
Returns
| Name | Type | Description |
|---|---|---|
weiPrice | uint256 | Registration cost in wei at that version. |
DotnsFlatPricing
Inherits: IDotnsPricing
Title: DotNS Flat Pricing
Prices every registration at a single deposit, whatever the base length.
The launch cost model: one constant amount for any name the bands admit, so a nine-plus
character name costs the same flat deposit and shorter names stay gated by PopRules. The
deposit is fixed at deployment, so a new amount is a fresh deployment registered under
DotnsConstants.COST_MODEL. DotnsScarcityPricing is the length-sensitive alternative
held as a later candidate; it is not the registered default.
Note: security-contact: admin@parity.io
Constants
FORM_ID
Identifier of the flat model form, mixed into version.
Separates this form from another model that reuses the same deposit, so version
cannot collide across model forms.
uint256 public constant FORM_ID = uint256(keccak256("dotns.pricing.flat.v1"))
deposit
Deposit in wei charged for every base length.
uint256 public immutable deposit
Functions
constructor
Fixes the deposit for the life of this model.
Requires a strictly positive deposit so the model can never price at zero; a zero amount triggers @custom:reverts PricingError.
constructor(uint256 depositValue) ;
Parameters
| Name | Type | Description |
|---|---|---|
depositValue | uint256 | Deposit in wei charged for every base length. |
priceForBaseLength
Returns the registration cost in wei for a label of the given base length.
Returns the same deposit for every base length: the amount does not vary with the label, so the argument is read only to satisfy the interface.
function priceForBaseLength(uint256) external view override returns (uint256 weiPrice);
Parameters
| Name | Type | Description |
|---|---|---|
<none> | uint256 |
Returns
| Name | Type | Description |
|---|---|---|
weiPrice | uint256 | Registration cost in wei for that base length. |
version
Returns a stable identifier for this model and its parameters.
Two deployments with the same deposit share a version; any change to the deposit changes it.
function version() external view override returns (uint256 modelVersion);
Returns
| Name | Type | Description |
|---|---|---|
modelVersion | uint256 | Identifier derived from the model form and its parameters. |
DotnsScarcityPricing
Inherits: IDotnsPricing
Title: DotNS Scarcity Pricing
Prices a registration on a geometric scarcity curve driven by base length.
The curve doubles the base fee for each character below nine and halves it for each
character from nine upward, never below the floor. The base fee is the curve's value at
nine characters. Both parameters are fixed at deployment, so a new curve is a fresh
deployment registered under DotnsConstants.COST_MODEL. Held as a length-sensitive
candidate for a later cost-model version; the launch default is the constant
DotnsFlatPricing, so this model ships unregistered until governance registers it.
Note: security-contact: admin@parity.io
Constants
FORM_ID
Identifier of the scarcity curve form, mixed into version.
Distinguishes this curve shape from another model that reuses the same base fee and
floor, so version cannot collide across model forms.
uint256 public constant FORM_ID = uint256(keccak256("dotns.pricing.scarcity.v1"))
baseFee
Base fee D in wei: the curve's value at nine characters.
uint256 public immutable baseFee
minPrice
Price floor F in wei: the least any name can cost. Never above the base fee.
uint256 public immutable minPrice
Functions
constructor
Fixes the base fee and floor for the life of this model.
Carries the curve invariants: the base fee and floor are both strictly positive, the
floor does not exceed the base fee, and the base fee stays within
type(uint256).max / 512 so the multiplication below nine characters cannot overflow.
An all-digit label such as "42" strips to base length 0 and reaches the 2**9
multiplier, which sets the /512 ceiling. Any breach triggers @custom:reverts PricingError.
constructor(uint256 baseFeeValue, uint256 minPriceValue) ;
Parameters
| Name | Type | Description |
|---|---|---|
baseFeeValue | uint256 | Base fee D in wei. |
minPriceValue | uint256 | Price floor F in wei. |
priceForBaseLength
Returns the registration cost in wei for a label of the given base length.
Below nine the multiplier is at most 512, and the constructor caps the base fee at
type(uint256).max / 512 so the multiplication cannot overflow. From nine upward the
base fee is right-shifted by baseLength - 9, so it only decreases and the floor stops
a long base length costing nothing. The floor stays at or below the base fee, so it
only binds from nine characters upward, never in the doubling range below nine.
function priceForBaseLength(uint256 baseLength)
external
view
override
returns (uint256 weiPrice);
Parameters
| Name | Type | Description |
|---|---|---|
baseLength | uint256 | Digit-stripped length of the label being priced. |
Returns
| Name | Type | Description |
|---|---|---|
weiPrice | uint256 | Registration cost in wei for that base length. |
version
Returns a stable identifier for this model and its parameters.
Two deployments with the same parameters share a version; any change to the base fee or floor changes it.
function version() external view override returns (uint256 modelVersion);
Returns
| Name | Type | Description |
|---|---|---|
modelVersion | uint256 | Identifier derived from the model form and its parameters. |
IDotnsCostModelRegistry
Title: DotNS Cost Model Registry
Holds every cost model the protocol has run and names the current one.
The address registered under DotnsConstants.COST_MODEL points here, set once and never
repointed. Changing the live curve registers a new model, which adds its version and moves
the current pointer. Prior models stay live and priceable by version, so a registration
committed against an earlier curve settles at the amount it committed to.
Note: security-contact: admin@parity.io
Functions
register
Registers a model and makes it current.
Owner-only. Keys the model by its own version, so a version can be registered once;
a repeat triggers @custom:reverts AlreadyRegistered. Moves the current pointer to the
new version and emits @custom:emits CostModelRegistered.
function register(IDotnsPricing model) external;
Parameters
| Name | Type | Description |
|---|---|---|
model | IDotnsPricing | The cost model to register. |
setCurrentVersion
Points the current version at an already-registered model.
Owner-only. Reverts to a previously registered version without redeploying it, so
governance can roll fresh pricing back to an earlier curve. @custom:reverts
UnknownVersion when no model is registered for version. Emits @custom:emits
CurrentModelSet.
function setCurrentVersion(uint256 version) external;
Parameters
| Name | Type | Description |
|---|---|---|
version | uint256 | The already-registered version to make current. |
modelOf
Returns the model registered for a version, or the zero address when none.
function modelOf(uint256 version) external view returns (IDotnsPricing model);
Parameters
| Name | Type | Description |
|---|---|---|
version | uint256 | The version to look up. |
Returns
| Name | Type | Description |
|---|---|---|
model | IDotnsPricing | The model registered for that version. |
currentVersion
Returns the version currently serving fresh pricing.
function currentVersion() external view returns (uint256 version);
Returns
| Name | Type | Description |
|---|---|---|
version | uint256 | The current version identifier. |
current
Returns the current model.
function current() external view returns (IDotnsPricing model);
Returns
| Name | Type | Description |
|---|---|---|
model | IDotnsPricing | The model serving the current version. |
priceForBaseLength
Prices a base length at the current version.
function priceForBaseLength(uint256 baseLength) external view returns (uint256 weiPrice);
Parameters
| Name | Type | Description |
|---|---|---|
baseLength | uint256 | Digit-stripped length of the label being priced. |
Returns
| Name | Type | Description |
|---|---|---|
weiPrice | uint256 | Registration cost in wei at the current version. |
priceForBaseLengthAtVersion
Prices a base length at a specific version.
@custom:reverts UnknownVersion when no model is registered for version.
function priceForBaseLengthAtVersion(
uint256 version,
uint256 baseLength
)
external
view
returns (uint256 weiPrice);
Parameters
| Name | Type | Description |
|---|---|---|
version | uint256 | The version to price against. |
baseLength | uint256 | Digit-stripped length of the label being priced. |
Returns
| Name | Type | Description |
|---|---|---|
weiPrice | uint256 | Registration cost in wei at that version. |
Events
CostModelRegistered
Emitted when a model is registered and becomes current.
event CostModelRegistered(uint256 indexed version, address indexed model);
Parameters
| Name | Type | Description |
|---|---|---|
version | uint256 | The model's version identifier. |
model | address | The model address now serving that version. |
CurrentModelSet
Emitted when the current version is pointed at an already-registered model.
event CurrentModelSet(uint256 indexed version);
Parameters
| Name | Type | Description |
|---|---|---|
version | uint256 | The version now serving fresh pricing. |
Errors
AlreadyRegistered
Thrown when registering a model whose version is already held.
error AlreadyRegistered(uint256 version);
Parameters
| Name | Type | Description |
|---|---|---|
version | uint256 | The version already registered. |
UnknownVersion
Thrown when pricing against a version that was never registered.
error UnknownVersion(uint256 version);
Parameters
| Name | Type | Description |
|---|---|---|
version | uint256 | The version with no registered model. |
ZeroVersion
Thrown when registering a model whose version is zero, which is the sentinel for an unregistered version and so cannot name a real model.
error ZeroVersion();
PricingVersionMismatch
Thrown when a registration reveals at a different version than it committed to.
Raised where a commit-reveal flow binds a version at commit and checks it at reveal, so the version a name prices at cannot move after the commitment is made.
error PricingVersionMismatch(uint256 committed, uint256 revealed);
Parameters
| Name | Type | Description |
|---|---|---|
committed | uint256 | The version bound when the commitment was made. |
revealed | uint256 | The version supplied at reveal. |
IDotnsPricing
Title: DotNS Pricing Cost Model
Prices a registration from the base length of its label alone.
The seam between name policy and the wei amount a registration costs. PopRules and the
public commit-reveal controller keep the classification, reservation, and tier rules; the
model owns only the amount for a given base length, so the curve can be swapped by
registering a new model under DotnsConstants.COST_MODEL without touching either. Only the
base length crosses the seam: the model reads no personhood band or PopStatus. The public
controller prices NoStatus deposits through this same path, so the model carries no PoP
name.
Note: security-contact: admin@parity.io
Functions
priceForBaseLength
Returns the registration cost in wei for a label of the given base length.
Pure amount lookup: the caller supplies the digit-stripped base length and the model returns the curve value for it. Runs on the ERC721 transfer floor read, so it stays a view with no state writes.
function priceForBaseLength(uint256 baseLength) external view returns (uint256 weiPrice);
Parameters
| Name | Type | Description |
|---|---|---|
baseLength | uint256 | Digit-stripped length of the label being priced. |
Returns
| Name | Type | Description |
|---|---|---|
weiPrice | uint256 | Registration cost in wei for that base length. |
version
Returns a stable identifier for this model and its parameters.
Changes when the model shape or its parameters change, so clients and telemetry can tell one live curve from another. Not consulted on the pricing path.
function version() external view returns (uint256 modelVersion);
Returns
| Name | Type | Description |
|---|---|---|
modelVersion | uint256 | Identifier derived from the model form and its parameters. |
Errors
PricingError
Thrown when a model constructor parameter breaks a pricing invariant.
error PricingError(string reason);
Parameters
| Name | Type | Description |
|---|---|---|
reason | string | Human-readable explanation of the failed invariant. |
IPopRules
Title: Proof of Personhood Rules for Dotns
Proof of personhood interface defining Dotns price calculation, PoP-tier requirements, and base-name reservation rules.
Classifies labels into the PoP tier required for registration and exposes reservation
metadata. Length <= 5 is reserved for governance; lengths 6-8 require PopFull unless they
carry exactly two trailing digits (PopLite, gateway-issued); lengths >= 9 are open to
every caller as NoStatus when they carry zero or exactly two trailing digits. Any one-digit
suffix, and any suffix longer than two digits, is invalid; internal digits do not affect
classification. Reservations are keyed by the digit-stripped stem so alice and alice42
share a slot.
Amounts come from the cost model registered under DotnsConstants.COST_MODEL, which owns
the curve; only the base length crosses that seam. Every caller pays the same amount for a
given length; personhood only unlocks the premium band.
Note: security-contact: admin@parity.io
Functions
classifyName
Classifies a name into a required PoP tier per DotNS naming rules.
Pure; inputs are the label bytes only. Callers use the returned tier to decide which pricing and verification branch applies. Non-canonical labels (anything other than a single lowercase ASCII DNS label) and labels with exactly one or more than two trailing digits both trigger @custom:reverts PopError.
function classifyName(string calldata name)
external
pure
returns (PopStatus requirement, string memory message);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | The name label being evaluated. |
Returns
| Name | Type | Description |
|---|---|---|
requirement | PopStatus | Required tier for registration. |
message | string | Explanation of the classification result. |
setShortNamesEnabled
Opens or closes the public market for names shorter than nine characters.
Owner-only; unauthorised callers trigger @custom:reverts OwnableUnauthorizedAccount. While closed, which is the deploy default, @custom:function priceWithCheck and
Note: function: priceWithoutCheck trigger @custom:reverts PopError for a base length below nine, so no public caller buys a short name. The gateway free grant and the registrar's registerReserved path do not read this flag. Emits @custom:emits ShortNamesEnabledUpdated.
function setShortNamesEnabled(bool enabled) external;
Parameters
| Name | Type | Description |
|---|---|---|
enabled | bool | Whether names shorter than nine characters may be bought. |
personhoodOf
Returns the personhood tier recorded for an account.
Reads the account's dotns-scoped tier from the personhood precompile and maps it to a
PopStatus. This is the direct account-tier read; the same tier otherwise surfaces
only as the userStatus field of a pricing query. Never returns Reserved, so the
result is one of NoStatus, PopLite, or PopFull.
function personhoodOf(address account) external view returns (PopStatus tier);
Parameters
| Name | Type | Description |
|---|---|---|
account | address | Address whose tier is read. |
Returns
| Name | Type | Description |
|---|---|---|
tier | PopStatus | The account's personhood tier. |
reserveBaseName
Creates or refreshes a reservation entry for a PopLite-eligible stem.
Commit-reveal reservation path. Only an authorised controller on the registrar may
call this, otherwise @custom:reverts NotRegistry. The caller passes the
already-stripped stem; the contract enforces stem shape (no trailing digits) and
PopLite-eligibility
(length in [6, 8]), and a non-canonical label or a label outside that shape triggers
Note: reverts: PopError. Cross-user collision on a live slot triggers @custom:reverts PopError so the caller cannot silently overwrite another user's reservation; same-user refresh and writes into an empty or expired slot emit @custom:emits BaseNameReserved.
function reserveBaseName(string calldata stem, address user) external;
Parameters
| Name | Type | Description |
|---|---|---|
stem | string | The base label with no trailing digits. |
user | address | The address receiving reservation rights. |
reserveBaseNameForPop
Writes or refreshes a reservation for a bare base-name stem.
Gateway-driven reservation path used by the PoP controller. Only a controller in the
registrar's controllers set may call this, otherwise @custom:reverts NotRegistry.
Does not apply the lite-format length window that @custom:function reserveBaseName
enforces, but does require the input to be canonical and stem-shaped (no trailing
digits); a non-canonical or non-stem label triggers @custom:reverts PopError. If the
slot is already live and held by a different user, @custom:reverts PopError so the
caller's local bookkeeping and PopRules state stay in lockstep; if it is live for the
same user, expiry is refreshed to block.timestamp + MAX_RESERVATION_TIME. Emits
Note: emits: BaseNameReserved on every successful write.
function reserveBaseNameForPop(string calldata stem, address user) external;
Parameters
| Name | Type | Description |
|---|---|---|
stem | string | The base label with no trailing digits. |
user | address | The address receiving reservation rights. |
releaseBaseName
Clears a reservation for a base-name stem.
Only a controller in the registrar's controllers set may call this, otherwise
Notes:
-
reverts: NotRegistry. Non-canonical or non-stem labels trigger
-
reverts: PopError. Live reservations may only be cleared by the same controller that wrote them; another authorised controller attempting to clear a live slot triggers
-
reverts: PopError. Expired reservations may be cleared by any authorised controller as garbage collection. Used by the PoP controller when a reservation is claimed, relinquished, or a queue head promotion leaves the slot empty. Emits
-
emits: BaseNameReleased once the slot is cleared.
function releaseBaseName(string calldata stem) external;
Parameters
| Name | Type | Description |
|---|---|---|
stem | string | The base label whose reservation should be cleared (no trailing digits). |
releaseReservationForReclaim
Clears a reservation when the slot owner matches expectedOwner, allowing any
registrar-authorised controller (not only the stamping one) to release the slot.
Narrower than @custom:function releaseBaseName: callers must prove they know the
slot owner, so cross-controller release is gated on a positive match rather than on
caller identity. Intended for the public registrar controller's reclaim path, where
a prior occupant has handed the name back to escrow and the new registrant needs
the cross-flow guard cleared regardless of which controller originally stamped it.
Only a registrar-authorised controller may call this (@custom:reverts NotRegistry).
Non-canonical or non-stem labels trigger @custom:reverts PopError. A live reservation
whose owner does not match expectedOwner triggers @custom:reverts PopError; expired
reservations are cleared regardless. Emits @custom:emits BaseNameReleased.
function releaseReservationForReclaim(string calldata stem, address expectedOwner) external;
Parameters
| Name | Type | Description |
|---|---|---|
stem | string | The base label whose reservation should be cleared (no trailing digits). |
expectedOwner | address | The address the caller expects to be the current reservation owner. |
getBaseNameReservation
Retrieves reservation information for a base name.
Raw accessor: returns the stored slot regardless of expiry. Use
Notes:
-
function: isBaseNameReserved when live-window semantics are needed. Non-canonical labels trigger
-
reverts: PopError.
function getBaseNameReservation(string calldata baseName)
external
view
returns (address owner, uint64 expires);
Parameters
| Name | Type | Description |
|---|---|---|
baseName | string | The base label without trailing digits. |
Returns
| Name | Type | Description |
|---|---|---|
owner | address | The address assigned to the reservation. |
expires | uint64 | UNIX timestamp when the reservation expires. |
stripDigits
Returns the bare stem of a label, i.e. the label with any trailing ASCII digits removed.
Mirrors the normalisation that @custom:function reserveBaseName applies before writing a reservation, so callers can look up or release a reservation by passing the full label without re-implementing the digit-stripping rule. Non-canonical labels trigger @custom:reverts PopError.
function stripDigits(string calldata name) external pure returns (string memory stem);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Full label (with or without trailing digits). |
Returns
| Name | Type | Description |
|---|---|---|
stem | string | The label with trailing digits removed. |
isBaseNameReserved
Indicates whether a base name is currently reserved.
Applies the live-window predicate to the stored slot so an expired reservation reads as free. Non-canonical labels trigger @custom:reverts PopError.
function isBaseNameReserved(string calldata baseName)
external
view
returns (bool reservedStatus, address owner, uint64 expires);
Parameters
| Name | Type | Description |
|---|---|---|
baseName | string | The base label without trailing digits. |
Returns
| Name | Type | Description |
|---|---|---|
reservedStatus | bool | True if a live reservation is active. |
owner | address | The reservation holder (zero when not reserved). |
expires | uint64 | UNIX timestamp when the reservation expires. |
priceWithCheck
Calculates price with PoP classification and reservation enforcement.
Reverting pricing path used by the commit-reveal controller. Price is the scarcity
curve for the label's base length and is charged to every caller, verified or not;
personhood only unlocks the premium band. Non-canonical
labels, a base stem held live by another user, a governance-reserved label, or a
userAddress whose personhood tier does not meet the label's required tier each
trigger @custom:reverts PopError.
function priceWithCheck(
string calldata name,
address userAddress
)
external
view
returns (PriceWithMeta memory metadata);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label. |
userAddress | address | Registering user for the given label. |
Returns
| Name | Type | Description |
|---|---|---|
metadata | PriceWithMeta | Price with PoP requirements and classification. |
priceWithCheckAtVersion
Calculates price at a specific cost-model version with PoP classification and reservation enforcement.
The versioned counterpart of @custom:function priceWithCheck: identical classification,
tier gating, and reservation rules, but the amount comes from the model registered for
pricingVersionValue rather than the current one. The commit-reveal controller prices
a reveal at the version bound into its commitment, so a model change between commit and
reveal does not move the amount. @custom:reverts UnknownVersion when the version was
never registered.
function priceWithCheckAtVersion(
string calldata name,
address userAddress,
uint256 pricingVersionValue
)
external
view
returns (PriceWithMeta memory metadata);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label. |
userAddress | address | Registering user for the given label. |
pricingVersionValue | uint256 | Cost-model version to price against. |
Returns
| Name | Type | Description |
|---|---|---|
metadata | PriceWithMeta | Price with PoP requirements and classification. |
priceWithoutCheck
Calculates price with PoP classification and reservation metadata, without reverting on conflicts.
Non-reverting counterpart to priceWithCheck: surfaces the same fields, but reports
a Reserved status through metadata instead of reverting when the base stem is
held by another user. Used by front-ends that need to present a price and eligibility
preview without forcing a transaction attempt. Governance-reserved names are not
rejected here either; the caller decides what to do. Non-canonical labels still
trigger @custom:reverts PopError because the input is malformed rather than just
contested.
function priceWithoutCheck(
string calldata name,
address userAddress
)
external
view
returns (PriceWithMeta memory metadata);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label. |
userAddress | address | Registering user for the given label. |
Returns
| Name | Type | Description |
|---|---|---|
metadata | PriceWithMeta | Price with PoP requirements and classification. |
priceWithoutCheckAtVersion
Calculates price at a specific cost-model version with PoP classification and reservation metadata, without reverting on conflicts.
The versioned counterpart of @custom:function priceWithoutCheck: same non-reverting
preview behaviour, but the amount comes from the model registered for
pricingVersionValue. @custom:reverts UnknownVersion when the version was never
registered.
function priceWithoutCheckAtVersion(
string calldata name,
address userAddress,
uint256 pricingVersionValue
)
external
view
returns (PriceWithMeta memory metadata);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label. |
userAddress | address | Registering user for the given label. |
pricingVersionValue | uint256 | Cost-model version to price against. |
Returns
| Name | Type | Description |
|---|---|---|
metadata | PriceWithMeta | Price with PoP requirements and classification. |
transferFloor
Transfer-time floor: the greater of the recipient-reach component and the sender-tier-downgrade component, each priced at the name's own length.
Re-prices the name at its own length on every move: returns the name's curve price when either (i) the recipient does not meet the label's required tier, or (ii) the recipient's personhood tier is strictly below the sender's, and zero when neither holds. Passing a name to a wallet that could never have registered it therefore costs the name's own curve price. The two components overlap on pure tier mismatches, so the function takes their maximum rather than their sum to avoid double-charging. Consumed by @custom:function DotnsRegistrar.quoteTransferFee. Non-canonical labels and labels with exactly one or more than two trailing digits trigger @custom:reverts PopError.
function transferFloor(
string calldata name,
address from,
address to
)
external
view
returns (uint256 floor);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label being transferred. |
from | address | Current holder of the name. |
to | address | Incoming holder of the name. |
Returns
| Name | Type | Description |
|---|---|---|
floor | uint256 | Transfer-time floor in wei: the name's own curve price, or zero. |
isBaseName
Returns whether name is a base name under PoP rules.
A base name has no trailing digits; lite-person labels always have exactly two trailing digits, so the two spaces are disjoint. Non-canonical labels trigger
Note: reverts: PopError.
function isBaseName(string calldata name) external pure returns (bool isBase);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | The label to check. |
Returns
| Name | Type | Description |
|---|---|---|
isBase | bool | True when the label has no trailing digits. |
price
Calculates registration cost for a label.
Prices the label by its base length through the cost model registered under
DotnsConstants.COST_MODEL. Ignores the caller's personhood status and reservation
state. A label whose trailing-digit suffix is neither zero nor exactly two, and any
non-canonical label, trigger @custom:reverts PopError.
function price(string calldata name) external view returns (uint256 cost);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label to price. |
Returns
| Name | Type | Description |
|---|---|---|
cost | uint256 | Registration cost in wei. |
pricingVersion
Returns the current cost-model version.
The current version held by the registry under DotnsConstants.COST_MODEL. The
commit-reveal controller binds it into a commitment and prices the reveal at that
version, so a model change between commit and reveal leaves the committed amount
unchanged. @custom:reverts PopError when no registry is configured.
function pricingVersion() external view returns (uint256 modelVersion);
Returns
| Name | Type | Description |
|---|---|---|
modelVersion | uint256 | Identifier of the current cost model and its parameters. |
Events
BaseNameReserved
Emitted when a base name receives a reservation.
event BaseNameReserved(string indexed baseName, address indexed owner, uint64 expires);
Parameters
| Name | Type | Description |
|---|---|---|
baseName | string | The digit-stripped label receiving the reservation. |
owner | address | Address obtaining the reservation right. |
expires | uint64 | UNIX timestamp when the reservation expires. |
ShortNamesEnabledUpdated
Emitted when the public market for names shorter than nine characters is opened or closed.
Owner-only setter @custom:function setShortNamesEnabled.
event ShortNamesEnabledUpdated(bool enabled);
Parameters
| Name | Type | Description |
|---|---|---|
enabled | bool | Whether names shorter than nine characters may now be bought. |
BaseNameReleased
Emitted when a base-name reservation is cleared.
event BaseNameReleased(string indexed baseName);
Parameters
| Name | Type | Description |
|---|---|---|
baseName | string | The base label whose reservation was released. |
Errors
PopError
Thrown when a name violates PoP-tier or reservation requirements.
error PopError(string reason);
Parameters
| Name | Type | Description |
|---|---|---|
reason | string | Human-readable explanation of the failure condition. |
NotRegistry
Thrown when a caller is not an authorised controller on the registrar.
error NotRegistry();
NameReserved
Thrown when registering a name whose base stem is held as a live reservation by another user.
error NameReserved(string label);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Caller-supplied label whose stem is reserved. |
GovernanceReserved
Thrown when registering a label that classifies as governance-reserved at the protocol level.
Distinct from @custom:reverts NameReserved so off-chain consumers can tell "wait for the holder to relinquish" apart from "this label is permanently held by governance".
error GovernanceReserved(string label);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Caller-supplied label that classifies as governance-reserved. |
OwnerStatusInsufficient
Thrown on the cross-payer path when the owner's recorded PoP tier does not meet the
label's required tier. The direct path's priceWithCheck covers this same condition via its
own revert.
error OwnerStatusInsufficient(string label, PopStatus userStatus, PopStatus required);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Label whose tier requirement was unmet. |
userStatus | PopStatus | Owner's recorded tier. |
required | PopStatus | Required tier for the label. |
Structs
PriceWithMeta
Bundle returned from metadata-aware pricing queries.
struct PriceWithMeta {
uint256 price;
PopStatus status;
PopStatus userStatus;
string message;
}
Properties
| Name | Type | Description |
|---|---|---|
price | uint256 | Registration cost from the current cost model for the label's base length. |
status | PopStatus | Required PoP tier for this name. |
userStatus | PopStatus | Current PoP status recorded for the querying user. |
message | string | Human-readable classification description. |
Reservation
Reservation metadata for a base name (digits removed).
struct Reservation {
address owner;
uint64 expires;
address controller;
}
Properties
| Name | Type | Description |
|---|---|---|
owner | address | Address holding exclusive claim rights during the reservation window. |
expires | uint64 | UNIX timestamp when the reservation expires. |
controller | address | Address that wrote the reservation; the only address permitted to release it before expiry. |
Enums
PopStatus
Proof-of-Personhood eligibility tier.
NoStatus is the default for unverified users; PopLite and PopFull are the two
personhood tiers; Reserved covers both governance-held names and base stems held by
another user through the reservation table.
enum PopStatus {
NoStatus,
PopLite,
PopFull,
Reserved
}
PopRules
Inherits: Initializable, UUPSUpgradeable, OwnableUpgradeable, ERC165Upgradeable, IPopRules
Title: PopRules
Implements DotNS classification, cost-model-driven pricing, and base-name reservations.
Tiers: base lengths <= 5 are governance-reserved, base lengths 6-8 require PopFull
(or PopLite when carrying exactly two trailing digits, for gateway-issued lite names),
base lengths >= 9 are open to any caller as NoStatus when they carry zero or exactly two
trailing digits. A one-digit suffix and more than two trailing digits are invalid.
Every caller pays the same amount for a given base length. The amount comes from the cost
model registered under DotnsConstants.COST_MODEL, which owns the curve; this contract
passes it only the base length and keeps the classification, reservation, and tier rules.
Personhood only unlocks the premium band. Base lengths below nine are closed to the public
paid path until governance sets shortNamesEnabled; the gateway and registerReserved do
not consult it.
Note: security-contact: admin@parity.io
Constants
MAX_RESERVATION_TIME
Maximum time a base name can be reserved.
uint256 public constant MAX_RESERVATION_TIME = 12 weeks
State Variables
reservations
Active reservations keyed by digit-stripped base name.
mapping(string baseName => Reservation reservation) public reservations
protocolRegistry
Protocol-level address registry for all DotNS contracts.
IDotnsProtocolRegistry public protocolRegistry
shortNamesEnabled
Whether the public paid path may register names shorter than nine characters. Closed by default; only governance opens it.
bool public shortNamesEnabled
__gap
uint256[50] private __gap
Functions
onlyRegistry
Restricts function to any registry-authorised controller.
modifier onlyRegistry() ;
constructor
Note: oz-upgrades-unsafe-allow: constructor
constructor() ;
initialize
Initialises the oracle (public entry point).
Runs once behind the proxy; subsequent calls trigger @custom:reverts
InvalidInitialization via the initializer modifier. Amounts come from the cost model
registered under DotnsConstants.COST_MODEL, so no price is seeded here.
function initialize(IDotnsProtocolRegistry registry) public initializer;
Parameters
| Name | Type | Description |
|---|---|---|
registry | IDotnsProtocolRegistry | Protocol-level address registry used to resolve sibling contracts. |
setShortNamesEnabled
Opens or closes the public market for names shorter than nine characters.
Owner-only; unauthorised callers trigger @custom:reverts OwnableUnauthorizedAccount. While closed, which is the deploy default, @custom:function priceWithCheck and
Note: function: priceWithoutCheck trigger @custom:reverts PopError for a base length below nine, so no public caller buys a short name. The gateway free grant and the registrar's registerReserved path do not read this flag. Emits @custom:emits ShortNamesEnabledUpdated.
function setShortNamesEnabled(bool enabled) external override onlyOwner;
Parameters
| Name | Type | Description |
|---|---|---|
enabled | bool | Whether names shorter than nine characters may be bought. |
classifyName
Classifies a name into a required PoP tier per DotNS naming rules.
Pure; inputs are the label bytes only. Callers use the returned tier to decide which pricing and verification branch applies. Non-canonical labels (anything other than a single lowercase ASCII DNS label) and labels with exactly one or more than two trailing digits both trigger @custom:reverts PopError.
function classifyName(string calldata name)
external
pure
override
returns (PopStatus requirement, string memory message);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | The name label being evaluated. |
Returns
| Name | Type | Description |
|---|---|---|
requirement | PopStatus | Required tier for registration. |
message | string | Explanation of the classification result. |
reserveBaseName
Creates or refreshes a reservation entry for a PopLite-eligible stem.
Commit-reveal reservation path. Only an authorised controller on the registrar may
call this, otherwise @custom:reverts NotRegistry. The caller passes the
already-stripped stem; the contract enforces stem shape (no trailing digits) and
PopLite-eligibility
(length in [6, 8]), and a non-canonical label or a label outside that shape triggers
Note: reverts: PopError. Cross-user collision on a live slot triggers @custom:reverts PopError so the caller cannot silently overwrite another user's reservation; same-user refresh and writes into an empty or expired slot emit @custom:emits BaseNameReserved.
function reserveBaseName(
string calldata stem,
address userAddress
)
external
override
onlyRegistry;
Parameters
| Name | Type | Description |
|---|---|---|
stem | string | The base label with no trailing digits. |
userAddress | address |
isBaseName
Returns whether name is a base name under PoP rules.
A base name has no trailing digits; lite-person labels always have exactly two trailing digits, so the two spaces are disjoint. Non-canonical labels trigger
Note: reverts: PopError.
function isBaseName(string calldata baseName) external pure override returns (bool isBase);
Parameters
| Name | Type | Description |
|---|---|---|
baseName | string |
Returns
| Name | Type | Description |
|---|---|---|
isBase | bool | True when the label has no trailing digits. |
getBaseNameReservation
Retrieves reservation information for a base name.
Raw accessor: returns the stored slot regardless of expiry. Use
Notes:
-
function: isBaseNameReserved when live-window semantics are needed. Non-canonical labels trigger
-
reverts: PopError.
function getBaseNameReservation(string calldata baseName)
external
view
override
returns (address reservationOwner, uint64 expiryTimestamp);
Parameters
| Name | Type | Description |
|---|---|---|
baseName | string | The base label without trailing digits. |
Returns
| Name | Type | Description |
|---|---|---|
reservationOwner | address | owner The address assigned to the reservation. |
expiryTimestamp | uint64 | expires UNIX timestamp when the reservation expires. |
isBaseNameReserved
Indicates whether a base name is currently reserved.
Applies the live-window predicate to the stored slot so an expired reservation reads as free. Non-canonical labels trigger @custom:reverts PopError.
function isBaseNameReserved(string calldata baseName)
external
view
override
returns (bool isReserved, address reservationOwner, uint64 expiryTimestamp);
Parameters
| Name | Type | Description |
|---|---|---|
baseName | string | The base label without trailing digits. |
Returns
| Name | Type | Description |
|---|---|---|
isReserved | bool | reservedStatus True if a live reservation is active. |
reservationOwner | address | owner The reservation holder (zero when not reserved). |
expiryTimestamp | uint64 | expires UNIX timestamp when the reservation expires. |
priceWithCheck
Calculates price with PoP classification and reservation enforcement.
Reverting pricing path used by the commit-reveal controller. Price is the scarcity
curve for the label's base length and is charged to every caller, verified or not;
personhood only unlocks the premium band. Non-canonical
labels, a base stem held live by another user, a governance-reserved label, or a
userAddress whose personhood tier does not meet the label's required tier each
trigger @custom:reverts PopError.
function priceWithCheck(
string calldata name,
address userAddress
)
external
view
override
returns (PriceWithMeta memory metadata);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label. |
userAddress | address | Registering user for the given label. |
Returns
| Name | Type | Description |
|---|---|---|
metadata | PriceWithMeta | Price with PoP requirements and classification. |
priceWithCheckAtVersion
Calculates price at a specific cost-model version with PoP classification and reservation enforcement.
The versioned counterpart of @custom:function priceWithCheck: identical classification,
tier gating, and reservation rules, but the amount comes from the model registered for
pricingVersionValue rather than the current one. The commit-reveal controller prices
a reveal at the version bound into its commitment, so a model change between commit and
reveal does not move the amount. @custom:reverts UnknownVersion when the version was
never registered.
function priceWithCheckAtVersion(
string calldata name,
address userAddress,
uint256 pricingVersionValue
)
external
view
override
returns (PriceWithMeta memory metadata);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label. |
userAddress | address | Registering user for the given label. |
pricingVersionValue | uint256 | Cost-model version to price against. |
Returns
| Name | Type | Description |
|---|---|---|
metadata | PriceWithMeta | Price with PoP requirements and classification. |
priceWithoutCheck
Calculates price with PoP classification and reservation metadata, without reverting on conflicts.
Non-reverting counterpart to priceWithCheck: surfaces the same fields, but reports
a Reserved status through metadata instead of reverting when the base stem is
held by another user. Used by front-ends that need to present a price and eligibility
preview without forcing a transaction attempt. Governance-reserved names are not
rejected here either; the caller decides what to do. Non-canonical labels still
trigger @custom:reverts PopError because the input is malformed rather than just
contested.
function priceWithoutCheck(
string calldata name,
address userAddress
)
external
view
override
returns (PriceWithMeta memory metadata);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label. |
userAddress | address | Registering user for the given label. |
Returns
| Name | Type | Description |
|---|---|---|
metadata | PriceWithMeta | Price with PoP requirements and classification. |
priceWithoutCheckAtVersion
Calculates price at a specific cost-model version with PoP classification and reservation metadata, without reverting on conflicts.
The versioned counterpart of @custom:function priceWithoutCheck: same non-reverting
preview behaviour, but the amount comes from the model registered for
pricingVersionValue. @custom:reverts UnknownVersion when the version was never
registered.
function priceWithoutCheckAtVersion(
string calldata name,
address userAddress,
uint256 pricingVersionValue
)
external
view
override
returns (PriceWithMeta memory metadata);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label. |
userAddress | address | Registering user for the given label. |
pricingVersionValue | uint256 | Cost-model version to price against. |
Returns
| Name | Type | Description |
|---|---|---|
metadata | PriceWithMeta | Price with PoP requirements and classification. |
_priceWithCheck
Shared body for the reservation-enforcing pricing reads.
atVersion selects the amount source: the current model when false, the model for
pricingVersionValue when true. Classification, tier gating, and reservation rules are
the same on both paths, so they live here once.
function _priceWithCheck(
string calldata name,
address userAddress,
bool atVersion,
uint256 pricingVersionValue
)
internal
view
returns (PriceWithMeta memory metadata);
_priceWithoutCheck
Shared body for the non-reverting pricing reads.
Mirror of @custom:function _priceWithCheck for the front-end preview path: reports a
contested reservation through metadata rather than reverting. atVersion selects the
amount source in the same way.
function _priceWithoutCheck(
string calldata name,
address userAddress,
bool atVersion,
uint256 pricingVersionValue
)
internal
view
returns (PriceWithMeta memory metadata);
price
Calculates registration cost for a label.
Prices the label by its base length through the cost model registered under
DotnsConstants.COST_MODEL. Ignores the caller's personhood status and reservation
state. A label whose trailing-digit suffix is neither zero nor exactly two, and any
non-canonical label, trigger @custom:reverts PopError.
function price(string calldata name) external view override returns (uint256);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label to price. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | cost Registration cost in wei. |
pricingVersion
Returns the current cost-model version.
The current version held by the registry under DotnsConstants.COST_MODEL. The
commit-reveal controller binds it into a commitment and prices the reveal at that
version, so a model change between commit and reveal leaves the committed amount
unchanged. @custom:reverts PopError when no registry is configured.
function pricingVersion() external view override returns (uint256 modelVersion);
Returns
| Name | Type | Description |
|---|---|---|
modelVersion | uint256 | Identifier of the current cost model and its parameters. |
transferFloor
Transfer-time floor: the greater of the recipient-reach component and the sender-tier-downgrade component, each priced at the name's own length.
Re-prices the name at its own length on every move: returns the name's curve price when either (i) the recipient does not meet the label's required tier, or (ii) the recipient's personhood tier is strictly below the sender's, and zero when neither holds. Passing a name to a wallet that could never have registered it therefore costs the name's own curve price. The two components overlap on pure tier mismatches, so the function takes their maximum rather than their sum to avoid double-charging. Consumed by @custom:function DotnsRegistrar.quoteTransferFee. Non-canonical labels and labels with exactly one or more than two trailing digits trigger @custom:reverts PopError.
function transferFloor(
string calldata name,
address from,
address to
)
external
view
override
returns (uint256 floor);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label being transferred. |
from | address | Current holder of the name. |
to | address | Incoming holder of the name. |
Returns
| Name | Type | Description |
|---|---|---|
floor | uint256 | Transfer-time floor in wei: the name's own curve price, or zero. |
personhoodOf
Returns the personhood tier recorded for an account.
Reads the account's dotns-scoped tier from the personhood precompile and maps it to a
PopStatus. This is the direct account-tier read; the same tier otherwise surfaces
only as the userStatus field of a pricing query. Never returns Reserved, so the
result is one of NoStatus, PopLite, or PopFull.
function personhoodOf(address account) external view override returns (PopStatus tier);
Parameters
| Name | Type | Description |
|---|---|---|
account | address | Address whose tier is read. |
Returns
| Name | Type | Description |
|---|---|---|
tier | PopStatus | The account's personhood tier. |
_personhoodTier
Reads account's dotns-scoped personhood tier from the alias-accounts
precompile and translates it into a PopStatus.
Single source of truth so callers cannot read the precompile directly and
drift on the status mapping. Tiers are defined incrementally on the
precompile side: 0=None, 1=Lite, 2=Full. Anything outside that range
collapses to NoStatus so a future tier addition fails closed instead of
silently being treated as a higher level than it actually is.
function _personhoodTier(address account) private view returns (PopStatus);
_meetsReach
Single canonical "is userStatus at reach for required?" predicate.
Both priceWithCheck and transferFloor build on this so the tier-eligibility rule
lives in exactly one place and the callers cannot disagree about who clears a given label.
_personhoodTier never returns Reserved, so userStatus is in {NoStatus, PopLite, PopFull} and the enum comparison reflects tier ordering directly. A Reserved required
(governance label) is unreachable by any verified user, so the comparison returns false and
the caller charges the friction fee, providing defence-in-depth if a Reserved label ever
enters circulation.
function _meetsReach(PopStatus required, PopStatus userStatus) private pure returns (bool);
_priceValidatedName
Amount for a base length at the current cost-model version.
The cost-model registry owns the curve; this contract passes it only the base length. The call is a view because it runs on the ERC721 transfer floor read through
Note: function: transferFloor.
function _priceValidatedName(uint256 baseLength) internal view returns (uint256 priceValue);
_priceValidatedNameAtVersion
Amount for a base length at a specific cost-model version.
Prices an in-flight registration at the version it committed to, so a model change between commit and reveal does not move its cost. @custom:reverts UnknownVersion (from the registry) when the version was never registered.
function _priceValidatedNameAtVersion(
uint256 pricingVersionValue,
uint256 baseLength
)
internal
view
returns (uint256 priceValue);
_costModelRegistry
Resolves the cost-model registry registered under DotnsConstants.COST_MODEL.
@custom:reverts PopError when no registry is configured, so a pricing read fails closed rather than resolving through the zero address.
function _costModelRegistry() private view returns (IDotnsCostModelRegistry registry);
_requireShortNamesOpen
Reverts a public paid registration of a base length below nine while the short-name market is closed.
The one gate both public price reads share. Base lengths of nine and above are always
open. @custom:reverts PopError when a base length below nine is priced while
shortNamesEnabled is false. The gateway and @custom:function registerReserved never
reach this, so neither is gated.
function _requireShortNamesOpen(uint256 baseLength) private view;
_validatedBaseLength
Validates the digit suffix and returns the base length that pricing and classification both use to place a name in its band.
A name carries no digit suffix or exactly two digits; any other count triggers
Note: reverts: PopError, so a longer suffix cannot slip a name into a shorter band.
function _validatedBaseLength(string calldata name) internal pure returns (uint256 baseLength);
_enforceReservationRules
Enforces base-name reservation rules.
function _enforceReservationRules(string calldata name, address userAddress) internal view;
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label. |
userAddress | address | Registering user. |
_isLive
Returns whether reservation is live at block.timestamp.
function _isLive(Reservation memory reservation) internal view returns (bool);
_countTrailingDigits
Counts trailing digits in a string.
function _countTrailingDigits(string calldata label)
internal
pure
returns (uint256 digitCount);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | String to analyse. |
Returns
| Name | Type | Description |
|---|---|---|
digitCount | uint256 | Number of trailing digits. |
_stripDigits
Strips trailing digits from a name.
function _stripDigits(string calldata name) internal pure returns (string memory baseName);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label. |
_classifyValidatedName
function _classifyValidatedName(string calldata name)
internal
pure
returns (PopStatus requirement, string memory message, uint256 baseLength);
_requireCanonicalLabel
function _requireCanonicalLabel(string calldata name) internal pure;
supportsInterface
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override
returns (bool supported);
_authorizeUpgrade
Function that should revert when msg.sender is not authorized to upgrade the contract.
Called by
{upgradeToAndCall}.
Normally, this function will use an xref:access.adoc[access control] modifier such as
{Ownable-onlyOwner}.
function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;
version
Returns implementation version.
function version() external pure virtual returns (string memory versionString);
_onlyRegistry
Ensures the caller is any controller authorised on the registrar.
function _onlyRegistry() internal view;
reserveBaseNameForPop
Writes or refreshes a reservation for a bare base-name stem.
Gateway-driven reservation path used by the PoP controller. Only a controller in the
registrar's controllers set may call this, otherwise @custom:reverts NotRegistry.
Does not apply the lite-format length window that @custom:function reserveBaseName
enforces, but does require the input to be canonical and stem-shaped (no trailing
digits); a non-canonical or non-stem label triggers @custom:reverts PopError. If the
slot is already live and held by a different user, @custom:reverts PopError so the
caller's local bookkeeping and PopRules state stay in lockstep; if it is live for the
same user, expiry is refreshed to block.timestamp + MAX_RESERVATION_TIME. Emits
Note: emits: BaseNameReserved on every successful write.
function reserveBaseNameForPop(
string calldata stem,
address userAddress
)
external
override
onlyRegistry;
Parameters
| Name | Type | Description |
|---|---|---|
stem | string | The base label with no trailing digits. |
userAddress | address |
stripDigits
Returns the bare stem of a label, i.e. the label with any trailing ASCII digits removed.
Mirrors the normalisation that @custom:function reserveBaseName applies before writing a reservation, so callers can look up or release a reservation by passing the full label without re-implementing the digit-stripping rule. Non-canonical labels trigger @custom:reverts PopError.
function stripDigits(string calldata name) external pure override returns (string memory stem);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Full label (with or without trailing digits). |
Returns
| Name | Type | Description |
|---|---|---|
stem | string | The label with trailing digits removed. |
releaseBaseName
Clears a reservation for a base-name stem.
Only a controller in the registrar's controllers set may call this, otherwise
Notes:
-
reverts: NotRegistry. Non-canonical or non-stem labels trigger
-
emits: BaseNameReleased once the slot is cleared.
function releaseBaseName(string calldata stem) external override onlyRegistry;
Parameters
| Name | Type | Description |
|---|---|---|
stem | string | The base label whose reservation should be cleared (no trailing digits). |
releaseReservationForReclaim
Clears a reservation when the slot owner matches expectedOwner, allowing any
registrar-authorised controller (not only the stamping one) to release the slot.
Narrower than @custom:function releaseBaseName: callers must prove they know the
slot owner, so cross-controller release is gated on a positive match rather than on
caller identity. Intended for the public registrar controller's reclaim path, where
a prior occupant has handed the name back to escrow and the new registrant needs
the cross-flow guard cleared regardless of which controller originally stamped it.
Only a registrar-authorised controller may call this (@custom:reverts NotRegistry).
Non-canonical or non-stem labels trigger @custom:reverts PopError. A live reservation
whose owner does not match expectedOwner triggers @custom:reverts PopError; expired
reservations are cleared regardless. Emits @custom:emits BaseNameReleased.
function releaseReservationForReclaim(
string calldata stem,
address expectedOwner
)
external
override
onlyRegistry;
Parameters
| Name | Type | Description |
|---|---|---|
stem | string | The base label whose reservation should be cleared (no trailing digits). |
expectedOwner | address | The address the caller expects to be the current reservation owner. |
_writeReservation
Internal single-source-of-truth writer for stem reservations.
Routes both @custom:function reserveBaseName and @custom:function reserveBaseNameForPop
through one path so the cross-user collision semantics stay identical: a live slot held
by a different user @custom:reverts PopError, and any other case writes a fresh expiry
and emits @custom:emits BaseNameReserved. Same-owner re-reservations refresh the expiry
to block.timestamp + MAX_RESERVATION_TIME. Callers are responsible for validating
stem is canonical and stem-shaped (no trailing digits); this helper does no input
validation of its own so each public entry can layer additional eligibility checks.
function _writeReservation(string calldata stem, address userAddress) internal;
Contents
- DotnsPopController
- DotnsPopLens
- DotnsRegistrar
- DotnsRegistrarController
- IDotnsController
- IDotnsPopController
- IDotnsPopLens
- IDotnsRegistrar
- IDotnsRegistrarController
DotnsPopController
Inherits: Initializable, UUPSUpgradeable, OwnableUpgradeable, ERC165Upgradeable, IDotnsPopController
Title: DotnsPopController
Dedicated PoP controller orchestrating lite-person and full-person username issuance on behalf of the PoP gateway pallet.
Lives behind its own UUPS proxy with its own storage. Registered on DotnsRegistrar
via addController, which is how multiple controllers coexist on the same registrar
without interfering with each other.
Enforcement:
Personhood is attested off-chain by the gateway pallet before the call reaches this
contract, so the on-chain personhood precompile is not re-queried on the gateway path.
Every base-label mint path still calls @custom:function IPopRules.classifyName to reject
governance-reserved labels (@custom:reverts InvalidBaseLabel on the base path,
Notes:
-
reverts: InvalidLiteLabel on the lite path). The lite leg accepts any two-digit lite label whose stem is not governance-reserved, regardless of stem length. Native-token pricing is bypassed entirely; the gateway pays no rent. Decoupling: This contract does not import or call
IDotnsRegistrarController. The public commit-reveal controller is equally unaware of this one. Cross-flow collision handling relies on two distinct properties, neither of which requires the two controllers to know about each other: (1) Lite-person labels (NAMEXX) share the public namespace: they are just DNS labels with exactly two trailing digits. First-to-mint wins at the ERC721 layer, so a lite-user and a public registrant cannot hold the same flat label simultaneously. Keeping one namespace removes the ambiguity downstream tooling (dotli, dweb) would see with a separate separator form. (2) Base-name reservations are synchronised intoIPopRules. The head of this controller's reservation queue is written throughIPopRules.reserveBaseNameForPopon every head transition; the slot is cleared throughIPopRules.releaseBaseNamewhen the queue empties (claim, final relinquish, final expiry). The public commit-reveal controller routes throughIPopRules.priceWithCheck, which rejects any registration targeting a base-name stem reserved for another user, so the public flow respects gateway reservations without ever importing this contract. PopRules is the single cross-flow authority; the queue here is the intra-PoP ordering layer on top of it. Shared primitives: labelhash / namehash via @custom:contract LabelUtils; the mint + forward-registry + store-write triad via @custom:contract RegistrationUtils; chat-key and lite-to-full link persistence via -
contract: IDotnsPopResolver. Keeping per-name records on the resolver preserves the "Store = labels only" invariant.
-
security-contact: admin@parity.io
Constants
MAX_RESERVATION_QUEUE
Upper bound for the number of simultaneously queued reservations per label.
Keeps expireReservation gas bounded.
uint16 public constant MAX_RESERVATION_QUEUE = 64
MIN_RESERVATION_DURATION
Minimum value accepted by @custom:function setReservationDuration.
Prevents owner misconfiguration from instantly expiring every live queue and pending-claim entry. The actual production duration is governance-tuned higher.
uint64 public constant MIN_RESERVATION_DURATION = 1 hours
CHAT_KEY_LENGTH
Required byte length for a non-empty chat key.
Mirrors @custom:contract IDotnsPopResolver InvalidChatKeyLength so the controller
can fail closed before the mint instead of bubbling the resolver's revert after partial
state has been committed.
uint256 private constant CHAT_KEY_LENGTH = 65
State Variables
protocolRegistry
Protocol-level address registry for all DotNS contracts.
IDotnsProtocolRegistry public protocolRegistry
_reservationMeta
Per-label queue metadata (head/tail pointers).
mapping(bytes32 labelhash => ReservationQueueMeta meta) internal _reservationMeta
_reservationEntries
Per-label sparse entries keyed by monotonically-increasing index.
mapping(bytes32 labelhash => mapping(uint64 index => ReservationEntry entry)) internal
_reservationEntries
_userReservations
Single per-user pointer into the reservation queues.
Keeps per-user reservation data behind one key and one struct value so callers read both fields in one call instead of two.
mapping(address user => UserReservation reservation) internal _userReservations
_reservedBaseLabel
Remembers the base-label string for each reserved labelhash so the PopRules
sync path can address the reservation by its original string form (PopRules keys its
reservations mapping by string).
Populated on first enqueue for a label, cleared when the queue empties. Exists
only to bridge the queue's bytes32 key space to PopRules' string key space;
nothing else reads it.
mapping(bytes32 labelhash => string baseLabel) internal _reservedBaseLabel
reservationDuration
Duration (in seconds) after which a reservation entry is considered expired.
Mirrors pallet_resources::UsernameReservationDuration. Configurable by
governance via setReservationDuration.
uint64 public override reservationDuration
_pendingClaimUsers
Enumeration set of users holding at least one pending claim.
Membership equals the set of users with a non-empty queue. Used by
pendingClaimUserCount and pendingClaimUsers for paginated enumeration.
EnumerableSet.AddressSet private _pendingClaimUsers
_pendingClaimQueue
Per-user pile of deferred names awaiting a LabelStore.
The Root gateway origin cannot deploy a LabelStore (contract creation is forbidden
from Root), so deferred names accumulate here until a signed-origin
Note:
function: settlePendingClaims deploys the store and writes the stashed labels. Each
entry's deadline is measured from its own mintedAt against reservationDuration.
mapping(address user => PendingClaim[] queue) internal _pendingClaimQueue
__gap
Reserved storage space to allow for layout changes in future upgrades.
uint256[50] private __gap
Functions
onlyRoot
Restricts calls to a substrate Root origin.
modifier onlyRoot() ;
constructor
Note: oz-upgrades-unsafe-allow: constructor
constructor() ;
initialize
Initialises the PoP controller.
Called once through the UUPS proxy; _disableInitializers on the implementation
makes direct calls revert with @custom:reverts InvalidInitialization, and any nested
call outside an active initialiser scope reverts with @custom:reverts NotInitializing.
Emits @custom:emits ReservationDurationSet so indexers observe the initial value
through the same event the setter uses later.
function initialize(
IDotnsProtocolRegistry registry,
uint64 reservationDuration_
)
external
initializer;
reserveLiteName
Registers a lite-person username on behalf of the supplied user without touching the base-name reservation queue.
Callable only under a substrate Root origin (otherwise @custom:reverts NotRoot). The
supplied label must satisfy the dotted stem.NN shape and the flattened label must classify
as PopLite (otherwise @custom:reverts InvalidLiteLabel); a supplied chat
key whose length is neither zero nor CHAT_KEY_LENGTH reverts
Notes:
-
reverts: InvalidChatKey before mint and resolver writes run. On a warm-path mint
-
emits: LiteNameReserved and @custom:emits NameRegistered. On a cold-path mint @custom:emits LiteNameReserved and @custom:emits PendingClaimStashed, with
function reserveLiteName(LiteRegistration calldata params) external override onlyRoot;
Parameters
| Name | Type | Description |
|---|---|---|
params | LiteRegistration | Registration request; see @custom:struct LiteRegistration. |
reserveBaseName
Registers a lite-person username on behalf of the supplied user and optionally enqueues a reservation for a base name they intend to claim as a full person later.
Callable only under a substrate Root origin (otherwise @custom:reverts NotRoot). The
lite leg validates the dotted stem.NN shape and requires the flattened label to classify
as PopLite (otherwise @custom:reverts InvalidLiteLabel), and rejects a
supplied chat key whose length is neither zero nor CHAT_KEY_LENGTH
(otherwise @custom:reverts InvalidChatKey). On a warm-path mint (user already has a
LabelStore) it @custom:emits LiteNameReserved and @custom:emits NameRegistered;
on a cold-path mint it @custom:emits LiteNameReserved and
Notes:
-
emits: PendingClaimStashed, with @custom:emits NameRegistered deferred to
-
function: settlePendingClaims when the claim settles. The base-name leg only runs when
reservedBaseLabelis non-empty: it validates the DNS-label shape and requires a true base label with no trailing digits (otherwise @custom:reverts InvalidBaseLabel) and with no owner on the registrar (otherwise @custom:reverts BaseNameAlreadyRegistered), since a name that already has an owner could never be claimed. This validation runs before both the lite mint and any queue mutation, so an already-registeredreservedBaseLabelaborts the whole call and the candidate receives no lite username either; callers should validate the reserved label before attesting rather than relying on this revert. It then advances the head past expired entries (@custom:emits ReservationExpired for each one), removes the user from any prior queue position so a single user holds at most one live reservation across all labels, and enqueues a fresh entry (@custom:emits ReservationQueued). The enqueue rejects with @custom:reverts AlreadyReserved when the user already holds a reservation that was not cleared by the prior removal and with @custom:reverts QueueFull when the per-label queue has reachedMAX_RESERVATION_QUEUE. Cross-chain callers pass the ABI-encoded reservation tuple as the call's payload, which Solidity decodes directly.
function reserveBaseName(BaseReservation calldata params) external override onlyRoot;
Parameters
| Name | Type | Description |
|---|---|---|
params | BaseReservation | Reservation request; see @custom:struct BaseReservation. |
reserveBaseNameOnly
Enqueues only the full/base-name reservation for a user.
Callable only under a substrate Root origin (otherwise @custom:reverts NotRoot). This is the second step of the split gateway flow: @custom:function reserveLiteName mints the lite username first, then this function reserves the full/base label in a separate transaction so proof-size stays below per-call limits. Reverts with @custom:reverts InvalidBaseLabel when the label is empty, non-canonical, digit-suffixed, or governance-reserved, and with
Note: reverts: BaseNameAlreadyRegistered when the label already has an owner on the registrar and so could never be claimed. The caller remains agnostic about backend batching; it simply exposes a small retryable primitive.
function reserveBaseNameOnly(BaseNameReservation calldata params) external override onlyRoot;
Parameters
| Name | Type | Description |
|---|---|---|
params | BaseNameReservation | Reservation request; see @custom:struct BaseNameReservation. |
_reserveLite
Lite-only mint shared by @custom:function reserveLiteName and the lite leg of @custom:function reserveBaseName.
Gateway attestation is the authority for personhood on this path; the on-chain
precompile is not consulted. The dotted-format check accepts only stem.NN, then
PopRules classification must place the flattened label outside the governance-reserved
tier before minting; any non-reserved two-digit lite label is accepted regardless of stem
length. Takes the @custom:struct LiteRegistration struct directly so both call sites pass
the same payload shape: the typed entrypoint forwards its own params, the
reserveBaseName entrypoint forwards params.lite.
function _reserveLite(IPopRules rules, LiteRegistration calldata params) internal;
registerBaseName
Registers a full-person username on behalf of the supplied user.
Callable only under a substrate Root origin (otherwise @custom:reverts NotRoot). The
base label must satisfy the DNS-label shape and be a true base label with no trailing digits
(otherwise @custom:reverts InvalidBaseLabel), and the label must not
classify as governance-reserved (otherwise @custom:reverts InvalidBaseLabel). The
gateway also defers to PopRules as the single cross-flow authority: when PopRules
carries a live base-name slot held by another user (stamped by the public commit-reveal
flow or this controller's prior queue head), the call reverts @custom:reverts NotHolder
before any queue mutation. Two orthogonal axes drive the state machine. The reservation
axis treats the user as claiming if and only if they hold the live head-of-queue
reservation on the base label: a claim wipes the entire queue, releases the PopRules
slot, and @custom:emits BaseNameClaimed; a non-claim silently relinquishes any
pending entry the user holds and @custom:emits StandaloneNameRegistered. Advancing
the queue head past expired entries @custom:emits ReservationExpired for each
one. The chat-key axis selects whether a fresh key is persisted on the resolver or the
new entry inherits its key from a prior lite-person username. The fresh-key branch
rejects a chat key whose length is neither zero nor CHAT_KEY_LENGTH (otherwise
Notes:
-
reverts: InvalidChatKey). The
LiteUsernamebranch validates the lite label'sNAMEXXshape (otherwise @custom:reverts InvalidLiteLabel), requires the registrant to own the lite token (otherwise @custom:reverts LiteLabelNotOwnedByUser), reads the lite node's chat key from the resolver and copies it across; if the lite node carries no chat key the inherited value is empty and the full node's chat-key write is silently skipped (theLiteToFullLinkedevent still fires). @custom:emits LiteToFullLinked alongside the registration event. On a warm-path mint the event order is -
emits: NameRegistered first (from the inner mint), then
function registerBaseName(FullRegistration calldata params) external override onlyRoot;
Parameters
| Name | Type | Description |
|---|---|---|
params | FullRegistration | Registration request; see @custom:struct FullRegistration. |
expireReservation
Permissionlessly removes expired entries from the head of a reservation queue.
Permissionless on purpose: anyone (typically a UI or a bot) can poke a stale queue
so the next live head takes over without waiting for the next gateway call. Validates
the DNS-label shape of reservedBaseLabel (otherwise @custom:reverts InvalidBaseLabel)
and @custom:emits ReservationExpired for every expired entry reaped from the
head. Only base-shaped labels (no trailing digits) ever key a reservation queue, so a
lite-shaped label still passes the shape check but resolves to an empty queue and the
call is a no-op.
function expireReservation(string calldata reservedBaseLabel) external override;
relinquishReservation
Lets the caller voluntarily drop their own active reservation.
Reverts with @custom:reverts NoActiveReservation when the caller holds no live reservation. On success the caller's entry is removed from its queue and
Note: emits: ReservationRelinquished is emitted; if the removed entry was the queue head, head advancement may additionally @custom:emits ReservationExpired for any stale entries reaped behind it.
function relinquishReservation() external override;
claimLabelStore
Settles the caller's own pending claims into their LabelStore.
Convenience for a user settling their own store: equivalent to
Notes:
-
function: settlePendingClaims with
msg.senderand a bounded batch. The caller deploys and pays for their store on the first write. Settles at most one bounded batch so the call cannot exceed the block gas limit;moreRemainingreports whether the caller still holds unsettled entries, in which case they call again. Emits the same -
emits: PendingClaimSettled and @custom:emits NameRegistered as
function claimLabelStore() external override returns (bool moreRemaining);
Returns
| Name | Type | Description |
|---|---|---|
moreRemaining | bool | Whether the caller still holds unsettled entries. |
settlePendingClaims
Settles up to limit of a user's pending claims, writing each stashed label into
the user's LabelStore and deploying that store when the user has none yet.
Permissionless: any caller may settle any user's claims and bears the full cost,
including the LabelStore storage deposit, which pallet-revive charges to the
transaction signer. Settlement is never destructive: the name is already minted, so this
only completes the deferred label write. Each settled entry is removed from the queue and
the user leaves the pending-claim enumeration set once their queue empties. At most
limit entries are processed so a large queue cannot exceed the block gas limit;
moreRemaining reports whether entries are left for a follow-up call, and a limit of
zero settles nothing. Writes are idempotent on an already-locked store slot, so a claim
whose label was independently written settles harmlessly. Emits
Note:
emits: PendingClaimSettled and @custom:emits NameRegistered per settled entry, with
settledBy set to the caller so a third-party settlement is distinguishable from a
self-settlement.
function settlePendingClaims(
address user,
uint256 limit
)
external
override
returns (uint256 settledCount, bool moreRemaining);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Account whose pending claims are settled. |
limit | uint256 | Maximum number of entries to settle in this call. |
Returns
| Name | Type | Description |
|---|---|---|
settledCount | uint256 | Number of entries settled. |
moreRemaining | bool | Whether the user still holds unsettled entries. |
_settlePending
Shared settlement loop behind @custom:function claimLabelStore and
Settles up to limit of the user's pending claims, deploying the store on the first
write, and removes the user from the enumeration set once their queue empties.
Note: function: settlePendingClaims.
function _settlePending(
address user,
uint256 limit
)
internal
returns (uint256 settledCount, bool moreRemaining);
_settlePendingLabel
Writes a single pending label into the user's store, deploying the store lazily.
The store is created only when there is a label to write, so a caller who settles an empty queue never leaves a fresh store behind with nothing in it. Returns the (possibly newly deployed) store so the caller threads it through the remaining entries.
function _settlePendingLabel(
IStoreFactory factory,
address store,
address user,
string memory label
)
internal
returns (address);
isReservedForClaim
Returns whether a label currently has a live reservation at the queue head.
Validates the DNS-label shape of reservedBaseLabel (otherwise
Note: reverts: InvalidBaseLabel) before inspecting the queue.
function isReservedForClaim(string calldata reservedBaseLabel)
external
view
override
returns (bool reserved, address holder);
setReservationDuration
Updates the reservation duration used to decide when queue entries expire.
Owner-gated (otherwise @custom:reverts OwnableUnauthorizedAccount); emits
Note: emits: ReservationDurationSet on success.
function setReservationDuration(uint64 duration) external override onlyOwner;
reservationMeta
Returns the queue metadata (head, tail) for labelhash.
Read-only accessor over the per-label reservation queue. head == tail means
the queue is empty; active entries occupy [head, tail). Exposed on the interface
because invariant tests and off-chain consumers (dotli, dweb) use it to enumerate
live queue state without scanning storage.
function reservationMeta(bytes32 labelhash)
external
view
override
returns (uint64 head, uint64 tail);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | Keccak-256 of the base label whose queue is being read. |
Returns
| Name | Type | Description |
|---|---|---|
head | uint64 | Index of the live queue head. |
tail | uint64 | Index one past the last queued entry. |
reservationEntry
Returns the queue entry at index for labelhash.
Sparse storage: a zero entryOwner means the slot was relinquished, expired and
reaped, or never written. Callers pair this with @custom:function reservationMeta to walk
the live window [head, tail).
function reservationEntry(
bytes32 labelhash,
uint64 index
)
external
view
override
returns (address entryOwner, uint64 joinedAt);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | Keccak-256 of the base label whose queue is being read. |
index | uint64 | Queue index to look up. |
Returns
| Name | Type | Description |
|---|---|---|
entryOwner | address | Owner of the slot (zero if empty/relinquished). |
joinedAt | uint64 | Timestamp the entry was enqueued (only meaningful when entryOwner != address(0)). |
userReservation
Returns user's current reservation pointer.
A zero labelhash on the returned struct means the user holds no reservation;
index is meaningful only when labelhash is non-zero.
function userReservation(address user)
external
view
override
returns (UserReservation memory reservation);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Account whose reservation pointer is being read. |
Returns
| Name | Type | Description |
|---|---|---|
reservation | UserReservation | Per-user reservation pointer; see @custom:struct UserReservation. |
pendingClaims
Returns a paginated slice of a user's pending claims in queue order.
An empty array means the user has no pending claims at offset. Each entry carries
its mintedAt; the settlement deadline is mintedAt + reservationDuration. An offset
past the end returns an empty array rather than reverting, and a page holds at most
DotnsConstants.MAX_PAGE_SIZE entries.
function pendingClaims(
address user,
uint256 offset,
uint256 limit
)
external
view
override
returns (PendingClaim[] memory claims);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Account whose pending claims are read. |
offset | uint256 | Start index into the queue. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
claims | PendingClaim[] | Page of the user's pending claims; see @custom:struct PendingClaim. |
pendingClaimCountOf
Returns the number of pending claims currently staged for user.
function pendingClaimCountOf(address user) external view override returns (uint256 count);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Account whose pending claims are counted. |
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Number of staged pending claims. |
pendingClaimUserCount
Returns the number of users with at least one live pending claim.
Exact live count, not an all-time tally: fully settled users are removed from the enumeration set so off-chain consumers can page through every stalled user without filtering.
function pendingClaimUserCount() external view override returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Number of users currently holding a pending claim. |
pendingClaimUsers
Returns a paginated slice of users with at least one live pending claim.
Pair with @custom:function pendingClaims to read each user's stashed entries.
Ordering is not chronological; callers MUST NOT assume mintedAt is monotonic
across the slice. Returns an empty array when offset is past the live count, and a page
holds at most DotnsConstants.MAX_PAGE_SIZE entries.
function pendingClaimUsers(
uint256 offset,
uint256 limit
)
external
view
override
returns (address[] memory users);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
users | address[] | Slice of users currently holding a pending claim. |
reservedBaseLabelOf
Returns the base label a reservation queue is keyed under.
Reverse lookup from the bytes32 queue key to its label string, so a consumer that
observed a queue by labelhash (for example from a reservation event) can recover the
human-readable label without holding its preimage. Returns an empty string when no
reservation was ever enqueued under labelhash.
function reservedBaseLabelOf(bytes32 labelhash)
external
view
override
returns (string memory baseLabel);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | Keccak-256 of the base label. |
Returns
| Name | Type | Description |
|---|---|---|
baseLabel | string | The base label string, or empty when unknown. |
supportsInterface
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC165Upgradeable, IERC165)
returns (bool);
version
Returns implementation version.
function version() external pure virtual returns (string memory versionString);
Returns
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
_completeGatewayRegistration
Mints a name, wires forward registry, persists PoP-flow records (chat key,
lite link) on the PoP resolver, and either writes the label into the owner's
existing LabelStore or stashes a pending claim when the owner has none yet.
The mint + forward-registry pair is delegated to
Notes:
-
function: RegistrationUtils.registerAndStore so this flow and the public commit-reveal flow share exactly one implementation of that sequence. The label is passed empty so the registrar does not deploy a
LabelStore; substrate Root cannot run theLabelStoreconstructor underpallet-revive. PoP-flow per-name records (chat key, lite link) are persisted eagerly on @custom:contract IDotnsPopResolver here, before the label is written, so the resolver carries the full identity record from mint time regardless of whether the owner already has aLabelStore. The Store stays labels-only. Warm path emits @custom:emits NameRegistered immediately; the cold path emits @custom:emits PendingClaimStashed at mint and defers -
emits: NameRegistered to @custom:function settlePendingClaims when the claim settles.
function _completeGatewayRegistration(
address user,
string memory label,
bytes32 labelhash,
bytes32 node,
bytes memory chatKeyBytes,
bytes32 liteLabelhash
)
internal;
_writeRecord
Writes a name's label into store.
Single canonical persistence step shared by the warm gateway path and
Note:
function: settlePendingClaims. The store key is node, matching
the registrar's _writeOwnerLabel convention. Idempotent on already-locked slots so a
user whose store was pre-populated under the same node (e.g. by a sibling protocol
flow) can still settle their pending claim without bricking on LabelAlreadyExists.
function _writeRecord(address store, bytes32 node, string memory label) internal;
Parameters
| Name | Type | Description |
|---|---|---|
store | address | Owner's LabelStore proxy. |
node | bytes32 | namehash(labelhash) for the entry. |
label | string | Bare DNS label (no TLD); the TLD is appended on write. |
_stashPendingClaim
Appends a deferred binding for user and adds them to the enumeration set.
The Root gateway origin cannot deploy the user's LabelStore, so deferred names pile
up in _pendingClaimQueue until a signed-origin @custom:function settlePendingClaims
writes them. Adding the user to the set is idempotent, so repeat stashes keep a single
enumeration entry. Emits @custom:emits PendingClaimStashed.
function _stashPendingClaim(address user, string memory label, bytes32 labelhash) internal;
_isExpired
Returns whether a queue entry is expired relative to block.timestamp.
function _isExpired(uint64 joinedAt) internal view returns (bool);
_enqueueReservation
Appends a new reservation entry to the tail of the queue for labelhash.
Reverts if the queue is full or the user already holds a reservation. When the
enqueued entry is the new head of an empty queue, the controller also reserves the
base name on PopRules so the public commit-reveal flow sees the reservation through
its existing priceWithCheck guard. Subsequent waiters only live in the local queue
until they are promoted.
function _enqueueReservation(
IPopRules rules,
bytes32 labelhash,
string memory baseLabel,
address user
)
internal;
_clearQueue
Wipes the entire reservation queue for labelhash and releases the
corresponding PopRules reservation.
Used when a holder claims their reservation: every waiter is evicted and their per-user tracking state is cleared, and PopRules is told the slot is free so future public registrations are unblocked (the claim itself just minted the name, so there is nothing left to reserve).
function _clearQueue(bytes32 labelhash) internal;
_advanceExpiredHead
Advances the queue head past every expired entry at the head of the queue.
Reset semantics matter: when the queue empties (head catches tail), the meta slot is deleted AND the PopRules base-name slot is released, so the public commit-reveal flow can register the label again. When a new live head emerges, PopRules is re-synced to that head so reservations cannot be paid around by another address. Emits
Note: emits: ReservationExpired once per expired entry reaped from the head.
function _advanceExpiredHead(bytes32 labelhash) internal;
_removeUserFromQueue
Removes user from whichever reservation queue they currently occupy.
For a head removal, we delete the entry without bumping meta.head and delegate
the advance to _advanceExpiredHead. Its existing zero-owner skip walks past the
freshly-deleted slot, and its head != meta.head branch fires the PopRules resync
in the one place head promotion is actually handled. Non-head removals leave the
queue shape intact, so no advance or resync is needed.
function _removeUserFromQueue(address user) internal;
_validateLiteLabel
Validates a lite-person NAMEXX label and derives (labelhash, node).
function _validateLiteLabel(string memory liteLabel)
internal
view
returns (bytes32 labelhash, bytes32 node);
_validateBaseLabel
Validates a base (full-person) DNS label and derives (labelhash, node).
function _validateBaseLabel(string calldata baseLabel)
internal
view
returns (bytes32 labelhash, bytes32 node);
_validateReservableBaseLabel
Validates a base label as reservable and returns its hashes.
Shared by both reservation entrypoints so the guard cannot drift between them. Runs
three checks and reverts on the first failure, before any reservation state is mutated: the
label must classify outside the governance-reserved tier and be a base name, be a canonical
single label, and have no owner on the registrar. The last check is the fix for a
reservation queued over an already-registered name: the queue keys by stem, so such a
reservation could never be redeemed yet would lock every two-digit variant of the stem for
the full reservation window. exists (owner set) mirrors exactly what makes the eventual
claim's mint revert, so a label that passes here is one a claim can still register.
function _validateReservableBaseLabel(
IPopRules rules,
string calldata baseLabel
)
internal
view
returns (bytes32 labelhash, bytes32 node);
_requireValidChatKey
Reverts when a non-empty chat key is not exactly CHAT_KEY_LENGTH bytes.
Mirrors the resolver's own length gate so the gateway sees a controller-local
InvalidChatKey revert before any mint state is written.
function _requireValidChatKey(bytes memory chatKey) internal pure;
_popResolver
Resolves the PoP resolver via the protocol registry.
function _popResolver() internal view returns (IDotnsPopResolver);
_popRules
Resolves the PopRules contract via the protocol registry.
function _popRules() internal view returns (IPopRules);
_storeFactory
Resolves the Store factory via the protocol registry.
function _storeFactory() internal view returns (IStoreFactory);
_registrar
Resolves the registrar via the protocol registry.
function _registrar() internal view returns (IDotnsRegistrar);
_syncPopRulesToHead
Writes the new head of the queue into PopRules so the public commit-reveal flow
rejects registrations of this base name for anyone other than newHead.
Callers guarantee newHead is non-zero (the queue holds a live entry) and that
_reservedBaseLabel[labelhash] is non-empty (any non-empty queue had its first head
write the slot). The release-then-reserve pair satisfies PopRules' ownership gate on
reserveBaseNameForPop.
function _syncPopRulesToHead(bytes32 labelhash, address newHead) internal;
_releasePopRulesSlot
Clears the PopRules slot and the local label bookkeeping when the queue empties (claim, last-relinquish, last-expire).
function _releasePopRulesSlot(bytes32 labelhash) internal;
_onlyRoot
Internal check enforcing a substrate Root origin.
Authorises a call when @custom:function SystemUtils.originIsRoot is true, and
reverts with NotRoot otherwise. msg.sender is deliberately not consulted: a
Root origin has no account behind it, so reading msg.sender traps. The same
applies to anything reachable from an onlyRoot entrypoint.
function _onlyRoot() internal view;
_authorizeUpgrade
Function that should revert when msg.sender is not authorized to upgrade the contract.
Called by
{upgradeToAndCall}.
Normally, this function will use an xref:access.adoc[access control] modifier such as
{Ownable-onlyOwner}.
function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;
DotnsPopLens
Inherits: IDotnsPopLens
Title: DotnsPopLens
Read-only view over PoP identity data.
Stateless beyond the protocol registry it holds, and never mints or settles. It composes
each field from the contract that owns it: names from the owner's LabelStore and the
controller's pending queue, ownership from the registrar, chat keys and links from the PoP
resolver, and label classification from PopRules. Living outside the controller keeps the
controller within the contract-size limit and keeps the registrar the single source of
ownership truth. Deployed as a plain contract through the CREATE3 factory, so its address is
deterministic and it can be redeployed on a read change without touching stored state.
Note: security-contact: admin@parity.io
Constants
_protocolRegistry
Protocol-level address registry used to resolve every sibling contract.
IDotnsProtocolRegistry internal immutable _protocolRegistry
Functions
constructor
Binds the lens to the protocol registry it reads through.
constructor(IDotnsProtocolRegistry registry) ;
Parameters
| Name | Type | Description |
|---|---|---|
registry | IDotnsProtocolRegistry | Protocol registry resolving the controller, registrar, store factory, PoP resolver, and PopRules. |
protocolRegistry
The protocol registry the lens resolves siblings through.
function protocolRegistry() external view override returns (address registry);
Returns
| Name | Type | Description |
|---|---|---|
registry | address | The protocol registry address. |
liteNamesOf
Lists the lite-person names currently owned by user.
Reads the user's LabelStore labels and pending claims, keeps the lite-person
shaped ones, and re-checks each against registrar.ownerOf so a name transferred away
drops out and a name transferred in shows under its current owner. Ordering follows the
store then the pending queue. An offset past the end returns an empty array rather than
reverting, and a short return means the slice ended. A gateway name transferred before it
settles has its label in no store, so it cannot appear here and is reachable only by node
via @custom:function nameDetailByNode. Gas grows with the account's holdings, so call it
off-chain. A page holds at most DotnsConstants.MAX_PAGE_SIZE entries, and the pending
portion covers up to that many staged claims.
function liteNamesOf(
address user,
uint256 offset,
uint256 limit
)
external
view
override
returns (Name[] memory names);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Account whose lite names are listed. |
offset | uint256 | Start index into the filtered sequence. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
names | Name[] | Page of the account's lite names; see @custom:struct Name. |
fullNamesOf
Lists the full-person names currently owned by user.
Same ownership-verified read as @custom:function liteNamesOf, keeping base-shaped labels instead of lite-shaped ones.
function fullNamesOf(
address user,
uint256 offset,
uint256 limit
)
external
view
override
returns (Name[] memory names);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Account whose full names are listed. |
offset | uint256 | Start index into the filtered sequence. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
names | Name[] | Page of the account's full names; see @custom:struct Name. |
liteNameCountOf
Counts the lite-person names currently owned by user.
Uses the same ownership-verified read as @custom:function liteNamesOf; counting scans the account's holdings, so gas grows with them. Call it off-chain.
function liteNameCountOf(address user) external view override returns (uint256 count);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Account whose lite names are counted. |
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Number of lite names currently owned. |
fullNameCountOf
Counts the full-person names currently owned by user.
Uses the same ownership-verified read as @custom:function fullNamesOf; counting scans the account's holdings, so gas grows with them. Call it off-chain.
function fullNameCountOf(address user) external view override returns (uint256 count);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Account whose full names are counted. |
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Number of full names currently owned. |
nameDetail
Returns the full on-chain record for a name given its label string.
Resolves the node internally, so a caller holding only the string needs no namehash
implementation. Never reverts on an unknown name: absent fields read as zero or empty.
This overload can populate fullClaim because it holds the label and so its labelhash.
function nameDetail(string calldata name) external view override returns (NameDetail memory);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Bare DNS label (no TLD). |
Returns
| Name | Type | Description |
|---|---|---|
<none> | NameDetail | detail The name's record; see @custom:struct NameDetail. |
nameDetailByNode
Returns the full on-chain record for a name given its node.
The node cannot be inverted to its labelhash, so fullClaim is populated only when
the label is independently resolvable from the node and reads zero otherwise; every other
field is resolved directly. Never reverts on an unknown node.
function nameDetailByNode(bytes32 node) external view override returns (NameDetail memory);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | namehash of the name. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | NameDetail | detail The name's record; see @custom:struct NameDetail. |
profileOf
Returns an account-level summary of a user's PoP state.
O(1) facts only; lite and full name counts are read separately via
Note: function: liteNameCountOf and @custom:function fullNameCountOf because those scan the account's holdings. Never reverts.
function profileOf(address user) external view override returns (PopProfile memory profile);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Account being summarised. |
Returns
| Name | Type | Description |
|---|---|---|
profile | PopProfile | The account summary; see @custom:struct PopProfile. |
_matchesShape
Whether label belongs in the lite listing (wantLite) or the full listing.
A lite-person label is a single label with two trailing digits; a full-person label is any other single label. The two sets are disjoint and together cover every single label, so one predicate drives both listings.
function _matchesShape(string memory label, bool wantLite) internal pure returns (bool);
_countNames
Counts the names currently owned by user that match the requested shape.
Walks the user's LabelStore (settled names) then their pending claims, keeping only
shape matches still owned by user on the registrar. A pending entry already written into
the store by a sibling flow is skipped so it is not counted twice.
function _countNames(address user, bool wantLite) internal view returns (uint256 count);
_pageNames
Returns a page of user's owned names matching the requested shape.
Same ownership-verified walk as @custom:function _countNames, in the same order
(store then pending), skipping the first offset matches and returning up to limit
entries. limit is clamped to DotnsConstants.MAX_PAGE_SIZE to bound the memory and the
scan.
function _pageNames(
address user,
uint256 offset,
uint256 limit,
bool wantLite
)
internal
view
returns (Name[] memory names);
_ownedBy
Whether node is a minted name currently owned by user.
Guards the ownerOf call with exists so a missing token returns false rather than
reverting, keeping the listing reads total.
function _ownedBy(
IDotnsRegistrar registrar,
bytes32 node,
address user
)
internal
view
returns (bool);
_detail
Gathers a name's record from the registrar, PoP resolver, and PopRules.
Reads defensively so an unminted or unsettled name yields zeroed fields instead of
reverting. fullClaim is left for the caller because it needs the labelhash, which is
recoverable from the label string but not from the node alone. tier classifies the
label shape and is skipped for an empty label.
function _detail(bytes32 node) internal view returns (NameDetail memory detail);
_pendingClaims
Reads a bounded page of user's pending claims from the controller.
The listings scan this page in memory; it holds up to DotnsConstants.MAX_PAGE_SIZE
staged claims, which the reads document as their pending-portion bound.
function _pendingClaims(address user)
internal
view
returns (IDotnsPopController.PendingClaim[] memory claims);
_controller
Resolves the PoP controller via the protocol registry.
function _controller() internal view returns (IDotnsPopController);
_registrar
Resolves the registrar via the protocol registry.
function _registrar() internal view returns (IDotnsRegistrar);
_storeFactory
Resolves the store factory via the protocol registry.
function _storeFactory() internal view returns (IStoreFactory);
_popResolver
Resolves the PoP resolver via the protocol registry.
function _popResolver() internal view returns (IDotnsPopResolver);
_popRules
Resolves the PopRules contract via the protocol registry.
function _popRules() internal view returns (IPopRules);
DotnsRegistrar
Inherits: Initializable, UUPSUpgradeable, OwnableUpgradeable, ERC721Upgradeable, IDotnsRegistrar
Title: Dotns Registrar
ERC721-backed registrar implementing permanent name ownership.
Deliberately policy-free. Transfers are supported to allow ownership changes without
registry hooks, and the registrar itself does not encode pricing, reservations, or PoP
gating; those live in the controllers and @custom:contract IPopRules. The fee-on-transfer hook
in _update is a thin enforcement layer that consults the escrow.
Note: security-contact: admin@parity.io
State Variables
controllers
Mapping of authorised controllers.
Controllers may call register. Keyed by the shared baseline @custom:contract
IDotnsController interface so the registrar doesn't depend on any specific controller shape.
Commit-reveal, PoP, and future controllers coexist here so long as they implement the
baseline interface.
Note: oz-retyped-from: mapping(IDotnsRegistrarController => bool)
mapping(IDotnsController controller => bool exists) public controllers
protocolRegistry
Protocol-level address registry for all DotNS contracts.
Used to resolve sibling contract addresses (store factory, controller, registry) without storing individual references.
IDotnsProtocolRegistry public protocolRegistry
__gap
Reserved storage space to allow for layout changes in the future.
uint256[50] private __gap
Functions
onlyController
Restricts function access to authorised controllers.
modifier onlyController() ;
constructor
Note: oz-upgrades-unsafe-allow: constructor
constructor() ;
initialize
Initialises the registrar.
Uses OpenZeppelin upgradeable initialisers and is callable once through the UUPS
proxy; direct calls on the implementation revert with @custom:reverts InvalidInitialization
because _disableInitializers runs in the constructor, and any nested call outside an
active initialiser scope reverts with @custom:reverts NotInitializing.
function initialize(
string calldata name,
string calldata symbol,
IDotnsProtocolRegistry registry
)
external
initializer;
addController
Adds an authorised controller.
Typed against the baseline IDotnsController (not a concrete subtype) so a single
authorisation surface accepts every controller flavour (commit-reveal, PoP gateway,
future variants) without per-flavour setters. Owner-gated (otherwise
Note: reverts: OwnableUnauthorizedAccount); emits @custom:emits ControllerAdded on success.
function addController(IDotnsController controller) external onlyOwner;
removeController
Removes an authorised controller.
Mirrors the @custom:function addController baseline-typed signature so any registered controller can be revoked through the same entry point. Owner-gated (otherwise
Note: reverts: OwnableUnauthorizedAccount); emits @custom:emits ControllerRemoved on success.
function removeController(IDotnsController controller) external onlyOwner;
available
Returns whether a registration call may proceed for id.
Signals two distinct paths to the controller. Returns true when the owner slot is
empty (a fresh @custom:function register call may mint) OR when the current owner is
the configured escrow and the released position's redeem window has elapsed (the
controller must then route through @custom:function IDotnsNameEscrow.reclaim instead of
Note:
function: register, because register calls _mint which rejects existing
tokens). All other holders return false. The controller distinguishes the two true
cases via @custom:function exists.
Escrow custody inside the redeem window returns false: that window belongs to the
previous holder, who may still @custom:function IDotnsNameEscrow.redeem the name, and
reclaim would revert until it elapses. Clients wanting the exact moment a released name
becomes registrable should read redeemableUntil from
function available(uint256 id) public view override returns (bool isAvailable);
register
Registers a name permanently.
Permanence is by construction: there is no expire, renew, or release path on
the registrar. Custody only moves via ERC721 transfers (which the registrar polices via
the fee-on-transfer hook) or via escrow reclaim. Restricted to authorised controllers
(otherwise @custom:reverts NotController) and rejects ids that are not available
(otherwise @custom:reverts NameNotAvailable). Emits @custom:emits NameRegistered on
success.
function register(
uint256 id,
address owner,
string calldata label
)
external
override
onlyController;
Parameters
| Name | Type | Description |
|---|---|---|
id | uint256 | |
owner | address | |
label | string | The human-readable label string (e.g. "alice"). |
labelOf
Returns the human-readable label a token was registered with.
Canonical state source for the label string; any client that holds a node or tokenId can resolve the original label in one view call without scanning registration events. Returns the empty string when the token does not exist.
function labelOf(uint256 tokenId) external view override returns (string memory);
quoteTransferFee
Quotes the additional native fee required to transfer a token to to.
Returns the fee from @custom:function PopRules.transferFloor: the name's own price
as the maximum of (i) the reach component charged when the recipient does not meet
the label's required tier and (ii) the downgrade component charged when the
recipient tier is strictly below the sender tier. Self-transfers and
escrow-touching transfers (release into escrow, reclaim out of escrow) return
zero. A token whose sender has no stored label also returns zero because there
is no label-derived price to charge against; this covers gateway-cold PoP mints
(the controller passes an empty label to @custom:function register so substrate
Root does not have to deploy a LabelStore) until the user settles via
Notes:
-
function: IDotnsPopController.claimLabelStore. Because settlement writes the label into the original claimant's store, a transfer that happens before settlement leaves the recipient with no label entry and the zero-fee branch persists for that token under all future holders. A token registered with no label that is moved off-chain prior to settlement therefore carries no PoP-tier transfer friction. Off-chain consumers integrating PoP mints should treat
-
reverts: ERC721InvalidReceiver, an unminted
tokenIdwith
function quoteTransferFee(
uint256 tokenId,
address to
)
external
view
override
returns (uint256 requiredFee);
transferFrom
Subject to the same fee-on-transfer gate as the safe overloads; reverts with
Note:
reverts: TransferFeeRequired when the recipient owes a non-zero transfer fee and
the caller has not forwarded it as msg.value.
function transferFrom(
address from,
address to,
uint256 tokenId
)
public
payable
override(ERC721Upgradeable, IDotnsRegistrar);
safeTransferFrom
Subject to the same fee-on-transfer gate as the four-argument overload; reverts with
Note:
reverts: TransferFeeRequired when the recipient owes a non-zero transfer fee and
the caller has not forwarded it as msg.value.
function safeTransferFrom(
address from,
address to,
uint256 tokenId
)
public
payable
override(ERC721Upgradeable, IDotnsRegistrar);
safeTransferFrom
The registrar's _update hook consults @custom:function PopRules.transferFloor
to compute the required transfer fee; if the caller does not forward at least that
amount as msg.value, the transfer reverts with @custom:reverts TransferFeeRequired.
The payable modifier on every transfer overload exists so the fee can be forwarded
in the same call.
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes memory data
)
public
payable
override(ERC721Upgradeable, IDotnsRegistrar);
version
Returns implementation version.
function version() external pure virtual returns (string memory versionString);
Returns
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
exists
Returns whether a given token id has been minted.
function exists(uint256 tokenId) external view override returns (bool tokenExists);
_exists
Checks whether a token ID exists.
function _exists(uint256 tokenId) internal view returns (bool);
_onlyController
Internal function to check for controller access.
function _onlyController() internal view;
_update
Transfers tokenId from its current owner to to, or alternatively mints (or burns) if
the current owner
(or to) is the zero address. Returns the owner of the tokenId before the update.
The auth argument is optional. If the value passed is non 0, then this function will check
that
auth is either the owner of the token, or approved to operate on the token (by the owner).
Emits a {Transfer} event.
NOTE: If overriding this function in a way that tracks balances, see also {_increaseBalance}.
function _update(
address to,
uint256 tokenId,
address auth
)
internal
override
returns (address from);
_syncRecipientStore
Mirrors the sender's label entry into the recipient's LabelStore.
function _syncRecipientStore(
IStoreFactory factory,
address to,
address from,
uint256 tokenId
)
internal;
_readLabelFor
Reads the full name (label.tld) for tokenId from holder's LabelStore using
a caller-supplied factory.
function _readLabelFor(
IStoreFactory factory,
uint256 tokenId,
address holder
)
private
view
returns (string memory fullName);
_readLabel
Reads the full name for tokenId from holder's LabelStore via fresh lookups.
Used by external view functions where caching the factory is not yet established; the hot transfer path uses @custom:function _readLabelFor with a cached factory.
function _readLabel(
uint256 tokenId,
address holder
)
private
view
returns (string memory fullName);
_escrow
Resolves the configured name escrow address from the protocol registry.
function _escrow() private view returns (address escrow);
_popRules
Resolves the configured PoP rules contract from the protocol registry.
function _popRules() private view returns (IPopRules rules);
_storeFactory
Resolves the configured store factory from the protocol registry.
function _storeFactory() private view returns (IStoreFactory factory);
_writeOwnerLabel
Writes the canonical full name into owner's LabelStore keyed by
bytes32(tokenId).
Caller (@custom:function register) is responsible for short-circuiting on empty label; the factory is a protocol-critical dependency and is assumed non-zero (a zero return from the registry would have already broken every other call site).
function _writeOwnerLabel(address owner, uint256 tokenId, string calldata label) private;
_quoteTransferFee
Quotes the friction fee required for a transfer.
Required fee is the name's own price returned by @custom:function PopRules.transferFloor. It is paid by the sender on every downward or cross-reach transfer and settles to the protocol fee pot. Any prior deposit travels with the NFT: the escrow rebinds the position to the new holder rather than refunding the sender, so transferring a funded name forfeits the locked deposit to the recipient. Self-transfers and escrow-touching transfers return zero.
function _quoteTransferFee(
address from,
address to,
uint256 tokenId
)
private
view
returns (address escrow, uint256 transferFee, uint256 requiredFee);
_quoteTransferFeeFor
Quotes the transfer floor reusing a caller-cached registry and store factory.
Hot-path variant used by @custom:function _update. Returns (0, 0) for any
escrow-touching move or when the sender holds no label entry; otherwise reads the canonical
label and delegates to @custom:function PopRules.transferFloor.
function _quoteTransferFeeFor(
IDotnsProtocolRegistry registry,
IStoreFactory factory,
bool isEscrowTouching,
address from,
address to,
uint256 tokenId
)
private
view
returns (uint256 transferFee, uint256 requiredFee);
_authorizeUpgrade
Function that should revert when msg.sender is not authorized to upgrade the contract.
Called by
{upgradeToAndCall}.
Normally, this function will use an xref:access.adoc[access control] modifier such as
{Ownable-onlyOwner}.
function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;
DotnsRegistrarController
Inherits: Initializable, UUPSUpgradeable, DotnsRoleManager, ReentrancyGuardTransient, IDotnsRegistrarController
Title: Dotns Registrar Controller
Allocates top-level labels using a commit reveal scheme.
Orchestrates allocation, PoP validation, pricing enforcement, forward registry
wiring, default reverse resolution, and immutable store writing.
Tokenisation: the minted ERC721 tokenId is uint256(node), where
node = namehash(tldNode, labelhash). The registry stores a sentinel owner
(address(0)) for tokenised nodes and derives ownership from the ERC721 registrar for
authorisation.
Note: security-contact: admin@parity.io
Constants
MAX_ALLOWED_COMMITMENT_AGE
Upper bound for commitment validity to cap storage griefing risk.
uint256 public constant MAX_ALLOWED_COMMITMENT_AGE = 7 days
State Variables
minCommitmentAge
Minimum age a commitment must reach before reveal.
uint256 public minCommitmentAge
maxCommitmentAge
Maximum age after which a commitment expires.
uint256 public maxCommitmentAge
commitments
Stores Mapping of commitment hashes to timestamp committed.
mapping(bytes32 hash => uint256 timestamp) public commitments
committedPricingVersion
Cost-model version stamped on a commitment at commit time.
Recorded from the registry's current version when commit runs, so the reveal can bind
a registration to the version that was current then. A caller cannot commit against an
arbitrary earlier, cheaper version: the reveal rejects a pricingVersion that differs
from this stamp.
mapping(bytes32 hash => uint256 version) public committedPricingVersion
whiteList
Whitelist for addresses allowed to call registerReserved.
mapping(address user => bool isWhiteListed) public whiteList
protocolRegistry
Protocol-level address registry for all DotNS contracts.
IDotnsProtocolRegistry public protocolRegistry
__gap
Reserved storage space to allow for layout changes in the future.
uint256[49] private __gap
Functions
onlyWhiteListedOrOwner
Restricts calls to whitelisted addresses or the owner.
Used to gate registerReserved, which allows registering reserved names without
PoP checks or payment. Necessary so the owner (or a whitelisted operator) can seed
reserved names on behalf of users who are already known and verified and do not need
PoP checks.
modifier onlyWhiteListedOrOwner() ;
onlyWhitelistOperatorOrOwner
modifier onlyWhitelistOperatorOrOwner() ;
constructor
Note: oz-upgrades-unsafe-allow: constructor
constructor() ;
initialize
Initialises the registrar controller.
Callable once through the UUPS proxy; direct calls on the implementation revert
with @custom:reverts InvalidInitialization, and any nested call outside an active
initialiser scope reverts with @custom:reverts NotInitializing. Validates the
commitment window bounds: minAge must be strictly positive (otherwise
Notes:
-
reverts: MinCommitmentAgeZero) so a reveal cannot land in the same block as its commit;
maxAgemust exceedminAge(otherwise -
reverts: MaxCommitmentAgeTooLow) and must stay within
MAX_ALLOWED_COMMITMENT_AGE(otherwise @custom:reverts MaxCommitmentAgeTooHigh) before wiring the protocol registry.
function initialize(
IDotnsProtocolRegistry registry,
uint256 minAge,
uint256 maxAge
)
external
initializer;
available
Returns whether a label is available for registration.
Validates the canonical DNS-label shape (otherwise @custom:reverts InvalidLabel) and rejects labels below the minimum-length policy with
Note: reverts: LabelTooShort before checking ERC721 availability on the registrar.
function available(string calldata label) public view override returns (bool);
makeCommitment
Computes the commitment hash for a registration.
Uses abi.encode so the variable-width label is length-prefixed and the boundary
between label and the fixed-width owner, secret, reserved, maxPrice, and
pricingVersion fields is unambiguous, binding the commitment to the exact tuple. The
price ceiling and cost-model version are part of that tuple, so neither can be altered
between commit and reveal.
function makeCommitment(Registration calldata registration)
public
pure
override
returns (bytes32 commitment);
commit
Submits a commitment for a future registration.
Idempotent over expiry: re-committing an unexpired hash reverts with
Note:
reverts: UnexpiredCommitmentExists (front-running guard); a hash whose stored
timestamp has passed maxCommitmentAge overwrites the slot so storage cannot be
permanently griefed. The expiry boundary is inclusive on the commit side
(committedAt + maxCommitmentAge <= block.timestamp overwrites) and exclusive on the
reveal side (register rejects at the same instant with @custom:reverts
CommitmentTooOld), so the slot is overwritable from exactly the timestamp at which
reveal begins rejecting it. Stamps the cost model's current version on the commitment, so
the reveal binds to the version live now and rejects a pricingVersion bound to an earlier
one with @custom:reverts PricingVersionMismatch. Emits @custom:emits NameCommitted on
success.
function commit(bytes32 commitment) external override;
_currentPricingVersion
Reads the cost model's current version through the protocol registry.
Resolved at commit time so the stamp binds the version live then, not at reveal.
function _currentPricingVersion() internal view returns (uint256 pricingVersion);
Returns
| Name | Type | Description |
|---|---|---|
pricingVersion | uint256 | The current cost-model version. |
register
Registers a name after the commitment delay.
Validates the label shape (otherwise @custom:reverts InvalidLabel), rejects labels
below the minimum length policy (@custom:reverts LabelTooShort), and ERC721 availability
(otherwise @custom:reverts NameNotAvailable), then consumes the prior commitment, which
fails with @custom:reverts CommitmentNotFound when no commitment exists for the supplied
registration, @custom:reverts CommitmentTooNew before minCommitmentAge, and
Note:
reverts: CommitmentTooOld past maxCommitmentAge, and finally resolves the
configured escrow address from the protocol registry (otherwise
function register(Registration calldata registration) external payable override nonReentrant;
_settleEscrow
Settles every escrow side-effect of a successful registration.
Extracted to keep register under the stack-depth ceiling. On a direct
registration the full chargeAmount lands in the refundable deposit position
keyed to nameOwner. On a cross-payer registration the deposit position is
seeded with a zero amount so the release lifecycle stays reachable, and the same
chargeAmount routes to the protocol fee pot via depositProtocolFee keyed to
msg.sender as the payer.
function _settleEscrow(
address escrow,
uint256 tokenId,
address nameOwner,
bool isDirect,
uint256 chargeAmount
)
internal;
isWhiteListed
Checks if the given address is whitelisted to call registerReserved.
function isWhiteListed(address who) external view override returns (bool);
whiteListAddress
Adds or removes an address from the whitelist for registerReserved.
Callable by the owner or an account holding DotnsConstants.WHITELIST_OPERATOR_ROLE;
any other caller reverts with @custom:reverts IDotnsRoleManager.NotRoleOrOwner. Emits
Note: emits: WhiteListed on success.
function whiteListAddress(
address who,
bool whiteListStatus
)
external
override
onlyWhitelistOperatorOrOwner;
registerReserved
Registers a name after the commitment delay.
Whitelisted issuance path used to seed reserved labels at zero base cost: skips the PoP price check and the escrow deposit, but reuses the same commit-reveal pipeline so the same anti-front-running guarantees apply. Restricted to whitelisted callers and the owner (otherwise @custom:reverts NotWhiteListedOrOwner). Validates the label shape (otherwise @custom:reverts InvalidLabel) and ERC721 availability (otherwise
Notes:
-
reverts: NameNotAvailable), then consumes the prior commitment, which fails with
-
emits: NameRegistered on success.
function registerReserved(Registration calldata registration)
external
override
onlyWhiteListedOrOwner
nonReentrant;
supportsInterface
Returns true if this contract implements the interface defined by
interfaceId. See the corresponding
https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[ERC section]
to learn more about how these ids are created.
This function call must use less than 30 000 gas.
function supportsInterface(bytes4 interfaceId)
public
view
override(DotnsRoleManager, IERC165)
returns (bool);
_validatedLabelNode
Validates label shape and derives (labelhash, node).
Delegates hashing to @custom:contract LabelUtils so the assembly sequence lives in
exactly one place across the codebase. Error ownership stays on this interface: shape
violations revert with InvalidLabel(); labels below the minimum length revert with
LabelTooShort(label) so off-chain consumers can distinguish "shape-valid but below
the policy minimum" from "shape-valid but already minted".
function _validatedLabelNode(string calldata label)
internal
view
returns (bytes32 labelhash, bytes32 node);
_requireAvailableLabel
function _requireAvailableLabel(string calldata label)
internal
view
returns (IDotnsRegistrar registrar, bytes32 labelhash, bytes32 node);
_consumeCommitment
function _consumeCommitment(Registration calldata registration) internal;
_completeRegistration
Completes a commit-reveal registration: mints (or skips when reclaiming), wires forward registry, optionally sets the reverse record, and writes the owner's Store.
On a fresh mint the triad of mint + forward-registry + store-write is delegated to @custom:function RegistrationUtils.registerAndStore, the single canonical implementation shared across every DotNS registration flow. On a reclaim the mint step is skipped (the escrow has already moved custody) and only the registry wiring and store write run. Reverse-record setting and the priced-registration event stay here because they are commit-reveal-specific policy.
function _completeRegistration(
Registration calldata registration,
bytes32 labelhash,
bytes32 node,
uint256 baseCost,
bool setReverseRecord,
IDotnsReverseResolver reverse,
bool isReclaim
)
internal;
_escrow
Returns the configured name escrow from the protocol registry.
function _escrow() internal view returns (address escrow);
version
Returns implementation version.
function version() external pure virtual returns (string memory versionString);
Returns
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
_onlyWhiteListedOrOwner
Internal check enforcing whitelist-or-owner access.
function _onlyWhiteListedOrOwner() internal view;
_isSupportedRole
function _isSupportedRole(bytes32 role) internal view override returns (bool supported);
_authorizeUpgrade
Function that should revert when msg.sender is not authorized to upgrade the contract.
Called by
{upgradeToAndCall}.
Normally, this function will use an xref:access.adoc[access control] modifier such as
{Ownable-onlyOwner}.
function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;
IDotnsController
Inherits: IERC165
Title: IDotnsController
Baseline interface implemented by every controller authorised on DotnsRegistrar.
Marker interface that types DotnsRegistrar.controllers so every authorised caller
(public commit-reveal, PoP gateway, any future privileged flow) fits the same mapping and
the same addController / removeController signatures without forcing a common call
surface. Extending @custom:contract IERC165 lets the registrar (or any observer) runtime-check
which concrete controller interface a given address implements.
Note: security-contact: admin@parity.io
IDotnsPopController
Inherits: IDotnsController
Title: IDotnsPopController
Interface for the dedicated PoP controller orchestrating lite-person and full-person username issuance on behalf of the PoP gateway pallet.
Deliberately disjoint from @custom:contract IDotnsRegistrarController. The two
controllers coexist on @custom:contract DotnsRegistrar via its multi-controller affordance
and neither imports the other. Collision handling reduces to the registrar's ERC721
availability check (first-to-mint wins). Reservation queuing for reservedBaseLabel is
an intra-PoP coordination mechanism only; it does not block public registrations.
Label formats:
Lite-person usernames (first argument to @custom:function reserveBaseName and the
liteLabel of a LinkKind.LiteUsername link) are DNS labels with exactly two
trailing digits (e.g. alice42) per @custom:function StringUtils.isLitePersonLabel.
The gateway strips any separator before calling so the on-chain label is flat.
Full-person usernames (the label of @custom:function registerBaseName and the
optional reservedBaseLabel of @custom:function reserveBaseName) follow the
DNS-label rules enforced by @custom:function StringUtils.isSingleLabel (e.g.
alice). Lite and public registrations share one namespace; first-to-mint wins at
the ERC721 layer. Cross-flow priority on the stripped base stem is arbitrated by
Notes:
-
function: IPopRules.reserveBaseNameForPop.
-
security-contact: admin@parity.io
Functions
reserveBaseName
Registers a lite-person username on behalf of the supplied user and optionally enqueues a reservation for a base name they intend to claim as a full person later.
Callable only under a substrate Root origin (otherwise @custom:reverts NotRoot). The
lite leg validates the dotted stem.NN shape and requires the flattened label to classify
as PopLite (otherwise @custom:reverts InvalidLiteLabel), and rejects a
supplied chat key whose length is neither zero nor CHAT_KEY_LENGTH
(otherwise @custom:reverts InvalidChatKey). On a warm-path mint (user already has a
LabelStore) it @custom:emits LiteNameReserved and @custom:emits NameRegistered;
on a cold-path mint it @custom:emits LiteNameReserved and
Notes:
-
emits: PendingClaimStashed, with @custom:emits NameRegistered deferred to
-
function: settlePendingClaims when the claim settles. The base-name leg only runs when
reservedBaseLabelis non-empty: it validates the DNS-label shape and requires a true base label with no trailing digits (otherwise @custom:reverts InvalidBaseLabel) and with no owner on the registrar (otherwise @custom:reverts BaseNameAlreadyRegistered), since a name that already has an owner could never be claimed. This validation runs before both the lite mint and any queue mutation, so an already-registeredreservedBaseLabelaborts the whole call and the candidate receives no lite username either; callers should validate the reserved label before attesting rather than relying on this revert. It then advances the head past expired entries (@custom:emits ReservationExpired for each one), removes the user from any prior queue position so a single user holds at most one live reservation across all labels, and enqueues a fresh entry (@custom:emits ReservationQueued). The enqueue rejects with @custom:reverts AlreadyReserved when the user already holds a reservation that was not cleared by the prior removal and with @custom:reverts QueueFull when the per-label queue has reachedMAX_RESERVATION_QUEUE. Cross-chain callers pass the ABI-encoded reservation tuple as the call's payload, which Solidity decodes directly.
function reserveBaseName(BaseReservation calldata params) external;
Parameters
| Name | Type | Description |
|---|---|---|
params | BaseReservation | Reservation request; see @custom:struct BaseReservation. |
reserveBaseNameOnly
Enqueues only the full/base-name reservation for a user.
Callable only under a substrate Root origin (otherwise @custom:reverts NotRoot). This is the second step of the split gateway flow: @custom:function reserveLiteName mints the lite username first, then this function reserves the full/base label in a separate transaction so proof-size stays below per-call limits. Reverts with @custom:reverts InvalidBaseLabel when the label is empty, non-canonical, digit-suffixed, or governance-reserved, and with
Note: reverts: BaseNameAlreadyRegistered when the label already has an owner on the registrar and so could never be claimed. The caller remains agnostic about backend batching; it simply exposes a small retryable primitive.
function reserveBaseNameOnly(BaseNameReservation calldata params) external;
Parameters
| Name | Type | Description |
|---|---|---|
params | BaseNameReservation | Reservation request; see @custom:struct BaseNameReservation. |
reserveLiteName
Registers a lite-person username on behalf of the supplied user without touching the base-name reservation queue.
Callable only under a substrate Root origin (otherwise @custom:reverts NotRoot). The
supplied label must satisfy the dotted stem.NN shape and the flattened label must classify
as PopLite (otherwise @custom:reverts InvalidLiteLabel); a supplied chat
key whose length is neither zero nor CHAT_KEY_LENGTH reverts
Notes:
-
reverts: InvalidChatKey before mint and resolver writes run. On a warm-path mint
-
emits: LiteNameReserved and @custom:emits NameRegistered. On a cold-path mint @custom:emits LiteNameReserved and @custom:emits PendingClaimStashed, with
-
emits: NameRegistered deferred to @custom:function settlePendingClaims when the claim settles. Cross-chain callers pass the ABI-encoded lite-registration tuple as the call's payload, which Solidity decodes directly.
function reserveLiteName(LiteRegistration calldata params) external;
Parameters
| Name | Type | Description |
|---|---|---|
params | LiteRegistration | Registration request; see @custom:struct LiteRegistration. |
registerBaseName
Registers a full-person username on behalf of the supplied user.
Callable only under a substrate Root origin (otherwise @custom:reverts NotRoot). The
base label must satisfy the DNS-label shape and be a true base label with no trailing digits
(otherwise @custom:reverts InvalidBaseLabel), and the label must not
classify as governance-reserved (otherwise @custom:reverts InvalidBaseLabel). The
gateway also defers to PopRules as the single cross-flow authority: when PopRules
carries a live base-name slot held by another user (stamped by the public commit-reveal
flow or this controller's prior queue head), the call reverts @custom:reverts NotHolder
before any queue mutation. Two orthogonal axes drive the state machine. The reservation
axis treats the user as claiming if and only if they hold the live head-of-queue
reservation on the base label: a claim wipes the entire queue, releases the PopRules
slot, and @custom:emits BaseNameClaimed; a non-claim silently relinquishes any
pending entry the user holds and @custom:emits StandaloneNameRegistered. Advancing
the queue head past expired entries @custom:emits ReservationExpired for each
one. The chat-key axis selects whether a fresh key is persisted on the resolver or the
new entry inherits its key from a prior lite-person username. The fresh-key branch
rejects a chat key whose length is neither zero nor CHAT_KEY_LENGTH (otherwise
Notes:
-
reverts: InvalidChatKey). The
LiteUsernamebranch validates the lite label'sNAMEXXshape (otherwise @custom:reverts InvalidLiteLabel), requires the registrant to own the lite token (otherwise @custom:reverts LiteLabelNotOwnedByUser), reads the lite node's chat key from the resolver and copies it across; if the lite node carries no chat key the inherited value is empty and the full node's chat-key write is silently skipped (theLiteToFullLinkedevent still fires). @custom:emits LiteToFullLinked alongside the registration event. On a warm-path mint the event order is -
emits: NameRegistered first (from the inner mint), then
-
emits: BaseNameClaimed or @custom:emits StandaloneNameRegistered, then
-
emits: LiteToFullLinked when applicable. On a cold-path mint
-
emits: PendingClaimStashed replaces the initial @custom:emits NameRegistered; the deferred @custom:emits NameRegistered fires later from @custom:function settlePendingClaims. Cross-chain callers pass the ABI-encoded full-registration tuple as the call's payload, which Solidity decodes directly.
function registerBaseName(FullRegistration calldata params) external;
Parameters
| Name | Type | Description |
|---|---|---|
params | FullRegistration | Registration request; see @custom:struct FullRegistration. |
expireReservation
Permissionlessly removes expired entries from the head of a reservation queue.
Permissionless on purpose: anyone (typically a UI or a bot) can poke a stale queue
so the next live head takes over without waiting for the next gateway call. Validates
the DNS-label shape of reservedBaseLabel (otherwise @custom:reverts InvalidBaseLabel)
and @custom:emits ReservationExpired for every expired entry reaped from the
head. Only base-shaped labels (no trailing digits) ever key a reservation queue, so a
lite-shaped label still passes the shape check but resolves to an empty queue and the
call is a no-op.
function expireReservation(string calldata reservedBaseLabel) external;
relinquishReservation
Lets the caller voluntarily drop their own active reservation.
Reverts with @custom:reverts NoActiveReservation when the caller holds no live reservation. On success the caller's entry is removed from its queue and
Note: emits: ReservationRelinquished is emitted; if the removed entry was the queue head, head advancement may additionally @custom:emits ReservationExpired for any stale entries reaped behind it.
function relinquishReservation() external;
isReservedForClaim
Returns whether a label currently has a live reservation at the queue head.
Validates the DNS-label shape of reservedBaseLabel (otherwise
Note: reverts: InvalidBaseLabel) before inspecting the queue.
function isReservedForClaim(string calldata reservedBaseLabel)
external
view
returns (bool reserved, address holder);
setReservationDuration
Updates the reservation duration used to decide when queue entries expire.
Owner-gated (otherwise @custom:reverts OwnableUnauthorizedAccount); emits
Note: emits: ReservationDurationSet on success.
function setReservationDuration(uint64 duration) external;
reservationMeta
Returns the queue metadata (head, tail) for labelhash.
Read-only accessor over the per-label reservation queue. head == tail means
the queue is empty; active entries occupy [head, tail). Exposed on the interface
because invariant tests and off-chain consumers (dotli, dweb) use it to enumerate
live queue state without scanning storage.
function reservationMeta(bytes32 labelhash) external view returns (uint64 head, uint64 tail);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | Keccak-256 of the base label whose queue is being read. |
Returns
| Name | Type | Description |
|---|---|---|
head | uint64 | Index of the live queue head. |
tail | uint64 | Index one past the last queued entry. |
reservationEntry
Returns the queue entry at index for labelhash.
Sparse storage: a zero entryOwner means the slot was relinquished, expired and
reaped, or never written. Callers pair this with @custom:function reservationMeta to walk
the live window [head, tail).
function reservationEntry(
bytes32 labelhash,
uint64 index
)
external
view
returns (address entryOwner, uint64 joinedAt);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | Keccak-256 of the base label whose queue is being read. |
index | uint64 | Queue index to look up. |
Returns
| Name | Type | Description |
|---|---|---|
entryOwner | address | Owner of the slot (zero if empty/relinquished). |
joinedAt | uint64 | Timestamp the entry was enqueued (only meaningful when entryOwner != address(0)). |
userReservation
Returns user's current reservation pointer.
A zero labelhash on the returned struct means the user holds no reservation;
index is meaningful only when labelhash is non-zero.
function userReservation(address user)
external
view
returns (UserReservation memory reservation);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Account whose reservation pointer is being read. |
Returns
| Name | Type | Description |
|---|---|---|
reservation | UserReservation | Per-user reservation pointer; see @custom:struct UserReservation. |
reservedBaseLabelOf
Returns the base label a reservation queue is keyed under.
Reverse lookup from the bytes32 queue key to its label string, so a consumer that
observed a queue by labelhash (for example from a reservation event) can recover the
human-readable label without holding its preimage. Returns an empty string when no
reservation was ever enqueued under labelhash.
function reservedBaseLabelOf(bytes32 labelhash) external view returns (string memory baseLabel);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | Keccak-256 of the base label. |
Returns
| Name | Type | Description |
|---|---|---|
baseLabel | string | The base label string, or empty when unknown. |
reservationDuration
Returns the window, in seconds, after which a queue or pending-claim entry lapses.
Governance-configurable via @custom:function setReservationDuration. Read by the lens to compute each pending claim's settlement deadline.
function reservationDuration() external view returns (uint64 duration);
Returns
| Name | Type | Description |
|---|---|---|
duration | uint64 | Reservation duration in seconds. |
settlePendingClaims
Settles up to limit of a user's pending claims, writing each stashed label into
the user's LabelStore and deploying that store when the user has none yet.
Permissionless: any caller may settle any user's claims and bears the full cost,
including the LabelStore storage deposit, which pallet-revive charges to the
transaction signer. Settlement is never destructive: the name is already minted, so this
only completes the deferred label write. Each settled entry is removed from the queue and
the user leaves the pending-claim enumeration set once their queue empties. At most
limit entries are processed so a large queue cannot exceed the block gas limit;
moreRemaining reports whether entries are left for a follow-up call, and a limit of
zero settles nothing. Writes are idempotent on an already-locked store slot, so a claim
whose label was independently written settles harmlessly. Emits
Note:
emits: PendingClaimSettled and @custom:emits NameRegistered per settled entry, with
settledBy set to the caller so a third-party settlement is distinguishable from a
self-settlement.
function settlePendingClaims(
address user,
uint256 limit
)
external
returns (uint256 settledCount, bool moreRemaining);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Account whose pending claims are settled. |
limit | uint256 | Maximum number of entries to settle in this call. |
Returns
| Name | Type | Description |
|---|---|---|
settledCount | uint256 | Number of entries settled. |
moreRemaining | bool | Whether the user still holds unsettled entries. |
claimLabelStore
Settles the caller's own pending claims into their LabelStore.
Convenience for a user settling their own store: equivalent to
Notes:
-
function: settlePendingClaims with
msg.senderand a bounded batch. The caller deploys and pays for their store on the first write. Settles at most one bounded batch so the call cannot exceed the block gas limit;moreRemainingreports whether the caller still holds unsettled entries, in which case they call again. Emits the same -
emits: PendingClaimSettled and @custom:emits NameRegistered as
-
function: settlePendingClaims.
function claimLabelStore() external returns (bool moreRemaining);
Returns
| Name | Type | Description |
|---|---|---|
moreRemaining | bool | Whether the caller still holds unsettled entries. |
pendingClaims
Returns a paginated slice of a user's pending claims in queue order.
An empty array means the user has no pending claims at offset. Each entry carries
its mintedAt; the settlement deadline is mintedAt + reservationDuration. An offset
past the end returns an empty array rather than reverting, and a page holds at most
DotnsConstants.MAX_PAGE_SIZE entries.
function pendingClaims(
address user,
uint256 offset,
uint256 limit
)
external
view
returns (PendingClaim[] memory claims);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Account whose pending claims are read. |
offset | uint256 | Start index into the queue. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
claims | PendingClaim[] | Page of the user's pending claims; see @custom:struct PendingClaim. |
pendingClaimCountOf
Returns the number of pending claims currently staged for user.
function pendingClaimCountOf(address user) external view returns (uint256 count);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Account whose pending claims are counted. |
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Number of staged pending claims. |
pendingClaimUserCount
Returns the number of users with at least one live pending claim.
Exact live count, not an all-time tally: fully settled users are removed from the enumeration set so off-chain consumers can page through every stalled user without filtering.
function pendingClaimUserCount() external view returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Number of users currently holding a pending claim. |
pendingClaimUsers
Returns a paginated slice of users with at least one live pending claim.
Pair with @custom:function pendingClaims to read each user's stashed entries.
Ordering is not chronological; callers MUST NOT assume mintedAt is monotonic
across the slice. Returns an empty array when offset is past the live count, and a page
holds at most DotnsConstants.MAX_PAGE_SIZE entries.
function pendingClaimUsers(
uint256 offset,
uint256 limit
)
external
view
returns (address[] memory users);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
users | address[] | Slice of users currently holding a pending claim. |
Events
LiteNameReserved
Emitted when a lite-person username is registered via the PoP gateway.
event LiteNameReserved(bytes32 indexed labelhash, address indexed user, string label);
BaseNameClaimed
Emitted when a full-person username is claimed out of an existing reservation.
event BaseNameClaimed(bytes32 indexed labelhash, address indexed user, string label);
StandaloneNameRegistered
Emitted when a standalone full-person username is registered via the PoP gateway.
event StandaloneNameRegistered(bytes32 indexed labelhash, address indexed user, string label);
ReservationQueued
Emitted when a reservation entry is added to the queue for a base name.
event ReservationQueued(
bytes32 indexed reservedLabelhash, address indexed user, uint64 position
);
Parameters
| Name | Type | Description |
|---|---|---|
reservedLabelhash | bytes32 | |
user | address | |
position | uint64 | Position in the queue at the time of joining (0 = active holder). |
ReservationExpired
Emitted when a reservation entry is removed due to expiry.
event ReservationExpired(bytes32 indexed reservedLabelhash, address indexed user);
ReservationRelinquished
Emitted when a user voluntarily relinquishes their reservation.
event ReservationRelinquished(bytes32 indexed reservedLabelhash, address indexed user);
LiteToFullLinked
Emitted when a full-person username is linked to a lite-person username.
event LiteToFullLinked(bytes32 indexed fullLabelhash, bytes32 indexed liteLabelhash);
ReservationDurationSet
Emitted when the reservation duration is updated.
event ReservationDurationSet(uint64 duration);
NameRegistered
Emitted when a name is successfully registered via the PoP controller.
event NameRegistered(
string indexed label, bytes32 indexed labelhash, address indexed owner, address store
);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | |
labelhash | bytes32 | |
owner | address | |
store | address | The Store instance used to persist the immutable registration record. |
PendingClaimStashed
Emitted when a gateway-path mint defers its LabelStore write into the
pending-claim mapping because the user has no store yet.
event PendingClaimStashed(address indexed user, bytes32 indexed labelhash, string label);
PendingClaimSettled
Emitted when a pending claim is written into a LabelStore.
Fires once per settled entry from @custom:function settlePendingClaims. settledBy
is the caller: it equals user for a self-settlement and is any other address for a
third-party settlement, so consumers can tell the two apart from the log alone.
event PendingClaimSettled(
address indexed user, bytes32 indexed labelhash, address store, address indexed settledBy
);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Account the settled name belongs to. |
labelhash | bytes32 | Labelhash of the settled name. |
store | address | The LabelStore the label was written into. |
settledBy | address | Caller that performed and paid for the settlement. |
ReservationHeadAdvanced
Emitted when a reservation queue's head transitions to a new user, either via expiry of the prior head or via the explicit relinquish path.
event ReservationHeadAdvanced(bytes32 indexed labelhash, address indexed newHead);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | Base-label hash whose queue head changed. |
newHead | address | Address now holding the head slot. |
Errors
NotRoot
Thrown when a gated entrypoint is reached without a substrate Root origin.
Carries no caller parameter: a Root origin has no account to report,
and reading msg.sender under one traps.
error NotRoot();
InvalidLiteLabel
Thrown when a supplied lite-person label does not match NAMEXX.
error InvalidLiteLabel();
InvalidBaseLabel
Thrown when a supplied base label is not a canonical DNS label.
error InvalidBaseLabel();
BaseNameAlreadyRegistered
Thrown when a reserved base label already has an owner on the registrar, so the queued reservation could never be redeemed at mint time.
error BaseNameAlreadyRegistered();
InvalidChatKey
Thrown when a supplied chat key is non-empty and not exactly 65 bytes long.
Mirrors the resolver's InvalidChatKeyLength so the controller surfaces a
controller-local error before the mint runs.
error InvalidChatKey(uint256 length);
Parameters
| Name | Type | Description |
|---|---|---|
length | uint256 | Caller-supplied chat key length, in bytes. |
NoActiveReservation
Thrown when a user tries to claim or relinquish a reservation that they do not hold.
error NoActiveReservation(address user);
QueueFull
Thrown when a reservation queue has reached its capacity.
error QueueFull(bytes32 labelhash);
AlreadyReserved
Thrown when attempting to enqueue a user who already has an active reservation.
error AlreadyReserved(address user, bytes32 labelhash);
NotHolder
Thrown when someone tries to mint a base label in standalone mode while another user holds the live head-of-queue reservation.
error NotHolder(address user, bytes32 labelhash);
LiteLabelNotOwnedByUser
Thrown when a lite-link inheritance does not match the registrar-side owner of the lite label.
Prevents identity hijack by ensuring the registrant on the full-name leg actually holds the prior lite identity whose chat key is being inherited.
error LiteLabelNotOwnedByUser(address user, bytes32 liteLabelhash);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Registrant supplied by the gateway. |
liteLabelhash | bytes32 | Lite label whose ownership did not match. |
ReservationDurationTooLow
Thrown when @custom:function setReservationDuration is called with a value below the protocol minimum.
error ReservationDurationTooLow(uint64 duration);
Parameters
| Name | Type | Description |
|---|---|---|
duration | uint64 | Caller-supplied duration, in seconds. |
Structs
Link
Tagged union selecting the chat-key source for a full-person registration.
struct Link {
LinkKind kind;
string liteLabel;
bytes chatKey;
}
Properties
| Name | Type | Description |
|---|---|---|
kind | LinkKind | |
liteLabel | string | Lite-person NAMEXX label (only read when kind == LiteUsername). |
chatKey | bytes | Chat key bytes (only read when kind == None). |
UserReservation
Per-user reservation pointer: which queue the user sits in and where.
struct UserReservation {
bytes32 labelhash;
uint64 index;
}
Properties
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | Non-zero when the user holds a live reservation; zero otherwise. |
index | uint64 | Monotonic queue index, meaningful only when labelhash is non-zero. |
ReservationEntry
Reservation queue entry: a user and the timestamp they joined the queue.
Packs into a single storage slot (20 + 8 bytes).
struct ReservationEntry {
address owner;
uint64 joinedAt;
}
ReservationQueueMeta
Metadata describing the occupied range of a reservation queue.
Uses monotonically increasing indices. Active entries occupy [head, tail);
length = tail - head. Slots past head are deleted as the head advances so
garbage never accumulates.
struct ReservationQueueMeta {
uint64 head;
uint64 tail;
}
PendingClaim
Deferred per-user binding of a freshly minted name to its LabelStore.
Recorded by the gateway path when the user has no LabelStore. The binding later
settles via @custom:function settlePendingClaims, which deploys the store from a signed
origin and writes the stashed label. PoP-resolver records (chat key, lite link) are
persisted eagerly at mint time on @custom:contract IDotnsPopResolver, not at settlement,
so the resolver carries the full identity record regardless of whether the user has
settled their Store. A user accumulates one entry per deferred name: the Root gateway path
cannot deploy a LabelStore (contract creation is forbidden from the Root origin), so it
keeps stashing entries until a signed-origin @custom:function settlePendingClaims deploys
the store and settles the entries. Each entry's deadline is measured from its own
mintedAt against reservationDuration.
struct PendingClaim {
string label;
uint64 mintedAt;
}
Properties
| Name | Type | Description |
|---|---|---|
label | string | Bare DNS label (no TLD); the TLD is appended at settlement time. |
mintedAt | uint64 | Timestamp of the originating mint. |
LiteRegistration
Lite-person registration payload.
Single struct so the gateway can ABI-encode one tuple as the cross-chain payload
and the contract decodes it directly out of msg.data. All fields are required;
chatKey may be empty bytes to skip the resolver write.
struct LiteRegistration {
string liteLabel;
address user;
bytes chatKey;
}
Properties
| Name | Type | Description |
|---|---|---|
liteLabel | string | Lite-person NAMEXX label being minted. |
user | address | Beneficiary account on this chain. |
chatKey | bytes | Chat-key bytes persisted on the PoP resolver. Empty leaves the slot unset. |
BaseReservation
Lite-person registration combined with an optional base-name reservation.
BaseReservation is a @custom:struct LiteRegistration plus a base-label reservation
slot, expressed as composition rather than duplicated fields so internal helpers can
consume the lite leg via params.lite without unpacking. The lite leg always runs;
the reservation leg only runs when reservedBaseLabel is non-empty.
struct BaseReservation {
LiteRegistration lite;
string reservedBaseLabel;
}
Properties
| Name | Type | Description |
|---|---|---|
lite | LiteRegistration | Lite-person registration request; see LiteRegistration. |
reservedBaseLabel | string | Base label to enqueue for a later full-person claim. Empty string skips the reservation leg. |
BaseNameReservation
Base-name reservation payload for the split gateway flow.
This is the reservation-only primitive. The lite username mint is handled by
Notes:
-
function: reserveLiteName, and LabelStore settlement is handled by
-
function: settlePendingClaims.
struct BaseNameReservation {
address user;
string reservedBaseLabel;
}
Properties
| Name | Type | Description |
|---|---|---|
user | address | Beneficiary account that will hold the reservation. |
reservedBaseLabel | string | Base label to enqueue for a later full-person claim. |
FullRegistration
Full-person registration payload.
struct FullRegistration {
string label;
address user;
Link link;
}
Properties
| Name | Type | Description |
|---|---|---|
label | string | Base DNS label being minted. |
user | address | Beneficiary account on this chain. |
link | Link | Chat-key source for the new entry; see @custom:struct Link. |
Enums
LinkKind
Discriminant for the Link union supplied to registerBaseName.
Selects the chat-key source for the full-person username. Orthogonal to whether
the registration is a claim or standalone; that is derived from on-chain reservation
state. None means the caller supplies a fresh chat key in link.chatKey.
LiteUsername means the full-person username is linked to a prior lite-person
username (link.liteLabel) and inherits its chat key.
enum LinkKind {
None,
LiteUsername
}
IDotnsPopLens
Title: IDotnsPopLens
Read-only view over PoP identity data, composed from the controller, the registrar, the store factory, the PoP resolver, and PopRules.
Holds no state of its own beyond the protocol registry it resolves siblings through, and takes no part in issuance. It exists so the query surface lives outside the controller, which keeps the controller within the contract-size limit and keeps ownership on the registrar.
Note: security-contact: admin@parity.io
Functions
protocolRegistry
The protocol registry the lens resolves siblings through.
function protocolRegistry() external view returns (address registry);
Returns
| Name | Type | Description |
|---|---|---|
registry | address | The protocol registry address. |
liteNamesOf
Lists the lite-person names currently owned by user.
Reads the user's LabelStore labels and pending claims, keeps the lite-person
shaped ones, and re-checks each against registrar.ownerOf so a name transferred away
drops out and a name transferred in shows under its current owner. Ordering follows the
store then the pending queue. An offset past the end returns an empty array rather than
reverting, and a short return means the slice ended. A gateway name transferred before it
settles has its label in no store, so it cannot appear here and is reachable only by node
via @custom:function nameDetailByNode. Gas grows with the account's holdings, so call it
off-chain. A page holds at most DotnsConstants.MAX_PAGE_SIZE entries, and the pending
portion covers up to that many staged claims.
function liteNamesOf(
address user,
uint256 offset,
uint256 limit
)
external
view
returns (Name[] memory names);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Account whose lite names are listed. |
offset | uint256 | Start index into the filtered sequence. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
names | Name[] | Page of the account's lite names; see @custom:struct Name. |
fullNamesOf
Lists the full-person names currently owned by user.
Same ownership-verified read as @custom:function liteNamesOf, keeping base-shaped labels instead of lite-shaped ones.
function fullNamesOf(
address user,
uint256 offset,
uint256 limit
)
external
view
returns (Name[] memory names);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Account whose full names are listed. |
offset | uint256 | Start index into the filtered sequence. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
names | Name[] | Page of the account's full names; see @custom:struct Name. |
liteNameCountOf
Counts the lite-person names currently owned by user.
Uses the same ownership-verified read as @custom:function liteNamesOf; counting scans the account's holdings, so gas grows with them. Call it off-chain.
function liteNameCountOf(address user) external view returns (uint256 count);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Account whose lite names are counted. |
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Number of lite names currently owned. |
fullNameCountOf
Counts the full-person names currently owned by user.
Uses the same ownership-verified read as @custom:function fullNamesOf; counting scans the account's holdings, so gas grows with them. Call it off-chain.
function fullNameCountOf(address user) external view returns (uint256 count);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Account whose full names are counted. |
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Number of full names currently owned. |
nameDetail
Returns the full on-chain record for a name given its label string.
Resolves the node internally, so a caller holding only the string needs no namehash
implementation. Never reverts on an unknown name: absent fields read as zero or empty.
This overload can populate fullClaim because it holds the label and so its labelhash.
function nameDetail(string calldata name) external view returns (NameDetail memory detail);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Bare DNS label (no TLD). |
Returns
| Name | Type | Description |
|---|---|---|
detail | NameDetail | The name's record; see @custom:struct NameDetail. |
nameDetailByNode
Returns the full on-chain record for a name given its node.
The node cannot be inverted to its labelhash, so fullClaim is populated only when
the label is independently resolvable from the node and reads zero otherwise; every other
field is resolved directly. Never reverts on an unknown node.
function nameDetailByNode(bytes32 node) external view returns (NameDetail memory detail);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | namehash of the name. |
Returns
| Name | Type | Description |
|---|---|---|
detail | NameDetail | The name's record; see @custom:struct NameDetail. |
profileOf
Returns an account-level summary of a user's PoP state.
O(1) facts only; lite and full name counts are read separately via
Note: function: liteNameCountOf and @custom:function fullNameCountOf because those scan the account's holdings. Never reverts.
function profileOf(address user) external view returns (PopProfile memory profile);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Account being summarised. |
Returns
| Name | Type | Description |
|---|---|---|
profile | PopProfile | The account summary; see @custom:struct PopProfile. |
Structs
Name
One row in a per-account name listing: the name and the node used to look it up.
Computed on read; not stored. settled is false while the name still sits in the
temporary pending-claim queue and true once its label is written into a LabelStore.
deadline is the pending settlement deadline (mintedAt + reservationDuration) and is
zero for a settled name.
struct Name {
bytes32 node;
string label;
bool settled;
uint64 deadline;
}
Properties
| Name | Type | Description |
|---|---|---|
node | bytes32 | namehash of the name; the key for chat-key, link, and detail lookups. |
label | string | Full name string. |
settled | bool | Whether the label is written into a LabelStore. |
deadline | uint64 | Pending settlement deadline, or zero when settled. |
NameDetail
The full on-chain record for a single name, gathered from the registrar, the PoP resolver, and PopRules in one read.
Computed on read; not stored. Never reverts on an unminted or unsettled name: absent
fields read as zero or empty. tier classifies the label shape (the tier the name
requires), not the owner's personhood. fullClaim is keyed by the lite labelhash, which
cannot be recovered from a node alone, so it is populated by @custom:function nameDetail
and left zero by @custom:function nameDetailByNode unless the label is independently
resolvable.
struct NameDetail {
bytes32 node;
string label;
address owner;
bool exists;
bool settled;
IPopRules.PopStatus tier;
bytes chatKey;
bytes32 liteLink;
bytes32 fullClaim;
}
Properties
| Name | Type | Description |
|---|---|---|
node | bytes32 | namehash of the name. |
label | string | Full name string, or empty when the name is unminted or its claim is unsettled. |
owner | address | Current registrar owner, or the zero address when the name does not exist. |
exists | bool | Whether the name is minted. |
settled | bool | Whether the label is written into the current owner's LabelStore. |
tier | IPopRules.PopStatus | PopRules classification of the label. |
chatKey | bytes | Chat-key bytes recorded on the PoP resolver for the node. |
liteLink | bytes32 | For a full name, the linked lite labelhash; zero otherwise. |
fullClaim | bytes32 | For a lite name, the promoted full node; zero otherwise or when unresolvable from a node. |
PopProfile
An account-level summary of PoP state, gathered in one read.
Computed on read; not stored, and never reverts. Name counts are excluded because counting scans the account's holdings; read them with @custom:function liteNameCountOf and
Note: function: fullNameCountOf when required. The account's personhood tier is read separately via @custom:function IPopRules.personhoodOf, which consults the personhood precompile and so does not belong in this precompile-free summary.
struct PopProfile {
bool hasLabelStore;
uint256 pendingClaimCount;
bytes32 reservationLabelhash;
}
Properties
| Name | Type | Description |
|---|---|---|
hasLabelStore | bool | Whether the account has a deployed LabelStore. |
pendingClaimCount | uint256 | Number of claims still staged in the pending queue. |
reservationLabelhash | bytes32 | The base label the account holds a live reservation on, or zero when none. |
IDotnsRegistrar
Inherits: IERC721
Title: Dotns Registrar
ERC721-backed ownership for DotNS names with controller-gated registration.
Intentionally minimal and policy-free. Provides ERC721 ownership for registered name token IDs and controller-gated registration; pricing, PoP enforcement, and flow-specific policy live in the controllers.
Note: security-contact: admin@parity.io
Functions
available
Returns whether a registration call may proceed for id.
Signals two distinct paths to the controller. Returns true when the owner slot is
empty (a fresh @custom:function register call may mint) OR when the current owner is
the configured escrow and the released position's redeem window has elapsed (the
controller must then route through @custom:function IDotnsNameEscrow.reclaim instead of
Notes:
-
function: register, because
registercalls_mintwhich rejects existing tokens). All other holders returnfalse. The controller distinguishes the twotruecases via @custom:function exists. Escrow custody inside the redeem window returnsfalse: that window belongs to the previous holder, who may still @custom:function IDotnsNameEscrow.redeem the name, and reclaim would revert until it elapses. Clients wanting the exact moment a released name becomes registrable should readredeemableUntilfrom -
function: IDotnsNameEscrow.getReleasePosition.
function available(uint256 id) external view returns (bool isAvailable);
register
Registers a name permanently.
Permanence is by construction: there is no expire, renew, or release path on
the registrar. Custody only moves via ERC721 transfers (which the registrar polices via
the fee-on-transfer hook) or via escrow reclaim. Restricted to authorised controllers
(otherwise @custom:reverts NotController) and rejects ids that are not available
(otherwise @custom:reverts NameNotAvailable). Emits @custom:emits NameRegistered on
success.
function register(uint256 id, address owner, string calldata label) external;
Parameters
| Name | Type | Description |
|---|---|---|
id | uint256 | |
owner | address | |
label | string | The human-readable label string (e.g. "alice"). |
exists
Returns whether a given token id has been minted.
function exists(uint256 tokenId) external view returns (bool tokenExists);
addController
Adds an authorised controller.
Typed against the baseline IDotnsController (not a concrete subtype) so a single
authorisation surface accepts every controller flavour (commit-reveal, PoP gateway,
future variants) without per-flavour setters. Owner-gated (otherwise
Note: reverts: OwnableUnauthorizedAccount); emits @custom:emits ControllerAdded on success.
function addController(IDotnsController controller) external;
removeController
Removes an authorised controller.
Mirrors the @custom:function addController baseline-typed signature so any registered controller can be revoked through the same entry point. Owner-gated (otherwise
Note: reverts: OwnableUnauthorizedAccount); emits @custom:emits ControllerRemoved on success.
function removeController(IDotnsController controller) external;
controllers
Returns whether controller is currently authorised to call
Note: function: register.
function controllers(IDotnsController controller) external view returns (bool authorised);
Parameters
| Name | Type | Description |
|---|---|---|
controller | IDotnsController | Candidate controller. |
Returns
| Name | Type | Description |
|---|---|---|
authorised | bool | True when controller was added via @custom:function addController and has not been removed. |
labelOf
Returns the human-readable label a token was registered with.
Canonical state source for the label string; any client that holds a node or tokenId can resolve the original label in one view call without scanning registration events. Returns the empty string when the token does not exist.
function labelOf(uint256 tokenId) external view returns (string memory label);
quoteTransferFee
Quotes the additional native fee required to transfer a token to to.
Returns the fee from @custom:function PopRules.transferFloor: the name's own price
as the maximum of (i) the reach component charged when the recipient does not meet
the label's required tier and (ii) the downgrade component charged when the
recipient tier is strictly below the sender tier. Self-transfers and
escrow-touching transfers (release into escrow, reclaim out of escrow) return
zero. A token whose sender has no stored label also returns zero because there
is no label-derived price to charge against; this covers gateway-cold PoP mints
(the controller passes an empty label to @custom:function register so substrate
Root does not have to deploy a LabelStore) until the user settles via
Notes:
-
function: IDotnsPopController.claimLabelStore. Because settlement writes the label into the original claimant's store, a transfer that happens before settlement leaves the recipient with no label entry and the zero-fee branch persists for that token under all future holders. A token registered with no label that is moved off-chain prior to settlement therefore carries no PoP-tier transfer friction. Off-chain consumers integrating PoP mints should treat
-
function: claimLabelStore as a prerequisite for accurate transfer-time pricing on gateway-issued names. Rejects a zero
towith -
reverts: ERC721InvalidReceiver, an unminted
tokenIdwith -
reverts: ERC721NonexistentToken via the underlying
ownerOf, and requires the protocol registry to have an escrow configured (otherwise -
reverts: EscrowNotConfigured). Returns zero when the protocol registry has no
STORE_FACTORYconfigured because no label-derived price is reachable.
function quoteTransferFee(
uint256 tokenId,
address to
)
external
view
returns (uint256 requiredFee);
safeTransferFrom
The registrar's _update hook consults @custom:function PopRules.transferFloor
to compute the required transfer fee; if the caller does not forward at least that
amount as msg.value, the transfer reverts with @custom:reverts TransferFeeRequired.
The payable modifier on every transfer overload exists so the fee can be forwarded
in the same call.
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
)
external
payable
override;
safeTransferFrom
Subject to the same fee-on-transfer gate as the four-argument overload; reverts with
Note:
reverts: TransferFeeRequired when the recipient owes a non-zero transfer fee and
the caller has not forwarded it as msg.value.
function safeTransferFrom(address from, address to, uint256 tokenId) external payable override;
transferFrom
Subject to the same fee-on-transfer gate as the safe overloads; reverts with
Note:
reverts: TransferFeeRequired when the recipient owes a non-zero transfer fee and
the caller has not forwarded it as msg.value.
function transferFrom(address from, address to, uint256 tokenId) external payable override;
Events
NameRegistered
Emitted when a name is registered.
event NameRegistered(uint256 indexed id, address indexed owner);
ControllerAdded
Emitted when a controller is added.
Typed as the shared baseline @custom:contract IDotnsController so the commit-reveal controller and the PoP controller (and any future controller) all fit the same signature without the registrar depending on any specific controller interface.
event ControllerAdded(IDotnsController indexed controller);
ControllerRemoved
Emitted when a controller is removed.
event ControllerRemoved(IDotnsController indexed controller);
Errors
NameNotAvailable
Thrown when a name is already registered.
error NameNotAvailable(uint256 tokenId);
NotController
Thrown when the caller is not an authorised controller.
error NotController(address caller);
EscrowNotConfigured
Thrown when the protocol registry has no escrow address configured.
error EscrowNotConfigured();
TransferFeeRequired
Thrown when a standard ERC721 transfer is attempted but the recipient
tier requires a non-zero transfer fee and the caller forwarded no msg.value.
error TransferFeeRequired(uint256 tokenId, address to, uint256 requiredFee);
ProtocolRegistryRequired
Thrown when @custom:function initialize is called with the zero address as the protocol registry.
error ProtocolRegistryRequired();
UnexpectedValue
Thrown when a mint, burn, or self-transfer carries msg.value. None of those
paths forward value onward, so attached value would be permanently trapped.
error UnexpectedValue();
InvalidOwner
Thrown when @custom:function register is called with the escrow address as
owner, which would mint directly into escrow custody with no @custom:struct
ReleasePosition recorded.
error InvalidOwner();
InvalidLabel
Thrown when @custom:function register receives an empty or non-canonical label.
error InvalidLabel();
IDotnsRegistrarController
Inherits: IDotnsController
Title: Dotns Registrar Controller
Interface for registering top-level labels using a commit reveal scheme.
Defines allocation only; forward resolution, reverse lookup, pricing mechanics, PoP validation, and store writing are handled by external contracts. Users commit a hash of registration parameters and, after a minimum delay, reveal the same parameters to register. Implementations write the successfully registered name into the user's Store to create an immutable on-chain record that doubles as a quick lookup for all names registered.
Note: security-contact: admin@parity.io
Functions
available
Returns whether a label is available for registration.
Validates the canonical DNS-label shape (otherwise @custom:reverts InvalidLabel) and rejects labels below the minimum-length policy with
Note: reverts: LabelTooShort before checking ERC721 availability on the registrar.
function available(string calldata label) external view returns (bool isAvailable);
makeCommitment
Computes the commitment hash for a registration.
Uses abi.encode so the variable-width label is length-prefixed and the boundary
between label and the fixed-width owner, secret, reserved, maxPrice, and
pricingVersion fields is unambiguous, binding the commitment to the exact tuple. The
price ceiling and cost-model version are part of that tuple, so neither can be altered
between commit and reveal.
function makeCommitment(Registration calldata registration)
external
pure
returns (bytes32 commitment);
commit
Submits a commitment for a future registration.
Idempotent over expiry: re-committing an unexpired hash reverts with
Note:
reverts: UnexpiredCommitmentExists (front-running guard); a hash whose stored
timestamp has passed maxCommitmentAge overwrites the slot so storage cannot be
permanently griefed. The expiry boundary is inclusive on the commit side
(committedAt + maxCommitmentAge <= block.timestamp overwrites) and exclusive on the
reveal side (register rejects at the same instant with @custom:reverts
CommitmentTooOld), so the slot is overwritable from exactly the timestamp at which
reveal begins rejecting it. Stamps the cost model's current version on the commitment, so
the reveal binds to the version live now and rejects a pricingVersion bound to an earlier
one with @custom:reverts PricingVersionMismatch. Emits @custom:emits NameCommitted on
success.
function commit(bytes32 commitment) external;
register
Registers a name after the commitment delay.
Validates the label shape (otherwise @custom:reverts InvalidLabel), rejects labels
below the minimum length policy (@custom:reverts LabelTooShort), and ERC721 availability
(otherwise @custom:reverts NameNotAvailable), then consumes the prior commitment, which
fails with @custom:reverts CommitmentNotFound when no commitment exists for the supplied
registration, @custom:reverts CommitmentTooNew before minCommitmentAge, and
Notes:
-
reverts: CommitmentTooOld past
maxCommitmentAge, and finally resolves the configured escrow address from the protocol registry (otherwise -
reverts: EscrowNotConfigured). Splits on direct vs cross-payer at
msg.sender == registration.owner. The direct path runspriceWithCheck(personhood
- reservation gate) and routes the charge to a refundable escrow deposit owned by
registration.owner. The cross-payer path skips the personhood revert inpriceWithCheckbut applies it directly via @custom:reverts OwnerStatusInsufficient when the owner's recorded tier does not meet the label's required tier, and still rejects governance-reserved labels with @custom:reverts GovernanceReserved and live cross-user stem reservations with @custom:reverts NameReserved. The cross-payer charge is the owner-side registration price; the path applies no separate transfer friction. The charge routes to the escrow protocol fee pot while seeding a zero-amount deposit slot so the release lifecycle stays reachable. The reveal prices the name at the committedpricingVersion, so a model change between commit and reveal leaves the amount unchanged, and rejects a total charge above the committed ceiling with @custom:reverts PriceExceedsMax before checking payment. The caller must supply at least the charge (otherwise @custom:reverts InsufficientValue); any overpayment is pushed back tomsg.senderinline and, on failure, credited to the escrow's pull-payment ledger so contract receivers cannot block registration. Emits @custom:emits OverpaymentRefunded on the inline branch, the escrow's own @custom:emits OverpaymentRefunded on the pull fallback, and @custom:emits NameRegistered on success.
function register(Registration calldata registration) external payable;
registerReserved
Registers a name after the commitment delay.
Whitelisted issuance path used to seed reserved labels at zero base cost: skips the PoP price check and the escrow deposit, but reuses the same commit-reveal pipeline so the same anti-front-running guarantees apply. Restricted to whitelisted callers and the owner (otherwise @custom:reverts NotWhiteListedOrOwner). Validates the label shape (otherwise @custom:reverts InvalidLabel) and ERC721 availability (otherwise
Notes:
-
reverts: NameNotAvailable), then consumes the prior commitment, which fails with
-
reverts: CommitmentNotFound, @custom:reverts CommitmentTooNew, or
-
reverts: CommitmentTooOld under the same conditions as @custom:function register. Emits
-
emits: NameRegistered on success.
function registerReserved(Registration calldata registration) external;
isWhiteListed
Checks if the given address is whitelisted to call registerReserved.
function isWhiteListed(address who) external view returns (bool isWhiteListed);
whiteListAddress
Adds or removes an address from the whitelist for registerReserved.
Callable by the owner or an account holding DotnsConstants.WHITELIST_OPERATOR_ROLE;
any other caller reverts with @custom:reverts IDotnsRoleManager.NotRoleOrOwner. Emits
Note: emits: WhiteListed on success.
function whiteListAddress(address who, bool whiteListStatus) external;
Events
NameCommitted
Emitted when a commitment is submitted.
event NameCommitted(bytes32 indexed commitment);
NameRegistered
Emitted when a name is successfully registered.
event NameRegistered(
string indexed label,
bytes32 indexed labelhash,
address indexed owner,
uint256 baseCost,
address store
);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | |
labelhash | bytes32 | |
owner | address | |
baseCost | uint256 | The price returned by the oracle for this registration. |
store | address | The Store instance used to persist an immutable registration record. |
WhiteListed
Emitted when an address is added to or removed from the whitelist.
event WhiteListed(address indexed who, bool indexed whiteListStatus);
OverpaymentRefunded
Emitted when overpayment is refunded to the payer at registration entry.
event OverpaymentRefunded(address indexed payer, uint256 amount);
Errors
NotWhiteListedOrOwner
Thrown when the caller is not whitelisted or the owner.
error NotWhiteListedOrOwner(address caller);
UnexpiredCommitmentExists
Thrown when an unexpired commitment already exists.
error UnexpiredCommitmentExists(bytes32 commitment);
CommitmentNotFound
Thrown when revealing a commitment that does not exist.
error CommitmentNotFound(bytes32 commitment);
CommitmentTooNew
Thrown when a commitment is revealed before the minimum age.
error CommitmentTooNew(bytes32 commitment, uint256 minTime, uint256 currentTime);
CommitmentTooOld
Thrown when a commitment has expired.
error CommitmentTooOld(bytes32 commitment, uint256 maxTime, uint256 currentTime);
NameNotAvailable
Thrown when attempting to register an unavailable name.
error NameNotAvailable(string label);
LabelTooShort
Thrown when a label is below the minimum-length policy.
Distinct from @custom:reverts NameNotAvailable so off-chain consumers can tell a too-short label apart from a name that is already minted.
error LabelTooShort(string label);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Caller-supplied label that failed the minimum-length policy. |
InvalidLabel
Thrown when a label is not a canonical lowercase ASCII DNS label.
error InvalidLabel();
InsufficientValue
Thrown when supplied payment is insufficient.
error InsufficientValue();
PriceExceedsMax
Thrown when the total charge exceeds the ceiling the caller committed to.
error PriceExceedsMax(string label, uint256 charged, uint256 maxPrice);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Label whose charge exceeded the ceiling. |
charged | uint256 | Total charge computed at reveal. |
maxPrice | uint256 | Ceiling the caller committed to. |
EscrowNotConfigured
Thrown when escrow is not configured in the protocol registry.
error EscrowNotConfigured();
MinCommitmentAgeZero
Thrown when min commitment age is zero, which would allow same-block commit-reveal and defeat the front-running guard.
error MinCommitmentAgeZero();
MaxCommitmentAgeTooLow
Thrown when max commitment age is invalid (must be > minCommitmentAge).
error MaxCommitmentAgeTooLow();
MaxCommitmentAgeTooHigh
Thrown when max commitment age is invalid (exceeds implementation limit).
error MaxCommitmentAgeTooHigh();
Structs
Registration
Parameters used to generate and reveal a commitment.
All fields must match exactly between commitment and reveal.
struct Registration {
string label;
address owner;
bytes32 secret;
bool reserved;
uint256 maxPrice;
uint256 pricingVersion;
}
Properties
| Name | Type | Description |
|---|---|---|
label | string | Label being registered (e.g. "alice"). |
owner | address | Beneficiary the registered name is minted to. |
secret | bytes32 | Caller-chosen entropy that hides the registration intent in the commit hash; revealed verbatim at registration time. |
reserved | bool | True when the registration flows through the whitelisted reserved pipeline (registerReserved); false for the standard public flow (register). |
maxPrice | uint256 | Ceiling in wei the caller accepts for this registration; a reveal charged above it reverts, closing the gap between the price at commit and the price at reveal. |
pricingVersion | uint256 | Cost-model version the caller committed to; the reveal prices the name at this version, so a model change between commit and reveal leaves the amount unchanged. It must equal the version current when commit ran, which that call stamps on the commitment; a reveal whose pricingVersion differs reverts, so the caller cannot bind an earlier, cheaper version. |
Contents
DotnsProtocolRegistry
Inherits: Initializable, UUPSUpgradeable, OwnableUpgradeable, IDotnsProtocolRegistry
Title: Dotns Protocol Registry
Author: Parity
Upgradeable address registry for all DotNS protocol contracts, and the authority for the network's top-level domain.
Single source of truth for sibling-contract lookups. All siblings resolve each other via
well-known bytes32 constants in DotnsConstants rather than holding direct addresses,
so an upgrade or rewire only mutates this contract. The TLD node and suffix are set once
at initialisation and read live by every consumer, so a network runs one TLD without
recompiling its contracts.
Note: security-contact: admin@parity.io
State Variables
_addresses
Address stored for each well-known protocol key.
mapping(bytes32 key => address addr) private _addresses
_registeredRefcount
Reference count per address, incremented for every key it is registered under.
Lets isRegisteredAddress answer in O(1) and survive a contract being mapped to
multiple keys without being treated as deregistered when only one key is rewired.
mapping(address addr => uint256 refcount) private _registeredRefcount
_tldNode
Namehash of the TLD node, namehash(0, keccak256(bytes(tldLabel))).
bytes32 private _tldNode
_tld
TLD suffix including the leading dot, e.g. .dot.
string private _tld
__gap
uint256[50] private __gap
Functions
constructor
Note: oz-upgrades-unsafe-allow: constructor
constructor() ;
initialize
Initialises the protocol registry and fixes the network's TLD.
Callable exactly once via Initializable, otherwise
Note:
reverts: InvalidInitialization. Sets the deployer as owner. tldLabel is the
bare label without a dot (e.g. dot, paseo); it must be a single DNS label,
otherwise @custom:reverts InvalidTld. The TLD is fixed here because changing it after
names exist would reroot every node.
function initialize(string calldata tldLabel) external initializer;
Parameters
| Name | Type | Description |
|---|---|---|
tldLabel | string | Bare TLD label, without the leading dot. |
get
Returns the address stored for a given key.
Returns address(0) when the key is unset; callers must validate when non-zero is
required.
function get(bytes32 key) external view override returns (address addr);
set
Sets or updates the address for a given key.
Owner-restricted, otherwise @custom:reverts OwnableUnauthorizedAccount. addr
must be non-zero, otherwise @custom:reverts ZeroAddress. Idempotent when the new
value matches the stored one (no event emitted in that case). Maintains a
per-address refcount so the same contract can occupy multiple keys without losing
its registered status until every key is rewired. Emits
Note: emits: AddressUpdated on each effective change.
function set(bytes32 key, address addr) external override onlyOwner;
isRegisteredAddress
Returns true iff addr is currently registered under at least one well-known key.
O(1) refcount-backed lookup. Canonical peer-trust check consumed by LabelStore
writes and StoreFactory deploys; only addresses governance has actively
registered return true. Treats address(0) as never registered regardless of
refcount.
function isRegisteredAddress(address addr) external view override returns (bool registered);
tldNode
Returns the namehash of the network's TLD node.
namehash(0, keccak256(bytes(tldLabel))), fixed at initialisation. Consumers use it
as the root parent when deriving a name's node.
function tldNode() external view override returns (bytes32 node);
tld
Returns the network's TLD suffix, including the leading dot (e.g. .dot).
Fixed at initialisation. Consumers append it when rendering a label as a full name.
function tld() external view override returns (string memory suffix);
version
Returns implementation version.
function version() external pure virtual returns (string memory versionString);
Returns
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
_authorizeUpgrade
Function that should revert when msg.sender is not authorized to upgrade the contract.
Called by
{upgradeToAndCall}.
Normally, this function will use an xref:access.adoc[access control] modifier such as
{Ownable-onlyOwner}.
function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;
DotnsRegistry
Inherits: Initializable, UUPSUpgradeable, OwnableUpgradeable, IDotnsRegistry
Title: Dotns Registry
Author: Parity
Upgradeable on-chain registry for hierarchical name ownership and resolution.
Tokenised second-level nodes store owner == address(0) as a sentinel and defer to
IDotnsRegistrar.ownerOf; subnodes carry an explicit owner address in records.
Note: security-contact: admin@parity.io
State Variables
records
Mapping of node identifiers to records.
mapping(bytes32 node => Record record) private records
protocolRegistry
Protocol-level address registry for all DotNS contracts.
IDotnsProtocolRegistry public protocolRegistry
__gap
uint256[50] private __gap
Functions
authorised
Restricts access to the current owner of node.
modifier authorised(bytes32 node) ;
onlyRegistrarController
Restricts access to the configured registrar controller.
modifier onlyRegistrarController() ;
constructor
Note: oz-upgrades-unsafe-allow: constructor
constructor() ;
initialize
Initialises the registry.
Callable exactly once via Initializable, otherwise
Notes:
-
reverts: InvalidInitialization.
registrymust be non-zero, otherwise -
reverts: NotAllowed.
function initialize(IDotnsProtocolRegistry registry) external initializer;
Parameters
| Name | Type | Description |
|---|---|---|
registry | IDotnsProtocolRegistry | Protocol-level address registry used to resolve sibling contracts. |
setSubnodeOwner
Creates or reassigns a subnode and assigns its owner.
Callable only by the current owner of record.parentNode, otherwise
Note: reverts: NotAuthorised. The new owner address must be non-zero, otherwise
function setSubnodeOwner(SubnodeRecord calldata record)
external
override
authorised(record.parentNode)
returns (bytes32 subnode);
setOwner
Creates or resets a node record for a tokenised base registration.
Restricted to the registrar's controllers, otherwise @custom:reverts NotAuthorised.
newOwner must be non-zero (otherwise @custom:reverts NotAllowed) and must match
the ERC-721 owner reported by the registrar (otherwise
Notes:
-
reverts: NotAuthorised). The function is callable both on a fresh registration and on every reclaim from escrow: each call rewrites
records[node].resolverto the protocol-registered default reverse resolver so a prior owner's resolver pointer (and the records keyed under it) cannot be inherited by the next holder. Storesowner = address(0)as a sentinel so reads delegate toIDotnsRegistrar.ownerOfand ERC-721 transfers remain authoritative. Emits -
emits: NodeTransferred on success.
function setOwner(bytes32 node, address newOwner) external override onlyRegistrarController;
setResolver
Sets or clears the resolver for a node.
Callable only by the current node owner, otherwise @custom:reverts NotAuthorised.
For tokenised nodes, authorisation falls back to ERC-721 owner / approved /
operator-for-all via the registrar. The registry does not validate
resolverAddr against any interface or code presence; off-chain consumers must
verify resolver shape before trusting reads. Emits @custom:emits NewResolver on
success.
function setResolver(bytes32 node, address newResolver) external override authorised(node);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | |
newResolver | address |
setSubnodeResolver
Sets the resolver for an existing subnode.
Callable only by the current owner of record.parentNode, otherwise
Note:
reverts: NotAuthorised. The subnode owner can still update the resolver
directly via setResolver. Both entry points emit the same NewResolver(subnode, ...) event, so the parent can silently override a subnode owner's chosen resolver:
this is the parent-sovereign hierarchy applied to resolution. Off-chain consumers
that surface trust signals to subnode owners should treat any resolver rotation as
a re-attestation prompt. record.subLabel must be a single canonical DNS label
(otherwise @custom:reverts InvalidLabel) and record.parentLabel must be a name
path whose namehash matches record.parentNode (otherwise
function setSubnodeResolver(SubnodeResolverRecord calldata record)
external
override
authorised(record.parentNode);
owner
Returns the owner of a node.
For tokenised nodes the stored owner is the zero sentinel; the implementation falls
back to IDotnsRegistrar.ownerOf(uint256(node)).
function owner(bytes32 node) external view override returns (address);
resolver
Returns the resolver of a node.
function resolver(bytes32 node) external view override returns (address);
recordExists
Returns whether a node exists.
function recordExists(bytes32 node) external view override returns (bool);
isAuthorised
Returns whether account is authorised to manage node.
For subnodes, authority is the explicit stored owner. For tokenised nodes it is the ERC-721 owner, an address approved for the token, or an operator approved for all of the owner's tokens via the registrar. This is the canonical authorisation check the registry enforces on owner-gated entry points; sibling contracts may consult it so a single registrar-level approval delegates management across the protocol. Returns false for a node that does not exist.
function isAuthorised(
bytes32 node,
address account
)
external
view
override
returns (bool authorisedFlag);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | Node identifier. |
account | address | Address whose authority is being checked. |
Returns
| Name | Type | Description |
|---|---|---|
authorisedFlag | bool | True when account may manage node. |
_writeSubnodeToStore
Writes subnode registration to the owner's LabelStore.
Keys the entry by node (full namehash) rather than labelhash so a single store
lookup yields the canonical full name without re-walking the parent chain.
function _writeSubnodeToStore(
address storeOwner,
bytes32 node,
string memory fullName
)
internal;
_parentNamehash
Computes the namehash of parentLabel rooted at the network's TLD node.
Walks the label right-to-left in calldata using memory-safe assembly to avoid the
cost of slicing into intermediate bytes and to keep gas linear in the label depth.
Reads the TLD node from the protocol registry, so it is a view rather than pure.
function _parentNamehash(string calldata parentLabel) internal view returns (bytes32 node);
_authorised
Internal authorisation check for node ownership.
Reverts with NotAuthorised when msg.sender is not authorised for node.
function _authorised(bytes32 node) internal view;
_isAuthorised
Canonical authorisation rule for a node, parameterised by account.
Honours the sentinel-zero pattern: if the registry has no explicit owner, fall back
to the registrar's ERC-721 owner / approved / operator-for-all chain. This is the
single source of truth _authorised and isAuthorised both delegate to.
function _isAuthorised(bytes32 node, address account) internal view returns (bool);
_onlyRegistrarController
Internal check for registrar-authorised controller privileges.
The registry trusts every controller the registrar trusts. Routing controller
authorisation through the registrar's controllers mapping keeps the trust list
in one place and lets commit-reveal and PoP controllers coexist without registry
reconfiguration on each addition.
function _onlyRegistrarController() internal view;
version
Returns implementation version.
function version() external pure virtual returns (string memory versionString);
Returns
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
_authorizeUpgrade
Function that should revert when msg.sender is not authorized to upgrade the contract.
Called by
{upgradeToAndCall}.
Normally, this function will use an xref:access.adoc[access control] modifier such as
{Ownable-onlyOwner}.
function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;
IDotnsProtocolRegistry
Title: IDotnsProtocolRegistry
Author: Parity
Interface for the DotNS protocol-level address registry.
Single source of truth for sibling lookups. Contracts resolve each other via well-known
bytes32 constants in DotnsConstants so an upgrade or rewire only mutates the
registry, never the consumers. The registry also holds the network's top-level domain,
so every consumer reads one TLD rather than compiling its own.
Note: security-contact: admin@parity.io
Functions
get
Returns the address stored for a given key.
Returns address(0) when the key is unset; callers must validate when non-zero is
required.
function get(bytes32 key) external view returns (address addr);
set
Sets or updates the address for a given key.
Owner-restricted, otherwise @custom:reverts OwnableUnauthorizedAccount. addr
must be non-zero, otherwise @custom:reverts ZeroAddress. Idempotent when the new
value matches the stored one (no event emitted in that case). Maintains a
per-address refcount so the same contract can occupy multiple keys without losing
its registered status until every key is rewired. Emits
Note: emits: AddressUpdated on each effective change.
function set(bytes32 key, address addr) external;
isRegisteredAddress
Returns true iff addr is currently registered under at least one well-known key.
O(1) refcount-backed lookup. Canonical peer-trust check consumed by LabelStore
writes and StoreFactory deploys; only addresses governance has actively
registered return true. Treats address(0) as never registered regardless of
refcount.
function isRegisteredAddress(address addr) external view returns (bool registered);
tldNode
Returns the namehash of the network's TLD node.
namehash(0, keccak256(bytes(tldLabel))), fixed at initialisation. Consumers use it
as the root parent when deriving a name's node.
function tldNode() external view returns (bytes32 node);
tld
Returns the network's TLD suffix, including the leading dot (e.g. .dot).
Fixed at initialisation. Consumers append it when rendering a label as a full name.
function tld() external view returns (string memory suffix);
Events
AddressUpdated
Emitted when a protocol address is set or updated.
event AddressUpdated(bytes32 indexed key, address indexed addr);
Errors
ZeroAddress
Thrown when a zero address is provided where one is not allowed.
error ZeroAddress();
InvalidTld
Thrown when the TLD label supplied at initialisation is not a single DNS label.
error InvalidTld();
IDotnsRegistry
Title: IDotnsRegistry
Author: Parity
Minimal on-chain registry for hierarchical name ownership and resolution.
Tokenised second-level nodes are owned through the registrar (ERC-721); subnodes are
owned directly by the address stored in Record.owner.
Note: security-contact: admin@parity.io
Functions
setSubnodeOwner
Creates or reassigns a subnode and assigns its owner.
Callable only by the current owner of record.parentNode, otherwise
Notes:
-
reverts: NotAuthorised. The new owner address must be non-zero, otherwise
-
reverts: NotAllowed.
record.subLabelmust be a single canonical DNS label (otherwise @custom:reverts InvalidLabel) andrecord.parentLabelmust be a name path whose namehash matchesrecord.parentNode(otherwise -
reverts: ParentLabelMismatch). Subnodes are parent-sovereign: the current
record.parentNodeowner may reassign or rotate a subnode's resolver at any time without the prior subnode owner's consent. On reassignment the resolver pointer is reset to the protocol-registered default reverse resolver so a prior subnode owner's resolver cannot be inherited by the next holder (records on other resolver contracts are keyed by node and are not cleared by this function; downstream consumers should gate resolver reads on current ownership). Indexes the subnode under the new owner'sLabelStorekeyed by the namehashedsubnodeso off-chain consumers can enumerate names per address. Emits @custom:emits NewOwner on each successful assignment.
function setSubnodeOwner(SubnodeRecord calldata record) external returns (bytes32 subnode);
setSubnodeResolver
Sets the resolver for an existing subnode.
Callable only by the current owner of record.parentNode, otherwise
Notes:
-
reverts: NotAuthorised. The subnode owner can still update the resolver directly via
setResolver. Both entry points emit the sameNewResolver(subnode, ...)event, so the parent can silently override a subnode owner's chosen resolver: this is the parent-sovereign hierarchy applied to resolution. Off-chain consumers that surface trust signals to subnode owners should treat any resolver rotation as a re-attestation prompt.record.subLabelmust be a single canonical DNS label (otherwise @custom:reverts InvalidLabel) andrecord.parentLabelmust be a name path whose namehash matchesrecord.parentNode(otherwise -
reverts: ParentLabelMismatch). The resulting subnode must already exist, otherwise @custom:reverts NotAuthorised. Emits @custom:emits NewResolver on success.
function setSubnodeResolver(SubnodeResolverRecord calldata record) external;
setOwner
Creates or resets a node record for a tokenised base registration.
Restricted to the registrar's controllers, otherwise @custom:reverts NotAuthorised.
newOwner must be non-zero (otherwise @custom:reverts NotAllowed) and must match
the ERC-721 owner reported by the registrar (otherwise
Notes:
-
reverts: NotAuthorised). The function is callable both on a fresh registration and on every reclaim from escrow: each call rewrites
records[node].resolverto the protocol-registered default reverse resolver so a prior owner's resolver pointer (and the records keyed under it) cannot be inherited by the next holder. Storesowner = address(0)as a sentinel so reads delegate toIDotnsRegistrar.ownerOfand ERC-721 transfers remain authoritative. Emits -
emits: NodeTransferred on success.
function setOwner(bytes32 node, address newOwner) external;
setResolver
Sets or clears the resolver for a node.
Callable only by the current node owner, otherwise @custom:reverts NotAuthorised.
For tokenised nodes, authorisation falls back to ERC-721 owner / approved /
operator-for-all via the registrar. The registry does not validate
resolverAddr against any interface or code presence; off-chain consumers must
verify resolver shape before trusting reads. Emits @custom:emits NewResolver on
success.
function setResolver(bytes32 node, address resolverAddr) external;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | |
resolverAddr | address | Resolver contract address (zero clears). |
owner
Returns the owner of a node.
For tokenised nodes the stored owner is the zero sentinel; the implementation falls
back to IDotnsRegistrar.ownerOf(uint256(node)).
function owner(bytes32 node) external view returns (address);
resolver
Returns the resolver of a node.
function resolver(bytes32 node) external view returns (address);
recordExists
Returns whether a node exists.
function recordExists(bytes32 node) external view returns (bool);
isAuthorised
Returns whether account is authorised to manage node.
For subnodes, authority is the explicit stored owner. For tokenised nodes it is the ERC-721 owner, an address approved for the token, or an operator approved for all of the owner's tokens via the registrar. This is the canonical authorisation check the registry enforces on owner-gated entry points; sibling contracts may consult it so a single registrar-level approval delegates management across the protocol. Returns false for a node that does not exist.
function isAuthorised(bytes32 node, address account) external view returns (bool authorisedFlag);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | Node identifier. |
account | address | Address whose authority is being checked. |
Returns
| Name | Type | Description |
|---|---|---|
authorisedFlag | bool | True when account may manage node. |
Events
NewOwner
Emitted when a new subnode owner is set.
event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | Parent node. |
label | bytes32 | Labelhash of the created subnode. |
owner | address |
NodeTransferred
Emitted when ownership of a node is transferred.
event NodeTransferred(bytes32 indexed node, address owner);
NewResolver
Emitted when a resolver is set or updated.
event NewResolver(bytes32 indexed node, address resolver);
Errors
NotAllowed
Thrown when an invalid (zero) address is provided.
error NotAllowed();
NotAuthorised
Thrown when the caller is not authorised.
error NotAuthorised();
NotRegistryController
Thrown when the caller is not the registry controller.
error NotRegistryController();
NodeAlreadyOwned
Thrown when attempting to create a node that already exists.
error NodeAlreadyOwned(bytes32 node);
InvalidLabel
Thrown when a sublabel is not a canonical lowercase ASCII DNS label.
error InvalidLabel();
ParentLabelMismatch
Thrown when the supplied parent label does not match the parent node.
error ParentLabelMismatch();
Structs
SubnodeRecord
Record describing a subnode creation request.
struct SubnodeRecord {
bytes32 parentNode;
string subLabel;
string parentLabel;
address owner;
}
Properties
| Name | Type | Description |
|---|---|---|
parentNode | bytes32 | |
subLabel | string | Human readable subnode label e.g "alice". |
parentLabel | string | Canonical parent name without the TLD suffix e.g. bob or child.bob. |
owner | address | Address to assign as owner of the created subnode. |
Record
Record describing the state of a node.
struct Record {
address owner;
address resolver;
bool exists;
}
Properties
| Name | Type | Description |
|---|---|---|
owner | address | Address that owns the node, or address(0) sentinel for tokenised nodes. |
resolver | address | Address of the resolver associated with the node. |
exists | bool | Whether the node has been explicitly created. |
SubnodeResolverRecord
Record describing a subnode resolver update request.
struct SubnodeResolverRecord {
bytes32 parentNode;
string subLabel;
string parentLabel;
address resolver;
}
Properties
| Name | Type | Description |
|---|---|---|
parentNode | bytes32 | |
subLabel | string | Human-readable subnode label e.g "alice". |
parentLabel | string | Canonical parent name without the TLD suffix e.g bob or child.bob. |
resolver | address | Resolver contract address (zero clears). |
Contents
- DotnsContentResolver
- DotnsPopResolver
- DotnsResolver
- DotnsReverseResolver
- IDotnsContentResolver
- IDotnsPopResolver
- IDotnsResolver
- IDotnsReverseResolver
DotnsContentResolver
Inherits: Initializable, UUPSUpgradeable, OwnableUpgradeable, ERC165Upgradeable, IDotnsContentResolver
Title: Dotns Content Resolver
Implements IDotnsContentResolver interface with content hash, text records, and
operator approvals.
Writes are gated on the registry's authorisation for the node (owner or registrar-level approval) or on a resolver-local operator the owner has approved, rather than on a privileged writer address. Content records are user-managed metadata, so write authority follows the node owner across transfers and honours the same delegates the registry recognises.
Note: security-contact: admin@parity.io
State Variables
contenthashes
Stores all content hash mappings
mapping(bytes32 node => bytes contentHash) private contenthashes
textRecords
Stores all text records
mapping(bytes32 node => mapping(string key => string value)) private textRecords
operators
Store all approval mapping
mapping(address owner => mapping(address operator => bool approved)) private operators
protocolRegistry
Protocol-level address registry for all DotNS contracts.
IDotnsProtocolRegistry public protocolRegistry
__gap
Reserved storage space to allow for layout changes in the future.
uint256[50] private __gap
Functions
constructor
Note: oz-upgrades-unsafe-allow: constructor
constructor() ;
initialize
Initialises the content resolver.
Runs once through the UUPS proxy; a repeat call reverts with
Note:
reverts: InvalidInitialization. Emits @custom:emits OwnershipTransferred when
msg.sender is recorded as the initial owner and @custom:emits Initialized once
setup completes.
function initialize(IDotnsProtocolRegistry registry) external initializer;
Parameters
| Name | Type | Description |
|---|---|---|
registry | IDotnsProtocolRegistry | Protocol-level address registry used to resolve sibling contracts. |
setContenthash
Sets the content hash for a node.
The caller must own the node in the DotNS registry or be an approved operator, otherwise @custom:reverts NotAuthorised. Content hashes are opaque bytes (e.g. an IPFS CID); the resolver stores them as-is and never interprets the payload. Emits
Note: emits: ContentHashUpdated on every successful write.
function setContenthash(bytes32 node, bytes calldata hash) external override;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node whose content hash is being set. |
hash | bytes | Opaque content hash bytes. |
contenthash
Returns the content hash associated with a node.
function contenthash(bytes32 node) external view override returns (bytes memory hash);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node to query. |
Returns
| Name | Type | Description |
|---|---|---|
hash | bytes | The stored content hash bytes, or empty if unset. |
setText
Sets a text record for a node.
The caller must own the node in the DotNS registry or be an approved operator,
otherwise @custom:reverts NotAuthorised. Text records are arbitrary key/value strings
(e.g. avatar, url, description). Emits @custom:emits TextUpdated on every
successful write.
function setText(bytes32 node, string calldata key, string calldata value) external override;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node whose text record is being set. |
key | string | Text record key (e.g., "ipfs", "avatar"). |
value | string | Text record value. |
text
Returns a text record for a node.
function text(
bytes32 node,
string calldata key
)
external
view
override
returns (string memory value);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node to query. |
key | string | Text record key. |
Returns
| Name | Type | Description |
|---|---|---|
value | string | Stored text value, or empty string if unset. |
setApprovalForAll
Enable or disable approval for a third party ("operator") to manage all of
msg.sender's nodes.
Emits @custom:emits ApprovalForAll whenever the approval flag is written, including idempotent writes that do not change the stored value.
function setApprovalForAll(address operator, bool approved) external override;
Parameters
| Name | Type | Description |
|---|---|---|
operator | address | Address to authorise or revoke. |
approved | bool | True to approve, false to revoke. |
isApprovedForAll
Query if an address is an approved operator for another address.
function isApprovedForAll(
address owner,
address operator
)
external
view
override
returns (bool);
Parameters
| Name | Type | Description |
|---|---|---|
owner | address | The owner of the nodes. |
operator | address | The address acting on behalf of the owner. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | True if operator is approved, false otherwise. |
_requireNodeOwnerOrOperator
Ensures the caller may write records for node.
Authority is granted to the node owner, to a resolver-local operator the owner has approved for all of their records, or to any address the registry deems authorised for the node. Delegating through the registry means a single registrar-level approval (ERC-721 owner / approved / operator-for-all) also confers record-write authority, while the resolver-local operator mapping remains a narrower record-only delegation that grants no power over ownership or transfers. The cheap owner and local-operator checks run before the cross-contract registry call.
function _requireNodeOwnerOrOperator(bytes32 node) internal view;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | Node identifier. |
version
Returns implementation version.
function version() external pure virtual returns (string memory versionString);
Returns
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
supportsInterface
function supportsInterface(bytes4 interfaceId) public view override returns (bool);
_authorizeUpgrade
Function that should revert when msg.sender is not authorized to upgrade the contract.
Called by
{upgradeToAndCall}.
Normally, this function will use an xref:access.adoc[access control] modifier such as
{Ownable-onlyOwner}.
function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;
DotnsPopResolver
Inherits: Initializable, UUPSUpgradeable, OwnableUpgradeable, ERC165Upgradeable, IDotnsPopResolver
Title: DotnsPopResolver
Per-node resolver holding records produced by the PoP username flow.
Writes are gated on the protocol-registered POP_CONTROLLER rather
than on node ownership. PoP records are issued by the gateway as part
of identity issuance, not curated by the holder, so authority lives
with the controller and not the user.
Note: security-contact: admin@parity.io
State Variables
protocolRegistry
Protocol-level address registry used to resolve the authorised writer.
IDotnsProtocolRegistry public protocolRegistry
_chatKeys
Stored chat-key bytes keyed by node.
mapping(bytes32 node => bytes chatKey) private _chatKeys
_liteLinks
Stored lite-person labelhash keyed by full-person node.
Forward direction (full => lite): maps a full-person node to the labelhash of the lite username it was claimed from.
mapping(bytes32 fullNode => bytes32 liteLabelhash) private _liteLinks
_fullClaims
Reverse index mapping a lite labelhash to the full-person node it was promoted to.
Written alongside _liteLinks on every claim so consumers that look
up by lite username resolve the full name without scanning events.
Zero when the lite label has never been linked to a full claim.
mapping(bytes32 liteLabelhash => bytes32 fullNode) private _fullClaims
__gap
Reserved storage space to allow for layout changes in the future.
uint256[50] private __gap
Functions
onlyPopController
Restricts writes to the address registered as POP_CONTROLLER.
modifier onlyPopController() ;
constructor
Note: oz-upgrades-unsafe-allow: constructor
constructor() ;
initialize
Initialises the PoP resolver.
Called once through the UUPS proxy; _disableInitializers on the implementation
makes direct calls revert and any repeat call on the proxy reverts with
Note:
reverts: InvalidInitialization. The registry pointer is the only storage this
setup needs because the authorised writer is resolved dynamically through
POP_CONTROLLER. Emits @custom:emits OwnershipTransferred when msg.sender is
recorded as the initial owner and @custom:emits Initialized once setup completes.
function initialize(IDotnsProtocolRegistry registry) external initializer;
Parameters
| Name | Type | Description |
|---|---|---|
registry | IDotnsProtocolRegistry | Protocol-level address registry used for writer resolution. |
setChatKey
Sets the chat key for node.
Callable only by the address registered under DotnsProtocolRegistry.POP_CONTROLLER,
otherwise @custom:reverts NotPopController. Overwrites any previous value. The payload
must be exactly 65 bytes: the uncompressed secp256k1 public key encoding (1 prefix
byte followed by the 32-byte X and 32-byte Y affine coordinates); any other length
reverts with @custom:reverts InvalidChatKeyLength. Emits @custom:emits ChatKeyUpdated
on every successful write.
function setChatKey(
bytes32 node,
bytes calldata chatKeyBytes
)
external
override
onlyPopController;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node whose chat key is being written. |
chatKeyBytes | bytes |
setLiteLink
Sets the lite-person link for a full-person node.
Callable only by the authorised PoP controller, otherwise
Notes:
-
reverts: NotPopController. Overwrites any previous link. When overwriting, the stale inverse entry is nulled so both the forward (
liteLink) and reverse (fullClaim) indices remain consistent: re-linking the samefullNodeto a newliteLabelhashclearsfullClaim(oldLite), and re-linking the sameliteLabelhashto a newfullNodeclearsliteLink(oldFull). The invariantfullClaim(liteLink(node)) == nodealways holds after the call. Emits -
emits: LiteLinkUpdated on every successful write.
function setLiteLink(
bytes32 fullNode,
bytes32 liteLabelhash
)
external
override
onlyPopController;
Parameters
| Name | Type | Description |
|---|---|---|
fullNode | bytes32 | The full-person node carrying the link. |
liteLabelhash | bytes32 | The labelhash of the linked lite-person username. |
chatKey
Returns the chat key associated with a node.
function chatKey(bytes32 node) external view override returns (bytes memory);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node to query. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bytes | chatKey The stored chat key bytes, or empty if unset. |
liteLink
Returns the lite-person labelhash linked to a full-person node.
function liteLink(bytes32 fullNode) external view override returns (bytes32);
Parameters
| Name | Type | Description |
|---|---|---|
fullNode | bytes32 | The full-person node to query. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bytes32 | liteLabelhash The linked lite-person labelhash, or zero if unset. |
fullClaim
Returns the full-person node a given lite label has claimed.
Reverse of @custom:function liteLink. Written by the same setLiteLink call so the
two directions stay in lockstep. Returns zero when the lite label
has never been linked to a full claim.
function fullClaim(bytes32 liteLabelhash) external view override returns (bytes32);
Parameters
| Name | Type | Description |
|---|---|---|
liteLabelhash | bytes32 | The labelhash of the lite-person username to query. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bytes32 | fullNode The full-person node claimed from this lite label, or zero if unset. |
version
Returns implementation version.
Bumped on every upgrade. Used by deployment scripts as a post-upgrade assertion target.
function version() external pure virtual returns (string memory versionString);
Returns
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
supportsInterface
function supportsInterface(bytes4 interfaceId) public view override returns (bool);
_onlyPopController
Internal check enforcing PoP-controller-only access.
function _onlyPopController() internal view;
_authorizeUpgrade
Function that should revert when msg.sender is not authorized to upgrade the contract.
Called by
{upgradeToAndCall}.
Normally, this function will use an xref:access.adoc[access control] modifier such as
{Ownable-onlyOwner}.
function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;
DotnsResolver
Inherits: Initializable, UUPSUpgradeable, OwnableUpgradeable, ERC165Upgradeable, IDotnsResolver
Title: Dotns Resolver
Stores forward-resolution address records for DotNS nodes
Writes are gated on node ownership in the forward registry, not on a privileged writer address. Address records describe where a name points and only the current node owner has the authority to set that target.
Note: security-contact: admin@parity.io
State Variables
protocolRegistry
Protocol-level address registry for all DotNS contracts.
IDotnsProtocolRegistry public protocolRegistry
__gap
Reserved storage space to allow for layout changes in the future.
uint256[50] private __gap
addresses
Node => resolved address.
mapping(bytes32 node => address owner) private addresses
Functions
onlyNodeOwner
Restricts access to the owner of node as recorded in the registry.
modifier onlyNodeOwner(bytes32 node) ;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | Node identifier. |
constructor
Note: oz-upgrades-unsafe-allow: constructor
constructor() ;
initialize
Initialises the resolver.
Runs once through the UUPS proxy; a repeat call reverts with
Note:
reverts: InvalidInitialization. Emits @custom:emits OwnershipTransferred when
msg.sender is recorded as the initial owner and @custom:emits Initialized once
setup completes.
function initialize(IDotnsProtocolRegistry registry) external initializer;
Parameters
| Name | Type | Description |
|---|---|---|
registry | IDotnsProtocolRegistry | Protocol-level address registry used to resolve sibling contracts. |
setAddress
Sets the resolved address for a node.
The caller must be the current owner of node in the forward registry, otherwise
Note: reverts: NotAuthorised. Emits @custom:emits AddressSet on every successful write.
function setAddress(bytes32 node, address value) external override onlyNodeOwner(node);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node identifier. |
value | address | The address to associate with the node. |
addressOf
Returns the resolved address for a node.
function addressOf(bytes32 node) external view override returns (address value);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node identifier. |
Returns
| Name | Type | Description |
|---|---|---|
value | address | The resolved address, or zero if unset. |
supportsInterface
function supportsInterface(bytes4 interfaceId) public view override returns (bool);
_onlyNodeOwner
Internal ownership check for a registry node.
Resolves the registry lazily through protocolRegistry so a registry
upgrade or rewire is picked up automatically without a resolver upgrade.
function _onlyNodeOwner(bytes32 node) internal view;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | Node identifier. |
version
Returns implementation version.
function version() external pure virtual returns (string memory versionString);
Returns
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
_authorizeUpgrade
Function that should revert when msg.sender is not authorized to upgrade the contract.
Called by
{upgradeToAndCall}.
Normally, this function will use an xref:access.adoc[access control] modifier such as
{Ownable-onlyOwner}.
function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;
DotnsReverseResolver
Inherits: Initializable, UUPSUpgradeable, OwnableUpgradeable, ERC165Upgradeable, IDotnsReverseResolver
Title: Dotns Reverse Resolver
Resolves an address to its associated name under the network TLD.
Writes are gated on a fixed writer address resolved from the protocol registry (the registrar or its controller), not on node ownership. Reverse records bind to an EOA rather than a registry node, so authority is delegated to the contract that mints names on the user's behalf.
Note: security-contact: admin@parity.io
State Variables
reverseNames
Mapping from address to its reverse name. An empty string indicates that no reverse name is set.
mapping(address owner => string name) private reverseNames
protocolRegistry
Protocol-level address registry for all DotNS contracts.
IDotnsProtocolRegistry public protocolRegistry
__gap
Reserved storage space to allow for layout changes in the future.
uint256[50] private __gap
Functions
onlyRegistrar
Restricts access to the configured registrar.
modifier onlyRegistrar() ;
constructor
Note: oz-upgrades-unsafe-allow: constructor
constructor() ;
initialize
Initialises the reverse resolver.
May only be called once per proxy; a repeat call reverts with
Note:
reverts: InvalidInitialization. Emits @custom:emits OwnershipTransferred when
msg.sender is recorded as the initial owner and @custom:emits Initialized once
setup completes.
function initialize(IDotnsProtocolRegistry registry) external initializer;
Parameters
| Name | Type | Description |
|---|---|---|
registry | IDotnsProtocolRegistry | Protocol-level address registry used to resolve sibling contracts. |
setReverseName
Associates an address with a reverse name record.
Callable only by the configured registrar or its controller, otherwise
Note:
reverts: NotRegistrarController. Overwrites any existing reverse record for
addr and emits @custom:emits ReverseNameSet on every successful write.
function setReverseName(address addr, string calldata name) external override onlyRegistrar;
Parameters
| Name | Type | Description |
|---|---|---|
addr | address | The address for which the reverse name is being set. |
name | string | The human-readable name associated with the address. |
claimReverseRecord
Self-service claim: associates msg.sender with <label> under the network TLD.
The caller must currently own the NFT for label per the configured registrar,
otherwise @custom:reverts NotNameOwner. Overwrites any existing record for the caller
and emits @custom:emits ReverseNameSet on every successful write. Transferring the
name away does not eagerly clear the record; @custom:function nameOf fails closed at
read time when the stored record no longer matches current ownership.
function claimReverseRecord(string calldata label) external override;
Parameters
| Name | Type | Description |
|---|---|---|
label | string | The label (without the TLD suffix) the caller is claiming a reverse record for. |
nameOf
Returns the reverse name for an address, fail-closed against current ownership.
Returns the empty string when no record is set, when the record is malformed, or when the address no longer owns the name pointed to by the stored record.
function nameOf(address addr) external view override returns (string memory name);
Parameters
| Name | Type | Description |
|---|---|---|
addr | address | The address to query. |
Returns
| Name | Type | Description |
|---|---|---|
name | string | The reverse name associated with addr, or the empty string. |
supportsInterface
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC165Upgradeable)
returns (bool supported);
_onlyRegistrar
Internal check enforcing registrar-only access.
function _onlyRegistrar() internal view;
version
Returns implementation version.
function version() external pure virtual returns (string memory versionString);
Returns
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
_authorizeUpgrade
Function that should revert when msg.sender is not authorized to upgrade the contract.
Called by
{upgradeToAndCall}.
Normally, this function will use an xref:access.adoc[access control] modifier such as
{Ownable-onlyOwner}.
function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;
IDotnsContentResolver
Title: Dotns Content Resolver
Defines storage and retrieval for content hash, text records, and operator approvals for DotNS nodes. @dev Content hash and text records point to off-chain content such as IPFS CIDs or future schemes; interpretation is handled off-chain. Operator approvals allow third parties to manage records on behalf of the owner.
Note: security-contact: admin@parity.io
Functions
setContenthash
Sets the content hash for a node.
The caller must own the node in the DotNS registry or be an approved operator, otherwise @custom:reverts NotAuthorised. Content hashes are opaque bytes (e.g. an IPFS CID); the resolver stores them as-is and never interprets the payload. Emits
Note: emits: ContentHashUpdated on every successful write.
function setContenthash(bytes32 node, bytes calldata hash) external;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node whose content hash is being set. |
hash | bytes | Opaque content hash bytes. |
contenthash
Returns the content hash associated with a node.
function contenthash(bytes32 node) external view returns (bytes memory hash);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node to query. |
Returns
| Name | Type | Description |
|---|---|---|
hash | bytes | The stored content hash bytes, or empty if unset. |
setText
Sets a text record for a node.
The caller must own the node in the DotNS registry or be an approved operator,
otherwise @custom:reverts NotAuthorised. Text records are arbitrary key/value strings
(e.g. avatar, url, description). Emits @custom:emits TextUpdated on every
successful write.
function setText(bytes32 node, string calldata key, string calldata value) external;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node whose text record is being set. |
key | string | Text record key (e.g., "ipfs", "avatar"). |
value | string | Text record value. |
text
Returns a text record for a node.
function text(bytes32 node, string calldata key) external view returns (string memory value);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node to query. |
key | string | Text record key. |
Returns
| Name | Type | Description |
|---|---|---|
value | string | Stored text value, or empty string if unset. |
setApprovalForAll
Enable or disable approval for a third party ("operator") to manage all of
msg.sender's nodes.
Emits @custom:emits ApprovalForAll whenever the approval flag is written, including idempotent writes that do not change the stored value.
function setApprovalForAll(address operator, bool approved) external;
Parameters
| Name | Type | Description |
|---|---|---|
operator | address | Address to authorise or revoke. |
approved | bool | True to approve, false to revoke. |
isApprovedForAll
Query if an address is an approved operator for another address.
function isApprovedForAll(address owner, address operator) external view returns (bool);
Parameters
| Name | Type | Description |
|---|---|---|
owner | address | The owner of the nodes. |
operator | address | The address acting on behalf of the owner. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | True if operator is approved, false otherwise. |
Events
ContentHashUpdated
Emitted when a node's content hash is updated.
event ContentHashUpdated(bytes32 indexed node, bytes hash);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node whose content hash was updated. |
hash | bytes | The new content hash bytes. |
TextUpdated
Emitted when a node's text record is updated.
event TextUpdated(bytes32 indexed node, string indexed key, string value);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node whose text record was updated. |
key | string | The text record key. |
value | string | The new text record value. |
ApprovalForAll
Emitted when an operator is approved or revoked.
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
Parameters
| Name | Type | Description |
|---|---|---|
owner | address | The owner of the nodes. |
operator | address | The operator address. |
approved | bool | True if approved, false if revoked. |
Errors
NotAuthorised
Thrown when the caller is not authorised to modify a node.
error NotAuthorised(bytes32 node, address caller);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node being modified. |
caller | address | The address attempting the modification. |
IDotnsPopResolver
Title: IDotnsPopResolver
Resolver for per-name records produced by the PoP username flow.
Holds three record kinds:
- Chat key: ECDH public-key bytes used for end-to-end encrypted messaging.
- Lite link: for a full-person node, the labelhash of the lite-person username it was minted from (when the link was made).
- Full claim: reverse index mapping a lite labelhash to the full-person
node it was promoted to. Mirrors
liteLinkon every write so a caller that holds a lite labelhash can resolve the full-person node without scanning events. Lives separately from the per-userLabelStoreso that the store can remain a labels-only, protocol-write / user-read surface, and follows the project's resolver-per-record-category convention used by @custom:contract IDotnsContentResolver and
Notes:
-
contract: IDotnsReverseResolver. Write authorisation is delegated to the address registered as
DotnsProtocolRegistry.POP_CONTROLLERat call time, so rotating the PoP controller is a singleseton the protocol registry with no resolver upgrade required. -
security-contact: admin@parity.io
Functions
setChatKey
Sets the chat key for node.
Callable only by the address registered under DotnsProtocolRegistry.POP_CONTROLLER,
otherwise @custom:reverts NotPopController. Overwrites any previous value. The payload
must be exactly 65 bytes: the uncompressed secp256k1 public key encoding (1 prefix
byte followed by the 32-byte X and 32-byte Y affine coordinates); any other length
reverts with @custom:reverts InvalidChatKeyLength. Emits @custom:emits ChatKeyUpdated
on every successful write.
function setChatKey(bytes32 node, bytes calldata chatKey) external;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node whose chat key is being written. |
chatKey | bytes | ECDH public key bytes (pallet-side type is [u8; 65]). |
setLiteLink
Sets the lite-person link for a full-person node.
Callable only by the authorised PoP controller, otherwise
Notes:
-
reverts: NotPopController. Overwrites any previous link. When overwriting, the stale inverse entry is nulled so both the forward (
liteLink) and reverse (fullClaim) indices remain consistent: re-linking the samefullNodeto a newliteLabelhashclearsfullClaim(oldLite), and re-linking the sameliteLabelhashto a newfullNodeclearsliteLink(oldFull). The invariantfullClaim(liteLink(node)) == nodealways holds after the call. Emits -
emits: LiteLinkUpdated on every successful write.
function setLiteLink(bytes32 fullNode, bytes32 liteLabelhash) external;
Parameters
| Name | Type | Description |
|---|---|---|
fullNode | bytes32 | The full-person node carrying the link. |
liteLabelhash | bytes32 | The labelhash of the linked lite-person username. |
chatKey
Returns the chat key associated with a node.
function chatKey(bytes32 node) external view returns (bytes memory chatKey);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node to query. |
Returns
| Name | Type | Description |
|---|---|---|
chatKey | bytes | The stored chat key bytes, or empty if unset. |
liteLink
Returns the lite-person labelhash linked to a full-person node.
function liteLink(bytes32 fullNode) external view returns (bytes32 liteLabelhash);
Parameters
| Name | Type | Description |
|---|---|---|
fullNode | bytes32 | The full-person node to query. |
Returns
| Name | Type | Description |
|---|---|---|
liteLabelhash | bytes32 | The linked lite-person labelhash, or zero if unset. |
fullClaim
Returns the full-person node a given lite label has claimed.
Reverse of @custom:function liteLink. Written by the same setLiteLink call so the
two directions stay in lockstep. Returns zero when the lite label
has never been linked to a full claim.
function fullClaim(bytes32 liteLabelhash) external view returns (bytes32 fullNode);
Parameters
| Name | Type | Description |
|---|---|---|
liteLabelhash | bytes32 | The labelhash of the lite-person username to query. |
Returns
| Name | Type | Description |
|---|---|---|
fullNode | bytes32 | The full-person node claimed from this lite label, or zero if unset. |
Events
ChatKeyUpdated
Emitted when a node's chat key is set or updated.
event ChatKeyUpdated(bytes32 indexed node, bytes chatKey);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node whose chat key was written. |
chatKey | bytes | The new chat key bytes. |
LiteLinkUpdated
Emitted when a full-person node's lite link is set or updated.
event LiteLinkUpdated(bytes32 indexed fullNode, bytes32 indexed liteLabelhash);
Parameters
| Name | Type | Description |
|---|---|---|
fullNode | bytes32 | The full-person node carrying the link. |
liteLabelhash | bytes32 | The labelhash of the linked lite-person username. |
Errors
NotPopController
Thrown when the caller is not the authorised PoP controller.
error NotPopController(address caller);
Parameters
| Name | Type | Description |
|---|---|---|
caller | address | The address that attempted the write. |
InvalidChatKeyLength
Thrown when the provided chat key does not match the expected 65-byte length.
error InvalidChatKeyLength(uint256 length);
Parameters
| Name | Type | Description |
|---|---|---|
length | uint256 | The length of the payload that was rejected. |
IDotnsResolver
Title: Dotns Resolver
Defines forward-resolution address records for DotNS nodes.
Forward-address records describe where a name points. Authority therefore follows node ownership in the forward registry, not a privileged writer.
Note: security-contact: admin@parity.io
Functions
setAddress
Sets the resolved address for a node.
The caller must be the current owner of node in the forward registry, otherwise
Note: reverts: NotAuthorised. Emits @custom:emits AddressSet on every successful write.
function setAddress(bytes32 node, address value) external;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node identifier. |
value | address | The address to associate with the node. |
addressOf
Returns the resolved address for a node.
function addressOf(bytes32 node) external view returns (address value);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node identifier. |
Returns
| Name | Type | Description |
|---|---|---|
value | address | The resolved address, or zero if unset. |
Events
AddressSet
Emitted when an address record is updated.
event AddressSet(bytes32 indexed node, address value);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node whose address record changed. |
value | address | The new resolved address. |
Errors
NotAuthorised
Thrown when a caller is not authorised to modify a node.
error NotAuthorised(bytes32 node, address caller);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node being modified. |
caller | address | The address attempting the modification. |
IDotnsReverseResolver
Title: Dotns Reverse Resolver
Interface for writing and reading reverse name records for addresses.
Reverse records bind to an EOA rather than a registry node. Two write paths exist: a registrar-only setter used by the controller during reserved registration, and a self-service claim path callable by the current NFT owner. Reads are fail-closed: if the stored record no longer maps to a name owned by the address, @custom:function nameOf returns the empty string.
Note: security-contact: admin@parity.io
Functions
setReverseName
Associates an address with a reverse name record.
Callable only by the configured registrar or its controller, otherwise
Note:
reverts: NotRegistrarController. Overwrites any existing reverse record for
addr and emits @custom:emits ReverseNameSet on every successful write.
function setReverseName(address addr, string calldata name) external;
Parameters
| Name | Type | Description |
|---|---|---|
addr | address | The address for which the reverse name is being set. |
name | string | The human-readable name associated with the address. |
claimReverseRecord
Self-service claim: associates msg.sender with <label> under the network TLD.
The caller must currently own the NFT for label per the configured registrar,
otherwise @custom:reverts NotNameOwner. Overwrites any existing record for the caller
and emits @custom:emits ReverseNameSet on every successful write. Transferring the
name away does not eagerly clear the record; @custom:function nameOf fails closed at
read time when the stored record no longer matches current ownership.
function claimReverseRecord(string calldata label) external;
Parameters
| Name | Type | Description |
|---|---|---|
label | string | The label (without the TLD suffix) the caller is claiming a reverse record for. |
nameOf
Returns the reverse name for an address, fail-closed against current ownership.
Returns the empty string when no record is set, when the record is malformed, or when the address no longer owns the name pointed to by the stored record.
function nameOf(address addr) external view returns (string memory name);
Parameters
| Name | Type | Description |
|---|---|---|
addr | address | The address to query. |
Returns
| Name | Type | Description |
|---|---|---|
name | string | The reverse name associated with addr, or the empty string. |
Events
ReverseNameSet
Emitted when a name is associated with an address.
event ReverseNameSet(address indexed addr, string indexed name);
Parameters
| Name | Type | Description |
|---|---|---|
addr | address | The address for which the reverse name is being set. |
name | string | The human-readable name associated with the address. |
Errors
NotRegistrarController
Thrown when a caller is not authorised to modify reverse records.
error NotRegistrarController(address caller);
Parameters
| Name | Type | Description |
|---|---|---|
caller | address | The address attempting the modification. |
NotNameOwner
Thrown when a caller attempts to claim a reverse record for a name they do not own.
error NotNameOwner(address caller, uint256 tokenId);
Parameters
| Name | Type | Description |
|---|---|---|
caller | address | The address attempting the claim. |
tokenId | uint256 | The token identifier derived from the claimed label. |
Contents
IDotnsStore
Title: IDotnsStore
Baseline interface implemented by every per-user DotNS store.
Marker interface shared by ILabelStore (protocol-written, labels-only) and
IUserStore (user-written, generic key/value). Every DotNS store type binds
to exactly one user forever, so owner() is the single shared surface.
Identity of a store is proven by its position in the StoreFactory mapping,
not by interface probing; the factory is the canonical source of truth.
Shared base so the factory and any cross-store consumer can prove which user a store
is bound to via a single uniform owner() call regardless of the concrete store type.
Note: security-contact: admin@parity.io
Functions
owner
Returns the permanent user this store is bound to.
Set once at initialize and never mutated; used as the binding-mismatch oracle
and immutable thereafter.
function owner() external view returns (address owner_);
Returns
| Name | Type | Description |
|---|---|---|
owner_ | address | The bound user address. |
ILabelStore
Inherits: IDotnsStore
Title: ILabelStore
Interface for the per-user DotNS label store.
The LabelStore is the protocol-managed half of the per-user storage pair:
write-only by addresses registered in the protocol registry, read-only by
everyone else, and permanently locked per labelhash on first write. It
holds registration records only; every other per-name category (reverse,
content, forward address, chat key, lite link) lives on a dedicated
resolver, not here.
Note: security-contact: admin@parity.io
Functions
initialize
Initialises the store, binding it permanently to user_ and protocolRegistry_.
Callable exactly once via Initializable; both parameters are immutable post-call.
user_ must be non-zero, otherwise @custom:reverts InvalidUser.
protocolRegistry_ must be non-zero, otherwise @custom:reverts
InvalidProtocolRegistry. @param user_ The user this store is bound to forever.
function initialize(address user_, address protocolRegistry_) external;
Parameters
| Name | Type | Description |
|---|---|---|
user_ | address | |
protocolRegistry_ | address | The protocol registry used to authorise writers. |
storeLabel
Records a label under labelhash and locks the slot permanently.
Gated to addresses currently registered in the protocol registry, otherwise
Notes:
-
reverts: NotAuthorised.
labelhashmust be non-zero, otherwise -
reverts: InvalidLabel. The slot must not already hold an entry, otherwise
-
reverts: LabelAlreadyExists; the write is permanent so any second call reverts. Emits @custom:emits LabelStored on the single successful write.
function storeLabel(bytes32 labelhash, string calldata label) external;
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The labelhash key. |
label | string | The label string to store. |
protocolRegistry
Returns the protocol registry this store queries for write authorisation.
function protocolRegistry() external view returns (address protocolRegistry_);
Returns
| Name | Type | Description |
|---|---|---|
protocolRegistry_ | address | The registry address. |
hasLabel
Returns true iff a label has been stored under labelhash.
function hasLabel(bytes32 labelhash) external view returns (bool exists);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The labelhash to check. |
Returns
| Name | Type | Description |
|---|---|---|
exists | bool | True iff the slot holds a label. |
isLocked
Returns true iff the slot for labelhash is permanently locked.
Always equal to hasLabel in the current design; exposed explicitly so future
implementations behind the beacon can distinguish "stored" from "locked" if needed.
function isLocked(bytes32 labelhash) external view returns (bool locked);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The labelhash to check. |
Returns
| Name | Type | Description |
|---|---|---|
locked | bool | True iff the slot is locked. |
getLabel
Returns the stored label for labelhash, or the empty string if none.
function getLabel(bytes32 labelhash) external view returns (string memory label);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The labelhash to look up. |
Returns
| Name | Type | Description |
|---|---|---|
label | string | The stored label string. |
getLabelCount
Returns the total number of labels ever stored.
function getLabelCount() external view returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Current length of the insertion-order list. |
getLabelAt
Returns the human-readable label at the given insertion-order index.
Primary read for "give me my names"; does not require the caller to know any labelhash. For the underlying labelhash key see @custom:function getLabelhashAt.
function getLabelAt(uint256 index) external view returns (string memory label);
Parameters
| Name | Type | Description |
|---|---|---|
index | uint256 | Zero-based index into the insertion-order list. |
Returns
| Name | Type | Description |
|---|---|---|
label | string | The stored label string at index. |
getLabelhashAt
Returns the labelhash at the given insertion-order index.
function getLabelhashAt(uint256 index) external view returns (bytes32 labelhash);
Parameters
| Name | Type | Description |
|---|---|---|
index | uint256 | Zero-based index into the insertion-order list. |
Returns
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The labelhash at index. |
getLabels
Paginated read returning just the stored labels, in insertion order.
Primary bulk read for "give me all my names". Callers never need to touch
labelhashes. Length is min(limit, getLabelCount() - offset);
offset >= getLabelCount() returns an empty array (not a revert).
function getLabels(uint256 offset, uint256 limit) external view returns (string[] memory labels);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
labels | string[] | Slice of label strings. |
getLabelhashes
Paginated read over the labelhash keys, in insertion order.
Advanced read for callers that need the raw labelhash keys. Symmetric with
Note: function: getLabels; same indices map to the same entries.
function getLabelhashes(
uint256 offset,
uint256 limit
)
external
view
returns (bytes32[] memory labelhashes);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
labelhashes | bytes32[] | Slice of labelhash keys. |
Events
LabelStored
Emitted when a label is stored for the first (and only) time under a given labelhash. @param owner The user this store is bound to.
event LabelStored(address indexed owner, bytes32 indexed labelhash, string label);
Parameters
| Name | Type | Description |
|---|---|---|
owner | address | |
labelhash | bytes32 | The labelhash key. |
label | string | The stored label string (typically the full name, e.g. "alice.dot"). |
Errors
NotAuthorised
Thrown when a caller that is not currently protocol-registered attempts a write.
error NotAuthorised(address caller);
Parameters
| Name | Type | Description |
|---|---|---|
caller | address | The msg.sender that failed the isRegisteredAddress check. |
InvalidUser
Thrown when initialize is called with a zero user address.
error InvalidUser(address user);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The invalid user argument. |
InvalidProtocolRegistry
Thrown when initialize is called with a zero protocol registry address.
error InvalidProtocolRegistry(address protocolRegistry);
Parameters
| Name | Type | Description |
|---|---|---|
protocolRegistry | address | The invalid registry argument. |
InvalidLabel
Thrown when storeLabel is called with a zero labelhash.
error InvalidLabel(bytes32 labelhash);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The invalid labelhash argument. |
LabelAlreadyExists
Thrown when storeLabel is called for a labelhash already present in the index.
Labels are write-once and permanently locked on first store, so any second write for the same labelhash fails with this error regardless of caller or session.
error LabelAlreadyExists(bytes32 labelhash);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The conflicting labelhash. |
IStoreFactory
Title: IStoreFactory
Interface for the DotNS per-user store factory.
Owns two UpgradeableBeacon instances; one for LabelStore (protocol-managed),
one for UserStore (user-claimed). Each user may acquire at most one of each,
forever. There is no transfer, no redeploy, no additional store type.
Note: security-contact: admin@parity.io
Functions
labelStoreBeacon
Returns the UpgradeableBeacon address backing all LabelStore proxies.
function labelStoreBeacon() external view returns (address beacon);
Returns
| Name | Type | Description |
|---|---|---|
beacon | address | Address of the beacon contract. |
userStoreBeacon
Returns the UpgradeableBeacon address backing all UserStore proxies.
function userStoreBeacon() external view returns (address beacon);
Returns
| Name | Type | Description |
|---|---|---|
beacon | address | Address of the beacon contract. |
protocolRegistry
Returns the protocol registry address used for writer authorisation.
function protocolRegistry() external view returns (address registry);
Returns
| Name | Type | Description |
|---|---|---|
registry | address | Address of the protocol registry. |
deployLabelStoreFor
Deploys a LabelStore beacon-proxy bound to user.
Callable by the factory owner or any address currently registered in the protocol
registry; any other caller @custom:reverts NotAuthorised. user must be non-zero,
otherwise @custom:reverts InvalidUser. The user must not already have a
LabelStore, otherwise @custom:reverts AlreadyDeployed. After deployment the
freshly initialised proxy must report user as its owner, otherwise
Notes:
-
reverts: ImplementationBindingMismatch. Emits
-
emits: LabelStoreDeployed on success.
function deployLabelStoreFor(address user) external returns (address store);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The user the store is bound to forever. |
Returns
| Name | Type | Description |
|---|---|---|
store | address | The deployed store address. |
getLabelStore
Returns the LabelStore address bound to user, or the zero address if none.
function getLabelStore(address user) external view returns (address store);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The user to look up. |
Returns
| Name | Type | Description |
|---|---|---|
store | address | The bound store address, or zero. |
getLabelStoreCount
Returns the total number of LabelStore proxies ever deployed.
function getLabelStoreCount() external view returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Length of the deployment list. |
getLabelStores
Paginated enumeration over every LabelStore proxy ever deployed.
Insertion order of deployLabelStoreFor calls. offset >= getLabelStoreCount()
returns an empty array; result length is min(limit, count - offset).
function getLabelStores(
uint256 offset,
uint256 limit
)
external
view
returns (address[] memory stores);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
stores | address[] | Slice of label-store addresses. |
upgradeLabelStoreImplementation
Upgrades the LabelStore implementation for every existing and future proxy.
Callable by the factory owner only, otherwise
Notes:
-
reverts: OwnableUnauthorizedAccount.
newImplementationmust be non-zero, otherwise @custom:reverts InvalidImplementation. The candidate is sentinel-probed by callingILabelStore.protocolRegistryon it before the beacon is rotated; if the address does not implement that selector the probe reverts and the upgrade does not land (deliberate fail-fast guard, no named error). Delegates toUpgradeableBeacon.upgradeToand emits -
emits: LabelStoreImplementationUpgraded on success.
function upgradeLabelStoreImplementation(address newImplementation) external;
Parameters
| Name | Type | Description |
|---|---|---|
newImplementation | address | The new implementation address. |
claimUserStore
Caller claims their UserStore beacon-proxy.
Self-claim only; _owner on the resulting store is always msg.sender,
regardless of who pays gas. One store per caller, forever: a caller who already
has a UserStore @custom:reverts AlreadyDeployed. After deployment the freshly
initialised proxy must report msg.sender as its owner, otherwise
Notes:
-
reverts: ImplementationBindingMismatch. Emits
-
emits: UserStoreClaimed on success.
function claimUserStore() external returns (address store);
Returns
| Name | Type | Description |
|---|---|---|
store | address | The deployed store address. |
getUserStore
Returns the UserStore address bound to user, or the zero address if none.
function getUserStore(address user) external view returns (address store);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The user to look up. |
Returns
| Name | Type | Description |
|---|---|---|
store | address | The bound store address, or zero. |
getUserStoreCount
Returns the total number of UserStore proxies ever claimed.
function getUserStoreCount() external view returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Length of the claim list. |
getUserStores
Paginated enumeration over every UserStore proxy ever claimed.
Insertion order of claimUserStore calls. offset >= getUserStoreCount()
returns an empty array; result length is min(limit, count - offset).
function getUserStores(
uint256 offset,
uint256 limit
)
external
view
returns (address[] memory stores);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
stores | address[] | Slice of user-store addresses. |
upgradeUserStoreImplementation
Upgrades the UserStore implementation for every existing and future proxy.
Callable by the factory owner only, otherwise
Notes:
-
reverts: OwnableUnauthorizedAccount.
newImplementationmust be non-zero, otherwise @custom:reverts InvalidImplementation. The candidate is sentinel-probed by callingIUserStore.getKeyCounton it before the beacon is rotated; if the address does not implement that selector the probe reverts and the upgrade does not land (deliberate fail-fast guard, no named error). Delegates toUpgradeableBeacon.upgradeToand emits -
emits: UserStoreImplementationUpgraded on success.
function upgradeUserStoreImplementation(address newImplementation) external;
Parameters
| Name | Type | Description |
|---|---|---|
newImplementation | address | The new implementation address. |
Events
LabelStoreDeployed
Emitted when a LabelStore beacon-proxy is deployed for user.
event LabelStoreDeployed(address indexed user, address indexed store);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The user the store is bound to. |
store | address | The deployed store address. |
UserStoreClaimed
Emitted when user claims their UserStore beacon-proxy.
event UserStoreClaimed(address indexed user, address indexed store);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The user the store is bound to. |
store | address | The deployed store address. |
LabelStoreImplementationUpgraded
Emitted when the LabelStore implementation behind the label beacon is upgraded.
event LabelStoreImplementationUpgraded(address indexed newImplementation);
Parameters
| Name | Type | Description |
|---|---|---|
newImplementation | address | The new implementation address. |
UserStoreImplementationUpgraded
Emitted when the UserStore implementation behind the user beacon is upgraded.
event UserStoreImplementationUpgraded(address indexed newImplementation);
Parameters
| Name | Type | Description |
|---|---|---|
newImplementation | address | The new implementation address. |
Errors
AlreadyDeployed
Thrown when attempting to deploy or claim a store that already exists.
error AlreadyDeployed(address user, address existingStore);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The user for whom the store exists. |
existingStore | address | The already-deployed store address. |
InvalidUser
Thrown when a zero user address is supplied.
error InvalidUser(address user);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The invalid user argument. |
InvalidProtocolRegistry
Thrown when a zero protocol registry address is supplied to the constructor.
error InvalidProtocolRegistry(address protocolRegistry);
Parameters
| Name | Type | Description |
|---|---|---|
protocolRegistry | address | The invalid registry argument. |
InvalidImplementation
Thrown when a zero implementation address is supplied to the constructor or an upgrade. @param implementation The invalid implementation argument.
error InvalidImplementation(address implementation);
NotAuthorised
Thrown when an unauthorised address attempts to deploy a label store.
error NotAuthorised(address caller);
Parameters
| Name | Type | Description |
|---|---|---|
caller | address | The unauthorised msg.sender. |
ImplementationBindingMismatch
Thrown when a freshly deployed proxy does not report the expected owner.
error ImplementationBindingMismatch();
IUserStore
Inherits: IDotnsStore
Title: IUserStore
Interface for the per-user generic key/value store with per-key history.
Each user may claim at most one UserStore from StoreFactory. The store is
bound to its claimer forever: _owner is set once at initialize and only that
address may write. Each setValue snapshots the prior non-empty value into a
per-key history list with a timestamp; current and history are both readable by
anyone, paginated.
Note: security-contact: admin@parity.io
Functions
initialize
Initialises the store, binding it permanently to user_.
Callable exactly once via Initializable. user_ must be non-zero, otherwise
Note: reverts: InvalidUser.
function initialize(address user_) external;
Parameters
| Name | Type | Description |
|---|---|---|
user_ | address | The user this store is bound to forever. |
setValue
Sets the current value for key.
Callable only by the bound owner; any other caller @custom:reverts NotOwner.
key must be non-zero, otherwise @custom:reverts InvalidKey. If a non-empty
prior value existed it is pushed into the per-key history list with
block.timestamp; empty prior values produce no history entry. Emits
Note: emits: ValueSet on every successful write.
function setValue(bytes32 key, bytes calldata value) external;
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to write. |
value | bytes | The new current value (may be empty). |
getValue
Returns the current value under key, or empty bytes if unset.
function getValue(bytes32 key) external view returns (bytes memory value);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to read. |
Returns
| Name | Type | Description |
|---|---|---|
value | bytes | The current value. |
hasValue
Returns true iff the current value under key has non-zero length.
function hasValue(bytes32 key) external view returns (bool present);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to check. |
Returns
| Name | Type | Description |
|---|---|---|
present | bool | True iff getValue(key).length != 0. |
getHistoryCount
Returns the number of prior (historical) values recorded for key.
function getHistoryCount(bytes32 key) external view returns (uint256 count);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to read. |
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Length of the history list. |
getHistoryAt
Returns the historical entry at index for key.
function getHistoryAt(bytes32 key, uint256 index) external view returns (Entry memory entry);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to read. |
index | uint256 | Zero-based index into the history list. |
Returns
| Name | Type | Description |
|---|---|---|
entry | Entry | The (value, timestamp) pair. |
getHistory
Paginated read over the per-key history list.
offset >= getHistoryCount(key) returns an empty array. Length is
min(limit, getHistoryCount(key) - offset).
function getHistory(
bytes32 key,
uint256 offset,
uint256 limit
)
external
view
returns (Entry[] memory entries);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to read. |
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
entries | Entry[] | Slice of history entries. |
getKeyCount
Returns the number of distinct keys ever written.
function getKeyCount() external view returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Length of the key-insertion list. |
getKeyAt
Returns the key at the given insertion-order index.
function getKeyAt(uint256 index) external view returns (bytes32 key);
Parameters
| Name | Type | Description |
|---|---|---|
index | uint256 | Zero-based index into the key list. |
Returns
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key at index. |
getKeys
Paginated read over the insertion-order key list.
offset >= getKeyCount() returns an empty array. Length is
min(limit, getKeyCount() - offset).
function getKeys(uint256 offset, uint256 limit) external view returns (bytes32[] memory keys);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
keys | bytes32[] | Slice of keys. |
Events
ValueSet
Emitted when the owner sets (or updates) a value under key.
event ValueSet(address indexed owner, bytes32 indexed key, bytes value);
Parameters
| Name | Type | Description |
|---|---|---|
owner | address | The user this store is bound to. |
key | bytes32 | The key being written. |
value | bytes | The new current value. |
Errors
NotOwner
Thrown when any caller other than the bound owner attempts a write.
error NotOwner(address caller);
Parameters
| Name | Type | Description |
|---|---|---|
caller | address | The unauthorised msg.sender. |
InvalidUser
Thrown when initialize is called with a zero user address.
error InvalidUser(address user);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The invalid user argument. |
InvalidKey
Thrown when setValue is called with a zero key.
error InvalidKey();
Structs
Entry
A historical (prior) value for a key and the block timestamp at which it was snapshotted. @param value The prior value that was just superseded.
struct Entry {
bytes value;
uint256 timestamp;
}
Properties
| Name | Type | Description |
|---|---|---|
value | bytes | |
timestamp | uint256 | Block timestamp at the moment of supersession. |
LabelStore
Inherits: Initializable, ILabelStore
Title: LabelStore
Permanent per-user DotNS label store.
One instance per user, deployed as a BeaconProxy by StoreFactory during registration.
Bound to its user forever: _owner and _protocolRegistry are set once at initialize
and never mutate. Writes are gated to addresses currently registered in
DotnsProtocolRegistry (isRegisteredAddress); every labelhash is single-write and
permanently locked on first use.
Labels-only by invariant: this store holds registration records only. Every other per-name category (reverse, content, forward address, chat key, lite link) lives on a dedicated resolver, never here.
Storage collision: the BeaconProxy stores the beacon address at EIP-1967 slot
keccak256("eip1967.proxy.beacon") - 1, which is non-sequential and cannot collide
with this contract's sequential storage slots.
Note: security-contact: admin@parity.io
State Variables
_owner
Permanent user this store belongs to. Set in initialize.
address private _owner
_protocolRegistry
Canonical DotNS protocol registry. Set in initialize.
address private _protocolRegistry
_labels
labelhash => stored label string.
mapping(bytes32 labelhash => string label) private _labels
_labelList
Insertion-order list of all stored labelhashes. Append-only.
bytes32[] private _labelList
_labelIndex
labelhash => 1-indexed position in _labelList (zero means "not present").
Doubles as the permanent-lock sentinel: a non-zero index proves the label was written and
the contract has no deletion path, so the index is also the locked flag.
mapping(bytes32 labelhash => uint256 indexPlusOne) private _labelIndex
__gap
Reserved storage space to allow for layout changes in future beacon upgrades.
uint256[50] private __gap
Functions
onlyAuthorisedProtocol
Restricts writes to protocol-registered addresses only.
modifier onlyAuthorisedProtocol() ;
constructor
Note: oz-upgrades-unsafe-allow: constructor
constructor() ;
initialize
Initialises the store, binding it permanently to user_ and protocolRegistry_.
Callable exactly once via Initializable; both parameters are immutable post-call.
user_ must be non-zero, otherwise @custom:reverts InvalidUser.
protocolRegistry_ must be non-zero, otherwise @custom:reverts
InvalidProtocolRegistry. @param user_ The user this store is bound to forever.
function initialize(address user_, address protocolRegistry_) external override initializer;
Parameters
| Name | Type | Description |
|---|---|---|
user_ | address | |
protocolRegistry_ | address | The protocol registry used to authorise writers. |
storeLabel
Records a label under labelhash and locks the slot permanently.
Gated to addresses currently registered in the protocol registry, otherwise
Note:
reverts: NotAuthorised. labelhash must be non-zero, otherwise
function storeLabel(
bytes32 labelhash,
string calldata label
)
external
override
onlyAuthorisedProtocol;
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The labelhash key. |
label | string | The label string to store. |
owner
Returns the permanent user this store is bound to.
Set once at initialize and never mutated; used as the binding-mismatch oracle
and immutable thereafter.
function owner() external view override returns (address owner_);
Returns
| Name | Type | Description |
|---|---|---|
owner_ | address | The bound user address. |
protocolRegistry
Returns the protocol registry this store queries for write authorisation.
function protocolRegistry() external view override returns (address protocolRegistry_);
Returns
| Name | Type | Description |
|---|---|---|
protocolRegistry_ | address | The registry address. |
hasLabel
Returns true iff a label has been stored under labelhash.
function hasLabel(bytes32 labelhash) external view override returns (bool exists);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The labelhash to check. |
Returns
| Name | Type | Description |
|---|---|---|
exists | bool | True iff the slot holds a label. |
isLocked
Returns true iff the slot for labelhash is permanently locked.
Always equal to hasLabel in the current design; exposed explicitly so future
implementations behind the beacon can distinguish "stored" from "locked" if needed.
function isLocked(bytes32 labelhash) external view override returns (bool locked);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The labelhash to check. |
Returns
| Name | Type | Description |
|---|---|---|
locked | bool | True iff the slot is locked. |
getLabel
Returns the stored label for labelhash, or the empty string if none.
function getLabel(bytes32 labelhash) external view override returns (string memory label);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The labelhash to look up. |
Returns
| Name | Type | Description |
|---|---|---|
label | string | The stored label string. |
getLabelCount
Returns the total number of labels ever stored.
function getLabelCount() external view override returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Current length of the insertion-order list. |
getLabelAt
Returns the human-readable label at the given insertion-order index.
Primary read for "give me my names"; does not require the caller to know any labelhash. For the underlying labelhash key see @custom:function getLabelhashAt.
function getLabelAt(uint256 index) external view override returns (string memory label);
Parameters
| Name | Type | Description |
|---|---|---|
index | uint256 | Zero-based index into the insertion-order list. |
Returns
| Name | Type | Description |
|---|---|---|
label | string | The stored label string at index. |
getLabelhashAt
Returns the labelhash at the given insertion-order index.
function getLabelhashAt(uint256 index) external view override returns (bytes32 labelhash);
Parameters
| Name | Type | Description |
|---|---|---|
index | uint256 | Zero-based index into the insertion-order list. |
Returns
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The labelhash at index. |
getLabels
Paginated read returning just the stored labels, in insertion order.
Primary bulk read for "give me all my names". Callers never need to touch
labelhashes. Length is min(limit, getLabelCount() - offset);
offset >= getLabelCount() returns an empty array (not a revert).
function getLabels(
uint256 offset,
uint256 limit
)
external
view
override
returns (string[] memory labels);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
labels | string[] | Slice of label strings. |
getLabelhashes
Paginated read over the labelhash keys, in insertion order.
Advanced read for callers that need the raw labelhash keys. Symmetric with
Note: function: getLabels; same indices map to the same entries.
function getLabelhashes(
uint256 offset,
uint256 limit
)
external
view
override
returns (bytes32[] memory labelhashes);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
labelhashes | bytes32[] | Slice of labelhash keys. |
version
Returns implementation version.
function version() external pure virtual returns (string memory versionString);
Returns
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
_onlyAuthorisedProtocol
Internal authorisation check deferred from the onlyAuthorisedProtocol modifier.
function _onlyAuthorisedProtocol() internal view;
StoreFactory
Inherits: Ownable, IStoreFactory
Title: StoreFactory
Factory for the two per-user DotNS store types, sharing one factory contract and two beacons. @dev Each user may acquire AT MOST two stores, ever:
- a
LabelStore, deployed viadeployLabelStoreForby a protocol-registered caller during registration; and - a
UserStore, claimed viaclaimUserStoreby the user themselves. Both areBeaconProxyinstances pointing at their respectiveUpgradeableBeacon. The factory owns both beacons so the factory owner can upgrade implementations for every proxy atomically. Neither per-user mapping is ever transferred, reassigned, or overwritten after the first write; bindings are permanent.
Note: security-contact: admin@parity.io
Constants
labelStoreBeacon
Beacon backing every LabelStore proxy.
Public getter name is interface-constrained by @custom:contract IStoreFactory.
address public immutable override labelStoreBeacon
userStoreBeacon
Beacon backing every UserStore proxy.
Public getter name is interface-constrained by @custom:contract IStoreFactory.
address public immutable override userStoreBeacon
protocolRegistry
Protocol registry used to authorise deployLabelStoreFor callers.
Public getter name is interface-constrained by @custom:contract IStoreFactory.
address public immutable override protocolRegistry
State Variables
_labelStores
user => their permanent LabelStore. Set once per user, forever.
mapping(address user => address store) private _labelStores
_userStores
user => their permanent UserStore. Set once per user, forever.
mapping(address user => address store) private _userStores
_labelStoreList
Insertion-order list of every LabelStore proxy ever deployed. Append-only.
address[] private _labelStoreList
_userStoreList
Insertion-order list of every UserStore proxy ever claimed. Append-only.
address[] private _userStoreList
Functions
onlyOwnerOrProtocol
Restricts deployLabelStoreFor to the owner or any protocol-registered caller.
modifier onlyOwnerOrProtocol() ;
constructor
Deploys the factory together with both store implementations and beacons.
A single new StoreFactory(protocolRegistry, owner) call wires everything:
- Deploys a fresh
LabelStoreimplementation. - Deploys a fresh
UserStoreimplementation. - Constructs both
UpgradeableBeaconinstances, owned byaddress(this)soupgrade*Implementationcan delegate tobeacon.upgradeTo. Keeping the implementation deployments inside the constructor removes a class of operator error: there is no "did I deploy the implementation first?" step and no way to pass the wrong implementation address.protocolRegistry_must be non-zero, otherwise @custom:reverts InvalidProtocolRegistry.
Implementations and beacons are deployed inline so a single factory address fully describes the store topology, removing a class of operator error around mismatched beacons.
constructor(address protocolRegistry_, address owner_) Ownable(owner_);
Parameters
| Name | Type | Description |
|---|---|---|
protocolRegistry_ | address | The protocol registry for writer auth on label stores. |
owner_ | address | Account that owns this factory and can upgrade store implementations. |
deployLabelStoreFor
Deploys a LabelStore beacon-proxy bound to user.
Callable by the factory owner or any address currently registered in the protocol
registry; any other caller @custom:reverts NotAuthorised. user must be non-zero,
otherwise @custom:reverts InvalidUser. The user must not already have a
LabelStore, otherwise @custom:reverts AlreadyDeployed. After deployment the
freshly initialised proxy must report user as its owner, otherwise
Notes:
-
reverts: ImplementationBindingMismatch. Emits
-
emits: LabelStoreDeployed on success.
function deployLabelStoreFor(address user)
external
override
onlyOwnerOrProtocol
returns (address store);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The user the store is bound to forever. |
Returns
| Name | Type | Description |
|---|---|---|
store | address | The deployed store address. |
getLabelStore
Returns the LabelStore address bound to user, or the zero address if none.
function getLabelStore(address user) external view override returns (address store);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The user to look up. |
Returns
| Name | Type | Description |
|---|---|---|
store | address | The bound store address, or zero. |
getLabelStoreCount
Returns the total number of LabelStore proxies ever deployed.
function getLabelStoreCount() external view override returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Length of the deployment list. |
getLabelStores
Paginated enumeration over every LabelStore proxy ever deployed.
Insertion order of deployLabelStoreFor calls. offset >= getLabelStoreCount()
returns an empty array; result length is min(limit, count - offset).
function getLabelStores(
uint256 offset,
uint256 limit
)
external
view
override
returns (address[] memory stores);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
stores | address[] | Slice of label-store addresses. |
upgradeLabelStoreImplementation
Upgrades the LabelStore implementation for every existing and future proxy.
Callable by the factory owner only, otherwise
Notes:
-
reverts: OwnableUnauthorizedAccount.
newImplementationmust be non-zero, otherwise @custom:reverts InvalidImplementation. The candidate is sentinel-probed by callingILabelStore.protocolRegistryon it before the beacon is rotated; if the address does not implement that selector the probe reverts and the upgrade does not land (deliberate fail-fast guard, no named error). Delegates toUpgradeableBeacon.upgradeToand emits -
emits: LabelStoreImplementationUpgraded on success.
function upgradeLabelStoreImplementation(address newImplementation)
external
override
onlyOwner;
Parameters
| Name | Type | Description |
|---|---|---|
newImplementation | address | The new implementation address. |
claimUserStore
Caller claims their UserStore beacon-proxy.
Self-claim only; _owner on the resulting store is always msg.sender,
regardless of who pays gas. One store per caller, forever: a caller who already
has a UserStore @custom:reverts AlreadyDeployed. After deployment the freshly
initialised proxy must report msg.sender as its owner, otherwise
Notes:
-
reverts: ImplementationBindingMismatch. Emits
-
emits: UserStoreClaimed on success.
function claimUserStore() external override returns (address store);
Returns
| Name | Type | Description |
|---|---|---|
store | address | The deployed store address. |
getUserStore
Returns the UserStore address bound to user, or the zero address if none.
function getUserStore(address user) external view override returns (address store);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The user to look up. |
Returns
| Name | Type | Description |
|---|---|---|
store | address | The bound store address, or zero. |
getUserStoreCount
Returns the total number of UserStore proxies ever claimed.
function getUserStoreCount() external view override returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Length of the claim list. |
getUserStores
Paginated enumeration over every UserStore proxy ever claimed.
Insertion order of claimUserStore calls. offset >= getUserStoreCount()
returns an empty array; result length is min(limit, count - offset).
function getUserStores(
uint256 offset,
uint256 limit
)
external
view
override
returns (address[] memory stores);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
stores | address[] | Slice of user-store addresses. |
upgradeUserStoreImplementation
Upgrades the UserStore implementation for every existing and future proxy.
Callable by the factory owner only, otherwise
Notes:
-
reverts: OwnableUnauthorizedAccount.
newImplementationmust be non-zero, otherwise @custom:reverts InvalidImplementation. The candidate is sentinel-probed by callingIUserStore.getKeyCounton it before the beacon is rotated; if the address does not implement that selector the probe reverts and the upgrade does not land (deliberate fail-fast guard, no named error). Delegates toUpgradeableBeacon.upgradeToand emits -
emits: UserStoreImplementationUpgraded on success.
function upgradeUserStoreImplementation(address newImplementation) external override onlyOwner;
Parameters
| Name | Type | Description |
|---|---|---|
newImplementation | address | The new implementation address. |
version
Returns implementation version.
function version() external pure virtual returns (string memory versionString);
Returns
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
_onlyOwnerOrProtocol
Internal authorisation check deferred from the onlyOwnerOrProtocol modifier.
function _onlyOwnerOrProtocol() internal view;
_paginateAddresses
Shared pagination helper used by getLabelStores and getUserStores.
Single canonical slicer so both enumerations bound-check and copy identically.
function _paginateAddresses(
address[] storage source,
uint256 offset,
uint256 limit
)
internal
view
returns (address[] memory slice);
Parameters
| Name | Type | Description |
|---|---|---|
source | address[] | Storage array to slice. |
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
slice | address[] | Result slice; empty when offset >= source.length. |
UserStore
Inherits: Initializable, IUserStore
Title: UserStore
Permanent per-user generic key/value store with per-key history.
One instance per user, deployed as a BeaconProxy by StoreFactory.claimUserStore.
Bound to its claimer forever: _owner is set once at initialize and only that
address may write. History is append-only; every setValue that supersedes a
non-empty prior value records the supersession with block.timestamp.
Cost isolation: each user's writes bill their own contract, keeping shared resolvers from being polluted by one user's blob usage.
Storage collision: the BeaconProxy stores the beacon address at EIP-1967 slot
keccak256("eip1967.proxy.beacon") - 1, which is non-sequential and cannot collide
with this contract's sequential storage slots.
Note: security-contact: admin@parity.io
State Variables
_owner
Permanent user this store belongs to. Set in initialize.
address private _owner
_current
key => current value bytes.
mapping(bytes32 key => bytes value) private _current
_history
key => insertion-order list of prior non-empty values and their supersession timestamps.
mapping(bytes32 key => Entry[] entries) private _history
_keyList
Insertion-order list of all keys ever written. Append-only (never pruned).
bytes32[] private _keyList
_keyIndex
key => 1-indexed position in _keyList (zero means "never written").
mapping(bytes32 key => uint256 indexPlusOne) private _keyIndex
__gap
Reserved storage space to allow for layout changes in future beacon upgrades.
uint256[50] private __gap
Functions
onlyOwner
Restricts writes to the bound owner.
modifier onlyOwner() ;
constructor
Note: oz-upgrades-unsafe-allow: constructor
constructor() ;
initialize
Initialises the store, binding it permanently to user_.
Callable exactly once via Initializable. user_ must be non-zero, otherwise
Note: reverts: InvalidUser.
function initialize(address user_) external override initializer;
Parameters
| Name | Type | Description |
|---|---|---|
user_ | address | The user this store is bound to forever. |
setValue
Sets the current value for key.
Callable only by the bound owner; any other caller @custom:reverts NotOwner.
key must be non-zero, otherwise @custom:reverts InvalidKey. If a non-empty
prior value existed it is pushed into the per-key history list with
block.timestamp; empty prior values produce no history entry. Emits
Note: emits: ValueSet on every successful write.
function setValue(bytes32 key, bytes calldata value) external override onlyOwner;
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to write. |
value | bytes | The new current value (may be empty). |
owner
Returns the permanent user this store is bound to.
Set once at initialize and never mutated; used as the binding-mismatch oracle
and immutable thereafter.
function owner() external view override returns (address owner_);
Returns
| Name | Type | Description |
|---|---|---|
owner_ | address | The bound user address. |
getValue
Returns the current value under key, or empty bytes if unset.
function getValue(bytes32 key) external view override returns (bytes memory value);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to read. |
Returns
| Name | Type | Description |
|---|---|---|
value | bytes | The current value. |
hasValue
Returns true iff the current value under key has non-zero length.
function hasValue(bytes32 key) external view override returns (bool present);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to check. |
Returns
| Name | Type | Description |
|---|---|---|
present | bool | True iff getValue(key).length != 0. |
getHistoryCount
Returns the number of prior (historical) values recorded for key.
function getHistoryCount(bytes32 key) external view override returns (uint256 count);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to read. |
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Length of the history list. |
getHistoryAt
Returns the historical entry at index for key.
function getHistoryAt(
bytes32 key,
uint256 index
)
external
view
override
returns (Entry memory entry);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to read. |
index | uint256 | Zero-based index into the history list. |
Returns
| Name | Type | Description |
|---|---|---|
entry | Entry | The (value, timestamp) pair. |
getHistory
Paginated read over the per-key history list.
offset >= getHistoryCount(key) returns an empty array. Length is
min(limit, getHistoryCount(key) - offset).
function getHistory(
bytes32 key,
uint256 offset,
uint256 limit
)
external
view
override
returns (Entry[] memory entries);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to read. |
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
entries | Entry[] | Slice of history entries. |
getKeyCount
Returns the number of distinct keys ever written.
function getKeyCount() external view override returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Length of the key-insertion list. |
getKeyAt
Returns the key at the given insertion-order index.
function getKeyAt(uint256 index) external view override returns (bytes32 key);
Parameters
| Name | Type | Description |
|---|---|---|
index | uint256 | Zero-based index into the key list. |
Returns
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key at index. |
getKeys
Paginated read over the insertion-order key list.
offset >= getKeyCount() returns an empty array. Length is
min(limit, getKeyCount() - offset).
function getKeys(
uint256 offset,
uint256 limit
)
external
view
override
returns (bytes32[] memory keys);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
keys | bytes32[] | Slice of keys. |
version
Returns implementation version.
function version() external pure virtual returns (string memory versionString);
Returns
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
_onlyOwner
Internal owner check deferred from the onlyOwner modifier.
function _onlyOwner() internal view;
Contents
DotnsConstants
Title: DotNS Constants
Protocol-level invariants shared across DotNS contracts.
Centralises the well-known protocol-registry keys that every contract uses to discover
its siblings (registrar, controller, registry, resolvers, etc.). Each key is a
role address resolved at call time, so rotating an implementation is a
single set on the protocol registry without redeploying consumers. The TLD is not a
constant here: it is set per network on the protocol registry and read by every consumer.
Note: security-contact: admin@parity.io
Constants
REVIVE_SYSTEM
Address of revive's System precompile, exposed by every revive runtime that opts the precompile in.
Mirrors the upstream SYSTEM_ADDR constant in
substrate/frame/revive/uapi/sol/ISystem.sol. Consumed by
DotnsPopController and DotnsNameWhitelist to authenticate
Root-origin dispatches via ISystem.originIsRoot().
address internal constant REVIVE_SYSTEM = address(0x0900)
PERSONHOOD
Address of the Proof-of-Personhood precompile backed by the alias-accounts pallet on Asset Hub.
Consumed by PopRules to read each account's personhood tier
(None / Lite / Full) and the dotns-scoped contextAlias.
address internal constant PERSONHOOD = address(0x000000000000000000000000000000000a010000)
PERSONHOOD_CONTEXT
Application identifier passed to @custom:function IPersonhood.personhoodStatus.
Fixed per project so the same person receives a stable, dotns-only
contextAlias and no cross-application linkability is exposed.
forge-lint: disable-next-line(unsafe-typecast)
bytes32 internal constant PERSONHOOD_CONTEXT = bytes32("dotns")
BASE_DEPOSIT
Launch deposit passed into the DotnsFlatPricing constructor.
10 DOT under revive's 18-decimal Asset Hub convention. A new amount is a fresh model deployment registered under @custom:constant COST_MODEL, so this constant seeds the model rather than being read afterwards. Single source of truth for deploy scripts and tests so the seed cannot drift between call sites.
uint256 internal constant BASE_DEPOSIT = 10 ether
MIN_PRICE
Price floor F passed into the DotnsScarcityPricing candidate's constructor.
Below BASE_DEPOSIT so that curve falls above nine characters. Seeds the candidate
constructor; a new floor is a fresh model deployment.
uint256 internal constant MIN_PRICE = 0.1 ether
COST_MODEL
Well-known key for the cost model pricing registrations by base length.
Role: single authority for the wei amount a registration costs. PopRules resolves
it here on every pricing read, so swapping the model is one set on the protocol
registry without redeploying PopRules or its consumers.
forge-lint: disable-next-line(unsafe-typecast)
bytes32 internal constant COST_MODEL = bytes32("costModel")
ESCROW_COOLDOWN
Default release cooldown seeded on DotnsNameEscrow.initialize.
Single source of truth for deploy scripts and tests so the value cannot drift between
call sites. Bounded on-chain by DotnsNameEscrow.MAX_COOLDOWN. Live deployments rotate
the runtime value via updateCooldown rather than rebuilding consumers.
uint256 internal constant ESCROW_COOLDOWN = 15 minutes
ESCROW_REDEEM_WINDOW
Default redeem window seeded on DotnsNameEscrow.initialize.
Single source of truth for deploy scripts and tests. Bounded on-chain by
DotnsNameEscrow.MAX_REDEEM_WINDOW. Live deployments rotate the runtime value via
updateRedeemWindow.
uint256 internal constant ESCROW_REDEEM_WINDOW = 1 days
MAX_PAGE_SIZE
Maximum entries a paginated view returns in a single page.
Shared ceiling for paginated reads: a view clamps its returned array to this figure,
and callers page through larger sets with offset.
uint256 internal constant MAX_PAGE_SIZE = 200
WHITELIST_OPERATOR_ROLE
Operational role allowed to manage the public controller whitelist.
Holders can grant or revoke whitelist entries, but cannot upgrade contracts or change protocol configuration.
bytes32 internal constant WHITELIST_OPERATOR_ROLE = keccak256("DOTNS_WHITELIST_OPERATOR_ROLE")
WHITELIST_DEFAULT_MAX_CLAIMANTS
Default per-name live-claim cap the name whitelist starts with.
Governance retunes it on the whitelist within WHITELIST_MAX_CLAIMANTS_LIMIT.
uint16 internal constant WHITELIST_DEFAULT_MAX_CLAIMANTS = 64
WHITELIST_DEFAULT_MAX_REASON_BYTES
Default claim-reason byte cap the name whitelist starts with.
Governance retunes it on the whitelist within WHITELIST_MAX_REASON_LIMIT.
uint256 internal constant WHITELIST_DEFAULT_MAX_REASON_BYTES = 256
WHITELIST_MAX_CLAIMANTS_LIMIT
Upper bound on the whitelist live-claim cap. Caps the claim clear-loop below the block gas limit.
uint16 internal constant WHITELIST_MAX_CLAIMANTS_LIMIT = 128
WHITELIST_MAX_REASON_LIMIT
Upper bound on the whitelist reason byte cap.
uint256 internal constant WHITELIST_MAX_REASON_LIMIT = 256
WHITELIST_DEFAULT_MAX_GRANT_BATCH
Default cap on labels granted in one grantNames call.
Governance retunes it on the whitelist within WHITELIST_MAX_GRANT_BATCH_LIMIT.
uint16 internal constant WHITELIST_DEFAULT_MAX_GRANT_BATCH = 100
WHITELIST_MAX_GRANT_BATCH_LIMIT
Upper bound on the grantNames batch cap. Bounds one call below the block gas
limit.
uint16 internal constant WHITELIST_MAX_GRANT_BATCH_LIMIT = 256
REGISTRAR
Well-known key for the ERC721 registrar backing name ownership.
Role: token-of-record for registered names. Mints, burns, and tracks the
tokenId => label mapping consumed by the forward registry on
transfer.
forge-lint: disable-next-line(unsafe-typecast)
bytes32 internal constant REGISTRAR = bytes32("registrar")
CONTROLLER
Well-known key for the registrar controller orchestrating commit-reveal
registration. @dev Role: commit-reveal entry point for the public registration flow.
Calls register on the registrar after pricing and validation.
forge-lint: disable-next-line(unsafe-typecast)
bytes32 internal constant CONTROLLER = bytes32("controller")
REGISTRY
Well-known key for the forward registry storing node ownership and resolver.
Role: source of truth for (node => owner, resolver). Read by every
resolver gate that defers authority to the node owner.
forge-lint: disable-next-line(unsafe-typecast)
bytes32 internal constant REGISTRY = bytes32("registry")
REVERSE_RESOLVER
Well-known key for the reverse resolver for address-to-name mapping.
Role: stores address => name reverse records. Writer is the
registrar/controller, not the address holder.
forge-lint: disable-next-line(unsafe-typecast)
bytes32 internal constant REVERSE_RESOLVER = bytes32("reverseResolver")
POP_RULES
Well-known key for the PoP oracle enforcing eligibility and pricing.
Role: arbiter of PoP cross-flow priority and pricing. Consulted by both the public commit-reveal controller and the PoP controller. forge-lint: disable-next-line(unsafe-typecast)
bytes32 internal constant POP_RULES = bytes32("popRules")
STORE_FACTORY
Well-known key for the factory deploying per-user Store instances.
Role: deploy-on-demand provisioning of user LabelStore proxies and
authorisation gate for protocol writes into them.
forge-lint: disable-next-line(unsafe-typecast)
bytes32 internal constant STORE_FACTORY = bytes32("storeFactory")
RESOLVER
Well-known key for the forward resolver storing address records.
Role: node => address records. Writes gated on node ownership.
forge-lint: disable-next-line(unsafe-typecast)
bytes32 internal constant RESOLVER = bytes32("resolver")
CONTENT_RESOLVER
Well-known key for the content resolver storing content hashes and text records.
Role: node => contenthash/text records and ERC721-style operator
approvals. Writes gated on node ownership or operator approval.
forge-lint: disable-next-line(unsafe-typecast)
bytes32 internal constant CONTENT_RESOLVER = bytes32("contentResolver")
POP_CONTROLLER
Well-known key for the dedicated PoP controller orchestrating lite/full-person username issuance on behalf of the PoP gateway.
Kept distinct from CONTROLLER (commit-reveal public controller) so the
two can coexist per DotnsRegistrar's multi-controller affordance.
forge-lint: disable-next-line(unsafe-typecast)
bytes32 internal constant POP_CONTROLLER = bytes32("popController")
POP_RESOLVER
Well-known key for the PoP resolver holding per-name records produced by the PoP username flow (chat keys, lite => full links).
Role: node => chatKey and bidirectional lite <=> full link index.
Writer is the POP_CONTROLLER, not the node owner.
forge-lint: disable-next-line(unsafe-typecast)
bytes32 internal constant POP_RESOLVER = bytes32("popResolver")
POP_LENS
Well-known key for the read-only lens over PoP identity data.
Role: off-chain query surface. Composes the account name listings, the per-name record, and the account summary from the controller, registrar, store factory, PoP resolver, and PopRules. Holds no authority and is consumed by clients, not by other contracts. forge-lint: disable-next-line(unsafe-typecast)
bytes32 internal constant POP_LENS = bytes32("popLens")
NAME_ESCROW
Well-known key for the name escrow holding refundable deposits and driving the release lifecycle for registered names.
Role: custodial vault for registration deposits and the state machine that drives the name release lifecycle. forge-lint: disable-next-line(unsafe-typecast)
bytes32 internal constant NAME_ESCROW = bytes32("nameEscrow")
MULTICALL3
Well-known key for the generic Multicall3 batching helper.
Role: unauthorised arbitrary-target multicall utility used by
clients and tooling. Target contracts still enforce their own
permissions and observe Multicall3 as msg.sender.
forge-lint: disable-next-line(unsafe-typecast)
bytes32 internal constant MULTICALL3 = bytes32("multicall3")
CREATE3_FACTORY
Well-known key for the CREATE3 factory backing the deterministic deploy pipeline.
Role: permissionless CREATE3 deployer. The first deploy stage bootstraps the factory, records it under this key, and every later stage resolves it from here, so deterministic addresses never depend on an environment variable. forge-lint: disable-next-line(unsafe-typecast)
bytes32 internal constant CREATE3_FACTORY = bytes32("create3Factory")
NAME_WHITELIST
Well-known key for the pre-launch name whitelist that binds a label to the one address permitted to register it.
Role: authority for label-bound registration grants. Both the public and PoP controllers resolve it here and read it at mint time; the whitelist stores the grants, the controllers only read them. forge-lint: disable-next-line(unsafe-typecast)
bytes32 internal constant NAME_WHITELIST = bytes32("nameWhitelist")
LabelUtils
Title: DotNS Label Utilities Library
Canonical keccak labelhash and namehash helpers shared by every DotNS contract that derives node identifiers from user-supplied labels.
Exists so that the identical inline-assembly keccak sequences don't need to live in every controller, registrar, or resolver. Every caller that maps a label to an on-chain node goes through this library, which is the single source of truth for how a label hashes. The TLD is supplied by the caller (read from the protocol registry), so this library holds no network-specific constant.
Deliberate non-goals:
- Validation (single-label checks, min-length rules, availability): each caller owns its own validation policy alongside its own interface-declared errors. Centralising validation here would require centralising the error types, which breaks interface-level error ownership.
- Lowercase ASCII letters/digits/hyphen rules with hyphen-position constraints live in @custom:function StringUtils.isSingleLabel; this library treats the input as opaque bytes once a caller has run its own checks.
Note: security-contact: admin@parity.io
Functions
labelhash
Computes keccak256(bytes(label)) via memory-safe scratch space.
function labelhash(string calldata label) internal pure returns (bytes32 hash);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Label string. |
Returns
| Name | Type | Description |
|---|---|---|
hash | bytes32 | keccak256(bytes(label)). |
labelhashMemory
Computes keccak256(bytes(label)) for a memory string.
Overload used by call sites that hold the label in memory (e.g. the
registrar's transfer sync path reading _labels[tokenId]). Same
semantics as @custom:function labelhash, different calldata shape.
function labelhashMemory(string memory label) internal pure returns (bytes32 hash);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Label string held in memory. |
Returns
| Name | Type | Description |
|---|---|---|
hash | bytes32 | keccak256(bytes(label)). |
namehashUnder
Computes namehash(parent, labelhash) against an arbitrary parent.
Pass the TLD node (from the protocol registry) as parent for a top-level
registration, or a subnode/PoP-namespace node for a nested name.
function namehashUnder(bytes32 parent, bytes32 labelhash_)
internal
pure
returns (bytes32 node);
Parameters
| Name | Type | Description |
|---|---|---|
parent | bytes32 | Parent node. |
labelhash_ | bytes32 | keccak256(bytes(label)). |
Returns
| Name | Type | Description |
|---|---|---|
node | bytes32 | namehash(parent, labelhash). |
deriveNode
Derives (labelhash, node) for a label under a given TLD node in one call.
Convenience combinator for registration flows: hashes the label exactly once and returns both identifiers callers need without recomputing.
function deriveNode(
bytes32 tldNode,
string calldata label
)
internal
pure
returns (bytes32 hash, bytes32 node);
Parameters
| Name | Type | Description |
|---|---|---|
tldNode | bytes32 | TLD node to root the name under, read from the protocol registry. |
label | string | Label string. |
Returns
| Name | Type | Description |
|---|---|---|
hash | bytes32 | keccak256(bytes(label)). |
node | bytes32 | namehash(tldNode, hash). |
stripTld
Strips the network's TLD suffix from a stored full name.
Returns the empty string when the input does not end in tld; callers treat
an empty return as a "do not trust this record" signal.
function stripTld(
string memory tldSuffix,
string memory fullName
)
internal
pure
returns (string memory label);
Parameters
| Name | Type | Description |
|---|---|---|
tldSuffix | string | TLD suffix including the leading dot, read from the protocol registry. |
fullName | string | Stored full name to strip. |
Multicall3
Title: Multicall3
Authors: Michael Elliot, Joshua Levine, Nick Johnson, Andreas Bigger, Matt Solomon
Aggregate results from multiple function calls.
Multicall and Multicall2 backwards-compatible.
Source-compatible with mds1/multicall3; pragma updated for this repository.
Aggregate methods are payable to match the standard Multicall3 ABI.
Functions
aggregate
Backwards-compatible call aggregation with Multicall.
function aggregate(Call[] calldata calls)
public
payable
returns (uint256 blockNumber, bytes[] memory returnData);
Parameters
| Name | Type | Description |
|---|---|---|
calls | Call[] | An array of Call structs. |
Returns
| Name | Type | Description |
|---|---|---|
blockNumber | uint256 | The block number where the calls were executed. |
returnData | bytes[] | An array of bytes containing the responses. |
tryAggregate
Backwards-compatible with Multicall2.
Aggregate calls without requiring success.
function tryAggregate(
bool requireSuccess,
Call[] calldata calls
)
public
payable
returns (Result[] memory returnData);
Parameters
| Name | Type | Description |
|---|---|---|
requireSuccess | bool | If true, require all calls to succeed. |
calls | Call[] | An array of Call structs. |
Returns
| Name | Type | Description |
|---|---|---|
returnData | Result[] | An array of Result structs. |
tryBlockAndAggregate
Backwards-compatible with Multicall2.
Aggregate calls and allow failures using tryAggregate.
function tryBlockAndAggregate(
bool requireSuccess,
Call[] calldata calls
)
public
payable
returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData);
Parameters
| Name | Type | Description |
|---|---|---|
requireSuccess | bool | If true, require all calls to succeed. |
calls | Call[] | An array of Call structs. |
Returns
| Name | Type | Description |
|---|---|---|
blockNumber | uint256 | The block number where the calls were executed. |
blockHash | bytes32 | The hash of the block where the calls were executed. |
returnData | Result[] | An array of Result structs. |
blockAndAggregate
Backwards-compatible with Multicall2.
Aggregate calls and allow failures using tryAggregate.
function blockAndAggregate(Call[] calldata calls)
public
payable
returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData);
Parameters
| Name | Type | Description |
|---|---|---|
calls | Call[] | An array of Call structs. |
Returns
| Name | Type | Description |
|---|---|---|
blockNumber | uint256 | The block number where the calls were executed. |
blockHash | bytes32 | The hash of the block where the calls were executed. |
returnData | Result[] | An array of Result structs. |
aggregate3
Aggregate calls, ensuring each returns success if required.
function aggregate3(Call3[] calldata calls)
public
payable
returns (Result[] memory returnData);
Parameters
| Name | Type | Description |
|---|---|---|
calls | Call3[] | An array of Call3 structs. |
Returns
| Name | Type | Description |
|---|---|---|
returnData | Result[] | An array of Result structs. |
aggregate3Value
Aggregate calls with a msg value.
Reverts if msg.value is not equal to the sum of the call values.
function aggregate3Value(Call3Value[] calldata calls)
public
payable
returns (Result[] memory returnData);
Parameters
| Name | Type | Description |
|---|---|---|
calls | Call3Value[] | An array of Call3Value structs. |
Returns
| Name | Type | Description |
|---|---|---|
returnData | Result[] | An array of Result structs. |
getBlockHash
Returns the block hash for the given block number.
function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash);
Parameters
| Name | Type | Description |
|---|---|---|
blockNumber | uint256 | The block number. |
getBlockNumber
Returns the block number.
function getBlockNumber() public view returns (uint256 blockNumber);
getCurrentBlockCoinbase
Returns the block coinbase.
function getCurrentBlockCoinbase() public view returns (address coinbase);
getCurrentBlockDifficulty
Returns the block prevrandao value.
function getCurrentBlockDifficulty() public view returns (uint256 difficulty);
getCurrentBlockGasLimit
Returns the block gas limit.
function getCurrentBlockGasLimit() public view returns (uint256 gaslimit);
getCurrentBlockTimestamp
Returns the block timestamp.
function getCurrentBlockTimestamp() public view returns (uint256 timestamp);
getEthBalance
Returns the native-token balance of a given address.
function getEthBalance(address addr) public view returns (uint256 balance);
getLastBlockHash
Returns the block hash of the last block.
function getLastBlockHash() public view returns (bytes32 blockHash);
getBasefee
Gets the base fee of the given block.
Can revert if the BASEFEE opcode is not implemented by the given chain.
function getBasefee() public view returns (uint256 basefee);
getChainId
Returns the chain id.
function getChainId() public view returns (uint256 chainid);
Structs
Call
struct Call {
address target;
bytes callData;
}
Call3
struct Call3 {
address target;
bool allowFailure;
bytes callData;
}
Call3Value
struct Call3Value {
address target;
bool allowFailure;
uint256 value;
bytes callData;
}
Result
struct Result {
bool success;
bytes returnData;
}
RegistrationUtils
Title: DotNS Registration Utilities Library
Single canonical implementation of the "mint + forward-registry + store-write" triad used by every DotNS registration flow.
Exists so that every controller (public commit-reveal, PoP gateway, future privileged flows) calls the same sequence. Without this library each controller re-implements the sequence, and the implementations drift.
Scope: this library is deliberately minimal. It only performs the steps that every registration flow needs regardless of policy:
- Mint the ERC721 name token on the base registrar.
- Write the forward registry entry (node => owner + default resolver).
- Resolve the per-user
LabelStore(deploying on demand) so the caller can pass it back. The registrar writes the labels-only store entry internally. Flow-specific concerns (pricing, reverse-record setting, chat-key persistence, reservation queue mutation) stay inside the calling controller.
Note: security-contact: admin@parity.io
Functions
registerAndStore
Performs the canonical mint + forward-registry + store-write sequence.
Callable by any authorised controller. Emits no events; each controller
emits its own flow-level event after this returns, so behavioural drift
between flows stays contained at the emission layer rather than at the
underlying state-transition layer. Store authorisation is handled by the
protocol registry (isRegisteredAddress) on every write, so no per-store
allowlist bookkeeping is needed here.
The registrar writes the LabelStore entry directly inside register
so this helper deliberately does not call StoreUtils.writeLabel.
Doing it twice would deploy or touch the store on every flow and
could conflict with the registrar's locked-entry semantics.
function registerAndStore(RegistrationContext memory context)
internal
returns (address labelStore);
Parameters
| Name | Type | Description |
|---|---|---|
context | RegistrationContext | Registration inputs. See @custom:struct RegistrationContext. |
Returns
| Name | Type | Description |
|---|---|---|
labelStore | address | The resolved or newly deployed LabelStore address for context.user. |
_resolveSiblings
Resolves sibling contracts via the protocol registry.
Exists so that resolution is one round-trip through a single helper and not duplicated inline at every call site. If protocol-registry key conventions change, the change lands here.
function _resolveSiblings(IDotnsProtocolRegistry protocolRegistry)
private
view
returns (Siblings memory siblings);
Structs
RegistrationContext
Inputs describing a single name registration.
Passed as a struct so callers do not have to thread a growing positional argument list, and so future additions (e.g. a subname parent node) can be made additively without breaking call sites.
struct RegistrationContext {
IDotnsProtocolRegistry protocolRegistry;
address user;
string label;
bytes32 labelhash;
bytes32 node;
}
Properties
| Name | Type | Description |
|---|---|---|
protocolRegistry | IDotnsProtocolRegistry | The protocol-level address registry for sibling lookups. |
user | address | Address receiving the name. |
label | string | Human-readable label (without the TLD). |
labelhash | bytes32 | keccak256(bytes(label)). |
node | bytes32 | namehash(tldNode, labelhash). |
Siblings
Resolved sibling contracts for a registration call.
Held as a struct internally so the helper can pass a single value to the
downstream steps rather than three separate locals. Never returned to
callers; kept in memory for the lifetime of one registerAndStore.
struct Siblings {
IDotnsRegistrar registrar;
IDotnsRegistry registry;
IStoreFactory storeFactory;
}
StoreUtils
Title: DotNS Store Utilities Library
Canonical helpers for protocol writes into per-user LabelStore instances.
One auth rule, one write path. Every DotNS consumer (controller, registrar,
registry, PoP controller) funnels label writes through writeLabel so
authorisation and deploy-on-first-use semantics are identical across flows.
Note: security-contact: admin@parity.io
Functions
ensureLabelStore
Returns the LabelStore for user, deploying one via the factory if absent.
Deploy-on-demand: a user's store is created on their first protocol write so unused accounts never pay the deployment cost. The deploy path is gated by the factory, so callers that are not the factory owner and not protocol-registered
Note: reverts: NotAuthorised when a deployment is required.
function ensureLabelStore(IStoreFactory factory, address user)
internal
returns (address store);
Parameters
| Name | Type | Description |
|---|---|---|
factory | IStoreFactory | The store factory. |
user | address | The user whose label store is being resolved. |
Returns
| Name | Type | Description |
|---|---|---|
store | address | The resolved or newly deployed store address. |
writeLabel
Writes label under labelhash for user, deploying their LabelStore if needed.
Idempotent on locked entries: once a label is locked the call is a no-op rather than a revert, so retried protocol flows (e.g. an ERC721 transfer back to a prior owner) pass through without failing on the existing lock. Inherits the factory's writer authorisation: callers that are not the factory owner and not protocol-registered @custom:reverts NotAuthorised when the user has no store yet.
function writeLabel(
IStoreFactory factory,
address user,
bytes32 labelhash,
string memory label
)
internal
returns (address store);
Parameters
| Name | Type | Description |
|---|---|---|
factory | IStoreFactory | The store factory. |
user | address | The label store owner. |
labelhash | bytes32 | The labelhash key. |
label | string | The label string (typically the full name, e.g. "alice.dot"). |
Returns
| Name | Type | Description |
|---|---|---|
store | address | The resolved or newly deployed store address. |
StringUtils
Title: String Utilities Library
Provides string manipulation utilities for DotNS contracts.
Extends OpenZeppelin's Strings library with additional UTF-8 and conversion helpers.
Note: security-contact: admin@parity.io
Constants
MIN_LITE_SUFFIX_DIGITS
Minimum number of trailing digits required in a lite-person PoP label suffix.
Matches pallet_resources::MIN_LITE_USERNAME_DIGITS to avoid drift between
what People Chain emits and what DotNS accepts.
uint256 internal constant MIN_LITE_SUFFIX_DIGITS = 2
MAX_DNS_LABEL_OCTETS
Maximum number of octets in a single DNS label.
RFC 1035 caps each label at 63 octets. Enforced inside @custom:function _isDnsLabel so every public validator (@custom:function isSingleLabel, @custom:function isNamePath,
Note: function: isSingleDotLiteLabel, @custom:function isLitePersonLabel) inherits the bound and oversized labels never reach the registrar.
uint256 internal constant MAX_DNS_LABEL_OCTETS = 63
Functions
strlen
Computes the character length of a UTF-8 encoded string.
Counts Unicode code points, not bytes. Handles multi-byte UTF-8 sequences:
- 1 byte: 0x00-0x7F (ASCII)
- 2 bytes: 0xC0-0xDF
- 3 bytes: 0xE0-0xEF
- 4 bytes: 0xF0-0xF7
- 5 bytes: 0xF8-0xFB (rare, outside Unicode standard)
- 6 bytes: 0xFC-0xFD (rare, outside Unicode standard)
function strlen(string memory value) internal pure returns (uint256 len);
Parameters
| Name | Type | Description |
|---|---|---|
value | string | The UTF-8 encoded string to measure. |
Returns
| Name | Type | Description |
|---|---|---|
len | uint256 | The number of Unicode characters in the string. |
isSingleLabel
Validates that s is a single canonical DNS label.
Lowercase ASCII letters, digits, and hyphen only; hyphen may not be the first or last
character; length must be in (0, MAX_DNS_LABEL_OCTETS]. No dots allowed; use
Note: function: isNamePath for dotted forms. Mirrors the label rules enforced at the registrar.
function isSingleLabel(string calldata value) internal pure returns (bool isValid);
Parameters
| Name | Type | Description |
|---|---|---|
value | string | Candidate label. |
Returns
| Name | Type | Description |
|---|---|---|
isValid | bool | True if value is a canonical DNS label. |
isSingleLabelMemory
Memory-location helper for @custom:function isSingleLabel, used where the candidate label is produced by an upstream string transformation (e.g. the output of
Note: function: stripDigits) so callers do not need a calldata round-trip.
function isSingleLabelMemory(string memory value) internal pure returns (bool isValid);
Parameters
| Name | Type | Description |
|---|---|---|
value | string | Candidate label held in memory. |
Returns
| Name | Type | Description |
|---|---|---|
isValid | bool | True if value is a canonical DNS label. |
stripDots
Removes dot separators from a dotted label.
Used by the PoP gateway boundary to normalise user-facing
name.path input into the flat label expected by pricing and minting.
function stripDots(string calldata value) internal pure returns (string memory stripped);
Parameters
| Name | Type | Description |
|---|---|---|
value | string | Candidate dotted label. |
Returns
| Name | Type | Description |
|---|---|---|
stripped | string | Label with all dots removed. |
isSingleDotLiteLabel
Validates the gateway-facing lite input shape: stem.suffix.
Requires exactly one dot separator. The left segment must be a canonical DNS label and the right segment must be digits-only with exactly
Note: constant: MIN_LITE_SUFFIX_DIGITS characters.
function isSingleDotLiteLabel(string calldata value) internal pure returns (bool isValid);
Parameters
| Name | Type | Description |
|---|---|---|
value | string | Candidate dotted lite label. |
Returns
| Name | Type | Description |
|---|---|---|
isValid | bool | True when value matches the gateway lite input shape. |
isLitePersonLabel
Validates the lite-person PoP label format: <stem><digits>.
A lite-person label is a single DNS label whose trailing characters are
digits, with at least @custom:constant MIN_LITE_SUFFIX_DIGITS digits at the tail. It
Mirrors @custom:pallet pallet_resources::MIN_LITE_USERNAME_DIGITS. Lite and public
registrations
share the same namespace; the gateway strips any separator before calling
so the on-chain label is a flat DNS label (e.g. alice42). First-to-mint
wins at the ERC721 layer; cross-flow priority on the stripped stem is
arbitrated by @custom:function IPopRules.reserveBaseNameForPop. Keeping a single
namespace avoids the ambiguity dotli/dweb would otherwise see between
andrew.47 (lite) and andrew owning 47 as a subname.
Note: constant: MIN_LITE_SUFFIX_DIGITS trailing digits.
function isLitePersonLabel(string calldata value) internal pure returns (bool isValid);
Parameters
| Name | Type | Description |
|---|---|---|
value | string | Candidate label. |
Returns
| Name | Type | Description |
|---|---|---|
isValid | bool | True if the label is a DNS label with at least |
isLitePersonLabelMemory
Memory-location helper for @custom:function isLitePersonLabel, used by controller-side normalisation paths.
Note: constant: MIN_LITE_SUFFIX_DIGITS trailing digits.
function isLitePersonLabelMemory(string memory value) internal pure returns (bool isValid);
Parameters
| Name | Type | Description |
|---|---|---|
value | string | Candidate label held in memory. |
Returns
| Name | Type | Description |
|---|---|---|
isValid | bool | True if the label is a DNS label with at least |
_isLitePersonLabel
function _isLitePersonLabel(bytes memory raw) private pure returns (bool isValid);
isNamePath
Validates that s is a dot-separated path of canonical DNS labels.
Each segment between dots must satisfy @custom:function isSingleLabel. Empty
segments (leading, trailing, or consecutive dots) fail. Used when
callers submit multi-label paths (e.g. alice.dot) rather than
bare labels.
function isNamePath(string calldata value) internal pure returns (bool isValid);
Parameters
| Name | Type | Description |
|---|---|---|
value | string | Candidate name path. |
Returns
| Name | Type | Description |
|---|---|---|
isValid | bool | True if every dot-separated segment is a canonical DNS label. |
_isDnsLabel
function _isDnsLabel(
bytes memory label,
uint256 start,
uint256 end
)
private
pure
returns (bool isValid);
uintToString
Converts a uint256 to its decimal string representation.
Wraps OpenZeppelin's Strings.toString().
function uintToString(uint256 value) internal pure returns (string memory);
Parameters
| Name | Type | Description |
|---|---|---|
value | uint256 | The unsigned integer to convert. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | string | The decimal string representation. |
addressToHex
Converts an address to its checksummed hexadecimal string representation.
Wraps OpenZeppelin's Strings.toHexString(). Returns lowercase hex with "0x" prefix.
function addressToHex(address account) internal pure returns (string memory);
Parameters
| Name | Type | Description |
|---|---|---|
account | address | The address to convert. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | string | The hexadecimal string representation (42 characters including "0x"). |
bytes32ToString
Converts a bytes32 value to a string, treating it as a null-terminated ASCII string.
Reads bytes until the first null byte (0x00) or end of bytes32. Useful for converting short strings stored in bytes32 back to string type.
function bytes32ToString(bytes32 _bytes32) internal pure returns (string memory);
Parameters
| Name | Type | Description |
|---|---|---|
_bytes32 | bytes32 | The bytes32 value containing a null-terminated ASCII string. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | string | The extracted string (up to 32 characters). |
SystemUtils
Title: SystemUtils
Shared access to revive's System precompile for DotNS contracts.
Canonical wrapper around ISystem at DotnsConstants.REVIVE_SYSTEM, so the precompile
address and interface are wired in one place rather than duplicated per consumer.
Note: security-contact: admin@parity.io
Functions
originIsRoot
Returns whether the transaction-level origin is substrate Root.
Reads the stack origin through ISystem.originIsRoot, which holds through a UUPS
proxy's delegatecall frame where callerIsRoot returns false, and returns false
rather than reverting on a non-Root origin.
function originIsRoot() internal view returns (bool root);
Returns
| Name | Type | Description |
|---|---|---|
root | bool | True when the transaction origin is Root. |
Contents
DotnsNameWhitelist
Inherits: Initializable, UUPSUpgradeable, DotnsRoleManager, IDotnsNameWhitelist
Title: DotnsNameWhitelist
Pre-launch name whitelist. A name is Open until governance reserves it or a claim is accepted for it. Several beneficiaries may claim the same Open name, each with a reason, and governance accepts one as the winner.
Lives behind its own UUPS proxy with its own storage. Callers pass bare labels only; the
contract derives the node from the label and the TLD in the protocol registry, so a
caller cannot supply a mismatched hash. Claims are keyed by the beneficiary user, not
the submitter, so a relayer or a cross-chain sovereign account can submit on a user's
behalf and the name binds to that user. All state is on-chain and queryable through views;
no event indexing is required. A name holds at most maxClaimants live claims, which
bounds the loop that clears them on resolution. Resolving a name deletes its claims,
refunding their storage deposit, so only reserved or won names persist. Governance is Root
or the owner. Substrate Root has no address, so the governance gates check
SystemUtils.originIsRoot, which is true through the proxy's delegatecall frame, before
reading msg.sender. Operators are signed role holders
for day-to-day approvals; the public and PoP controllers hold only the consume hook.
Entries are keyed by the node under the active TLD, which the deployment holds immutable
for the whitelist's lifetime.
Note: security-contact: admin@parity.io
State Variables
protocolRegistry
Protocol-level address registry for all DotNS contracts.
IDotnsProtocolRegistry public protocolRegistry
maxClaimants
Live-claim cap per name, tunable by governance within
DotnsConstants.WHITELIST_MAX_CLAIMANTS_LIMIT.
uint16 public maxClaimants
maxGrantBatch
Cap on labels per grantNames call, tunable by governance within
DotnsConstants.WHITELIST_MAX_GRANT_BATCH_LIMIT.
uint16 public maxGrantBatch
maxReasonBytes
Reason byte cap, tunable by governance within
DotnsConstants.WHITELIST_MAX_REASON_LIMIT.
uint256 public maxReasonBytes
_names
Resolved state per name.
mapping(bytes32 node => NameRecord record) private _names
_claims
Claims per name, keyed by beneficiary.
mapping(bytes32 node => mapping(address user => Claim claim)) private _claims
_claimants
Beneficiaries with a live claim per name.
mapping(bytes32 node => EnumerableSet.AddressSet claimants) private _claimants
_activeNodes
Names holding reserved, claimed or claim-holding state, kept enumerable for review.
EnumerableSet.Bytes32Set private _activeNodes
_requestOpen
Timestamp requests start being accepted.
uint64 private _requestOpen
_requestClose
Timestamp requests stop being accepted.
uint64 private _requestClose
__gap
Reserved storage space to allow for layout changes in the future.
uint256[50] private __gap
Functions
onlyGovernance
Restricts a call to Root or the owner.
Checks Root first so msg.sender, which traps under a Root origin, is read only for a
signed caller.
modifier onlyGovernance() ;
onlyOperatorOrGovernance
Restricts a call to Root, the owner, or an operator.
modifier onlyOperatorOrGovernance() ;
onlyController
Restricts a call to a registrar controller resolved through the registry.
modifier onlyController() ;
constructor
Note: oz-upgrades-unsafe-allow: constructor
constructor() ;
initialize
Initialises the whitelist.
Callable once through the UUPS proxy; direct calls on the implementation
Note: reverts: InvalidInitialization. Sets the deployer as owner and wires the protocol registry the node derivation reads the TLD from.
function initialize(IDotnsProtocolRegistry registry) external initializer;
Parameters
| Name | Type | Description |
|---|---|---|
registry | IDotnsProtocolRegistry | Protocol registry all DotNS contracts resolve through. |
setOperator
Grants or revokes the operator role for account.
Restricted to Root or the owner. Root has no address, so governance uses this rather than the owner-only role-admin path. @custom:emits IAccessControl.RoleGranted on grant and @custom:emits IAccessControl.RoleRevoked on revoke.
function setOperator(address account, bool enabled) external override onlyGovernance;
Parameters
| Name | Type | Description |
|---|---|---|
account | address | Address whose operator role is changed. |
enabled | bool | True to grant, false to revoke. |
setMaxClaimants
Sets the live-claim cap per name.
Restricted to Root or the owner. The cap is bounded by
DotnsConstants.WHITELIST_MAX_CLAIMANTS_LIMIT, which bounds the resolution clear-loop.
Notes:
-
reverts: MaxClaimantsOutOfRange when
newMaxis zero or above the ceiling. -
emits: MaxClaimantsSet.
function setMaxClaimants(uint16 newMax) external override onlyGovernance;
Parameters
| Name | Type | Description |
|---|---|---|
newMax | uint16 | New per-name claim cap. |
setMaxReasonBytes
Sets the reason byte cap.
Restricted to Root or the owner, bounded by
DotnsConstants.WHITELIST_MAX_REASON_LIMIT. @custom:reverts MaxReasonBytesOutOfRange when
newMax is zero or above the ceiling. @custom:emits MaxReasonBytesSet.
function setMaxReasonBytes(uint256 newMax) external override onlyGovernance;
Parameters
| Name | Type | Description |
|---|---|---|
newMax | uint256 | New reason byte cap. |
setMaxGrantBatch
Sets the cap on labels per grantNames call.
Restricted to Root or the owner, bounded by
DotnsConstants.WHITELIST_MAX_GRANT_BATCH_LIMIT. @custom:reverts MaxGrantBatchOutOfRange
when newMax is zero or above the ceiling. @custom:emits MaxGrantBatchSet.
function setMaxGrantBatch(uint16 newMax) external override onlyGovernance;
Parameters
| Name | Type | Description |
|---|---|---|
newMax | uint16 | New batch cap. |
requestName
Claims label for user.
Permissionless within the window; the submitter may differ from user. Requires the
name Open, the window open, user non-zero, a canonical label, user without an
existing claim, and fewer than maxClaimants claims on the name.
Notes:
-
reverts: WindowClosed, @custom:reverts NameNotOpen, @custom:reverts ZeroUser,
-
emits: NameRequested.
function requestName(
string calldata label,
string calldata reason,
address user
)
external
override;
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to claim. |
reason | string | Free-text justification, at most maxReasonBytes bytes. |
user | address | Beneficiary the name binds to if this claim wins. |
accept
Accepts user's claim as the winner of label.
Restricted to an operator, the owner, or Root. Requires user's claim Requested.
Sets the name Claimed with user the winner and clears every claim on the name, rejecting
the losers. @custom:reverts NotRequested. @custom:emits NameAccepted for the winner and
Note: emits: NameRejected for each loser.
function accept(
string calldata label,
address user
)
external
override
onlyOperatorOrGovernance;
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to resolve. |
user | address | Beneficiary whose claim wins. |
reject
Rejects user's pending claim on label without resolving the name.
Restricted to an operator, the owner, or Root. Requires the claim Requested.
Note: reverts: NotRequested. @custom:emits NameRejected.
function reject(
string calldata label,
address user
)
external
override
onlyOperatorOrGovernance;
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label. |
user | address | Beneficiary whose claim is rejected. |
grantName
Grants label to user directly, without a prior claim.
Restricted to an operator, the owner, or Root. Requires the name Open, user non-zero
and a canonical label. Sets the name Claimed with user the winner and clears any pending
claims. @custom:reverts NameNotOpen, @custom:reverts ZeroUser or
Notes:
-
reverts: InvalidLabel. @custom:emits NameAccepted, and
-
emits: NameRejected for each cleared claim.
function grantName(
string calldata label,
address user
)
external
override
onlyOperatorOrGovernance;
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to grant. |
user | address | Beneficiary the name binds to. |
grantNames
Grants several labels to one user directly.
Restricted to an operator, the owner, or Root. Applies @custom:function grantName to
each, at most maxGrantBatch labels per call.
Note:
reverts: TooManyLabels when labels exceeds the batch cap.
function grantNames(
string[] calldata labels,
address user
)
external
override
onlyOperatorOrGovernance;
Parameters
| Name | Type | Description |
|---|---|---|
labels | string[] | Bare labels to grant. |
user | address | Beneficiary each name binds to. |
revokeName
Resets label to Open, clearing any winner and claims.
Restricted to an operator, the owner, or Root. Resolves a Claimed or claim-holding name; a Reserved name is released through @custom:function setReserved, not here.
Notes:
-
reverts: NothingToRevoke when the name is not Claimed and holds no claims.
-
emits: NameRevoked, and @custom:emits NameRejected for each cleared claim.
function revokeName(string calldata label) external override onlyGovernance;
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to reset. |
setReserved
Reserves or releases label.
Restricted to Root or the owner. Reserving requires the name Open and clears any
pending claims, rejecting each; releasing requires it Reserved. @custom:reverts
NameNotOpen or @custom:reverts NotReserved. @custom:emits NameReserved or @custom:emits
NameUnreserved. @param label Bare label.
function setReserved(string calldata label, bool reserved) external override onlyGovernance;
Parameters
| Name | Type | Description |
|---|---|---|
label | string | |
reserved | bool | True to reserve, false to release. |
consume
Removes the win on label as registrant registers it.
Restricted to the registrar controllers resolved through the protocol registry. Resets the name to Open. @custom:reverts NotController for any other caller and
Notes:
-
reverts: NotWinner when
labelis not won byregistrant. -
emits: NameConsumed.
function consume(string calldata label, address registrant) external override onlyController;
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label being registered. |
registrant | address | Address registering the name. |
setWindow
Sets the request window relative to the current time.
Restricted to Root or the owner. Opens at block.timestamp + startsIn for duration.
Note:
reverts: BadWindow when duration is zero. @custom:emits WindowSet.
function setWindow(uint64 startsIn, uint64 duration) external override onlyGovernance;
Parameters
| Name | Type | Description |
|---|---|---|
startsIn | uint64 | Seconds from now until requests start being accepted. |
duration | uint64 | Seconds the window stays open. |
statusOf
Returns the status of label.
function statusOf(string calldata label) external view override returns (NameStatus status);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to look up. |
Returns
| Name | Type | Description |
|---|---|---|
status | NameStatus | Name status; see NameStatus. |
isReserved
Returns whether label is reserved.
function isReserved(string calldata label) external view override returns (bool reserved);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to look up. |
Returns
| Name | Type | Description |
|---|---|---|
reserved | bool | True when the name is Reserved. |
granteeOf
Returns the winner of label, or the zero address when not Claimed.
function granteeOf(string calldata label) external view override returns (address winner);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to look up. |
Returns
| Name | Type | Description |
|---|---|---|
winner | address | Winning beneficiary. |
isGrantedTo
Returns whether account won label.
The pair check the controllers use to admit a registrant. False for the zero address.
function isGrantedTo(
string calldata label,
address account
)
external
view
override
returns (bool granted);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to look up. |
account | address | Address to test against the winner. |
Returns
| Name | Type | Description |
|---|---|---|
granted | bool | True when account is the winner. |
claimOf
Returns user's claim on label.
function claimOf(
string calldata label,
address user
)
external
view
override
returns (Claim memory claim);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to look up. |
user | address | Beneficiary to look up. |
Returns
| Name | Type | Description |
|---|---|---|
claim | Claim | The stored claim; a zeroed struct with None status when absent. |
claimantCount
Returns the number of live claims on label.
function claimantCount(string calldata label) external view override returns (uint256 count);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to look up. |
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Live claim count. |
claims
Returns a page of claims on label for review.
Reads the canonical offset and limit window.
function claims(
string calldata label,
uint256 offset,
uint256 limit
)
external
view
override
returns (Claim[] memory page);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to look up. |
offset | uint256 | Index of the first claim. |
limit | uint256 | Maximum number of claims to return. |
Returns
| Name | Type | Description |
|---|---|---|
page | Claim[] | Claims in the window. |
nameCount
Returns the number of names with reserved, claimed or claim-holding state.
function nameCount() external view override returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Active name count. |
names
Returns a page of active names for review.
Reads the canonical offset and limit window. Iteration order is not stable.
function names(
uint256 offset,
uint256 limit
)
external
view
override
returns (NameView[] memory page);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Index of the first name. |
limit | uint256 | Maximum number of names to return. |
Returns
| Name | Type | Description |
|---|---|---|
page | NameView[] | Names in the window. |
window
Returns the request window.
function window() external view override returns (uint64 openAt, uint64 closeAt);
Returns
| Name | Type | Description |
|---|---|---|
openAt | uint64 | Timestamp requests start being accepted. |
closeAt | uint64 | Timestamp requests stop being accepted. |
isWindowOpen
Returns whether requests are currently accepted.
function isWindowOpen() external view override returns (bool open);
Returns
| Name | Type | Description |
|---|---|---|
open | bool | True when the current time is within the window. |
supportsInterface
function supportsInterface(bytes4 interfaceId)
public
view
override(DotnsRoleManager)
returns (bool supported);
_grant
Grants label to user directly, clearing any pending claims.
function _grant(string calldata label, address user) internal;
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to grant. |
user | address | Beneficiary the name binds to. |
_settle
Marks a name claimed for winner and clears its claims, rejecting the losers.
function _settle(bytes32 node, address winner, string calldata label) internal;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | Namehash of the label under the active TLD. |
winner | address | Beneficiary the name binds to. |
label | string | Bare label, stored for review. |
_clearClaimants
Deletes every claim on a name, rejecting each claimant that is not winner.
function _clearClaimants(bytes32 node, address winner, string calldata label) internal;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | Namehash of the label under the active TLD. |
winner | address | Claimant spared a rejection event; the zero address rejects every claimant. |
label | string | Bare label emitted with each rejection. |
_activate
Records a name as active and stores its label the first time it is seen.
function _activate(bytes32 node, string calldata label) internal;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | Namehash of the label under the active TLD. |
label | string | Bare label stored on first activation. |
_deactivate
Drops a name from the active set once it is Open with no claims.
function _deactivate(bytes32 node) internal;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | Namehash of the label under the active TLD. |
_nodeOf
Derives the namehash of label under the active TLD read from the registry.
function _nodeOf(string calldata label) internal view returns (bytes32 node);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to hash. |
Returns
| Name | Type | Description |
|---|---|---|
node | bytes32 | Namehash of the label under the active TLD. |
_isWindowOpen
Returns whether the current time is within the open window.
function _isWindowOpen() internal view returns (bool open);
Returns
| Name | Type | Description |
|---|---|---|
open | bool | True when the current time is within the window. |
_isSupportedRole
Returns whether role is recognised by the consuming contract.
Implemented by each consuming contract so unsupported role identifiers fail closed.
function _isSupportedRole(bytes32 role) internal pure override returns (bool supported);
_authorizeUpgrade
Function that should revert when msg.sender is not authorized to upgrade the contract.
Called by
{upgradeToAndCall}.
Normally, this function will use an xref:access.adoc[access control] modifier such as
{Ownable-onlyOwner}.
function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;
IDotnsNameWhitelist
Title: IDotnsNameWhitelist
Interface for the pre-launch name whitelist. A name is Open until governance either reserves it or a claim is accepted for it. Several beneficiaries may claim the same Open name, each with a reason, and governance accepts one as the winner.
Callers never supply a hash. Every entry point takes the bare label and derives the node
from the label and the TLD in the protocol registry, so a caller cannot supply a
mismatched hash. Claims are keyed by the beneficiary user, not the submitter, so a
relayer or a cross-chain sovereign account can submit a claim on a user's behalf and the
name still binds to that user. All state is on-chain and queryable through views; no event
indexing is required. Governance is Root or the owner. Substrate Root has no address, so
the governance gates check originIsRoot before reading msg.sender. Operators are signed
role holders for day-to-day approvals; the controllers hold only the consume hook.
Note: security-contact: admin@parity.io
Functions
requestName
Claims label for user.
Permissionless within the window; the submitter may differ from user. Requires the
name Open, the window open, user non-zero, a canonical label, user without an
existing claim, and fewer than maxClaimants claims on the name.
Notes:
-
reverts: WindowClosed, @custom:reverts NameNotOpen, @custom:reverts ZeroUser,
-
reverts: InvalidLabel, @custom:reverts ReasonTooLong,
-
reverts: AlreadyClaimed, or @custom:reverts TooManyClaimants.
-
emits: NameRequested.
function requestName(string calldata label, string calldata reason, address user) external;
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to claim. |
reason | string | Free-text justification, at most maxReasonBytes bytes. |
user | address | Beneficiary the name binds to if this claim wins. |
accept
Accepts user's claim as the winner of label.
Restricted to an operator, the owner, or Root. Requires user's claim Requested.
Sets the name Claimed with user the winner and clears every claim on the name, rejecting
the losers. @custom:reverts NotRequested. @custom:emits NameAccepted for the winner and
Note: emits: NameRejected for each loser.
function accept(string calldata label, address user) external;
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to resolve. |
user | address | Beneficiary whose claim wins. |
reject
Rejects user's pending claim on label without resolving the name.
Restricted to an operator, the owner, or Root. Requires the claim Requested.
Note: reverts: NotRequested. @custom:emits NameRejected.
function reject(string calldata label, address user) external;
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label. |
user | address | Beneficiary whose claim is rejected. |
grantName
Grants label to user directly, without a prior claim.
Restricted to an operator, the owner, or Root. Requires the name Open, user non-zero
and a canonical label. Sets the name Claimed with user the winner and clears any pending
claims. @custom:reverts NameNotOpen, @custom:reverts ZeroUser or
Notes:
-
reverts: InvalidLabel. @custom:emits NameAccepted, and
-
emits: NameRejected for each cleared claim.
function grantName(string calldata label, address user) external;
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to grant. |
user | address | Beneficiary the name binds to. |
grantNames
Grants several labels to one user directly.
Restricted to an operator, the owner, or Root. Applies @custom:function grantName to
each, at most maxGrantBatch labels per call.
Note:
reverts: TooManyLabels when labels exceeds the batch cap.
function grantNames(string[] calldata labels, address user) external;
Parameters
| Name | Type | Description |
|---|---|---|
labels | string[] | Bare labels to grant. |
user | address | Beneficiary each name binds to. |
revokeName
Resets label to Open, clearing any winner and claims.
Restricted to an operator, the owner, or Root. Resolves a Claimed or claim-holding name; a Reserved name is released through @custom:function setReserved, not here.
Notes:
-
reverts: NothingToRevoke when the name is not Claimed and holds no claims.
-
emits: NameRevoked, and @custom:emits NameRejected for each cleared claim.
function revokeName(string calldata label) external;
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to reset. |
setReserved
Reserves or releases label.
Restricted to Root or the owner. Reserving requires the name Open and clears any
pending claims, rejecting each; releasing requires it Reserved. @custom:reverts
NameNotOpen or @custom:reverts NotReserved. @custom:emits NameReserved or @custom:emits
NameUnreserved. @param label Bare label.
function setReserved(string calldata label, bool reserved) external;
Parameters
| Name | Type | Description |
|---|---|---|
label | string | |
reserved | bool | True to reserve, false to release. |
consume
Removes the win on label as registrant registers it.
Restricted to the registrar controllers resolved through the protocol registry. Resets the name to Open. @custom:reverts NotController for any other caller and
Notes:
-
reverts: NotWinner when
labelis not won byregistrant. -
emits: NameConsumed.
function consume(string calldata label, address registrant) external;
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label being registered. |
registrant | address | Address registering the name. |
setWindow
Sets the request window relative to the current time.
Restricted to Root or the owner. Opens at block.timestamp + startsIn for duration.
Note:
reverts: BadWindow when duration is zero. @custom:emits WindowSet.
function setWindow(uint64 startsIn, uint64 duration) external;
Parameters
| Name | Type | Description |
|---|---|---|
startsIn | uint64 | Seconds from now until requests start being accepted. |
duration | uint64 | Seconds the window stays open. |
setOperator
Grants or revokes the operator role for account.
Restricted to Root or the owner. Root has no address, so governance uses this rather than the owner-only role-admin path. @custom:emits IAccessControl.RoleGranted on grant and @custom:emits IAccessControl.RoleRevoked on revoke.
function setOperator(address account, bool enabled) external;
Parameters
| Name | Type | Description |
|---|---|---|
account | address | Address whose operator role is changed. |
enabled | bool | True to grant, false to revoke. |
setMaxClaimants
Sets the live-claim cap per name.
Restricted to Root or the owner. The cap is bounded by
DotnsConstants.WHITELIST_MAX_CLAIMANTS_LIMIT, which bounds the resolution clear-loop.
Notes:
-
reverts: MaxClaimantsOutOfRange when
newMaxis zero or above the ceiling. -
emits: MaxClaimantsSet.
function setMaxClaimants(uint16 newMax) external;
Parameters
| Name | Type | Description |
|---|---|---|
newMax | uint16 | New per-name claim cap. |
setMaxReasonBytes
Sets the reason byte cap.
Restricted to Root or the owner, bounded by
DotnsConstants.WHITELIST_MAX_REASON_LIMIT. @custom:reverts MaxReasonBytesOutOfRange when
newMax is zero or above the ceiling. @custom:emits MaxReasonBytesSet.
function setMaxReasonBytes(uint256 newMax) external;
Parameters
| Name | Type | Description |
|---|---|---|
newMax | uint256 | New reason byte cap. |
setMaxGrantBatch
Sets the cap on labels per grantNames call.
Restricted to Root or the owner, bounded by
DotnsConstants.WHITELIST_MAX_GRANT_BATCH_LIMIT. @custom:reverts MaxGrantBatchOutOfRange
when newMax is zero or above the ceiling. @custom:emits MaxGrantBatchSet.
function setMaxGrantBatch(uint16 newMax) external;
Parameters
| Name | Type | Description |
|---|---|---|
newMax | uint16 | New batch cap. |
maxClaimants
Returns the live-claim cap per name.
function maxClaimants() external view returns (uint16 cap);
Returns
| Name | Type | Description |
|---|---|---|
cap | uint16 | Current per-name claim cap. |
maxReasonBytes
Returns the reason byte cap.
function maxReasonBytes() external view returns (uint256 cap);
Returns
| Name | Type | Description |
|---|---|---|
cap | uint256 | Current reason byte cap. |
maxGrantBatch
Returns the cap on labels per grantNames call.
function maxGrantBatch() external view returns (uint16 cap);
Returns
| Name | Type | Description |
|---|---|---|
cap | uint16 | Current batch cap. |
statusOf
Returns the status of label.
function statusOf(string calldata label) external view returns (NameStatus status);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to look up. |
Returns
| Name | Type | Description |
|---|---|---|
status | NameStatus | Name status; see NameStatus. |
isReserved
Returns whether label is reserved.
function isReserved(string calldata label) external view returns (bool reserved);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to look up. |
Returns
| Name | Type | Description |
|---|---|---|
reserved | bool | True when the name is Reserved. |
granteeOf
Returns the winner of label, or the zero address when not Claimed.
function granteeOf(string calldata label) external view returns (address winner);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to look up. |
Returns
| Name | Type | Description |
|---|---|---|
winner | address | Winning beneficiary. |
isGrantedTo
Returns whether account won label.
The pair check the controllers use to admit a registrant. False for the zero address.
function isGrantedTo(
string calldata label,
address account
)
external
view
returns (bool granted);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to look up. |
account | address | Address to test against the winner. |
Returns
| Name | Type | Description |
|---|---|---|
granted | bool | True when account is the winner. |
claimOf
Returns user's claim on label.
function claimOf(string calldata label, address user) external view returns (Claim memory claim);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to look up. |
user | address | Beneficiary to look up. |
Returns
| Name | Type | Description |
|---|---|---|
claim | Claim | The stored claim; a zeroed struct with None status when absent. |
claimantCount
Returns the number of live claims on label.
function claimantCount(string calldata label) external view returns (uint256 count);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to look up. |
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Live claim count. |
claims
Returns a page of claims on label for review.
Reads the canonical offset and limit window.
function claims(
string calldata label,
uint256 offset,
uint256 limit
)
external
view
returns (Claim[] memory page);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Bare label to look up. |
offset | uint256 | Index of the first claim. |
limit | uint256 | Maximum number of claims to return. |
Returns
| Name | Type | Description |
|---|---|---|
page | Claim[] | Claims in the window. |
nameCount
Returns the number of names with reserved, claimed or claim-holding state.
function nameCount() external view returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Active name count. |
names
Returns a page of active names for review.
Reads the canonical offset and limit window. Iteration order is not stable.
function names(uint256 offset, uint256 limit) external view returns (NameView[] memory page);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Index of the first name. |
limit | uint256 | Maximum number of names to return. |
Returns
| Name | Type | Description |
|---|---|---|
page | NameView[] | Names in the window. |
window
Returns the request window.
function window() external view returns (uint64 openAt, uint64 closeAt);
Returns
| Name | Type | Description |
|---|---|---|
openAt | uint64 | Timestamp requests start being accepted. |
closeAt | uint64 | Timestamp requests stop being accepted. |
isWindowOpen
Returns whether requests are currently accepted.
function isWindowOpen() external view returns (bool open);
Returns
| Name | Type | Description |
|---|---|---|
open | bool | True when the current time is within the window. |
Events
NameRequested
Emitted when a beneficiary claims a name.
event NameRequested(bytes32 indexed node, address indexed user, string label, string reason);
NameAccepted
Emitted when a claim wins a name, including an operator direct grant.
event NameAccepted(bytes32 indexed node, address indexed user, string label);
NameRejected
Emitted when a claim is cleared without winning.
event NameRejected(bytes32 indexed node, address indexed user, string label);
NameRevoked
Emitted when a name is reset to Open by governance.
event NameRevoked(bytes32 indexed node, address indexed winner, string label);
NameConsumed
Emitted when a winner registers the name and its entry is consumed.
event NameConsumed(bytes32 indexed node, address indexed user, string label);
NameReserved
Emitted when governance withholds a name from claiming.
event NameReserved(bytes32 indexed node, string label);
NameUnreserved
Emitted when governance releases a reserved name back to Open.
event NameUnreserved(bytes32 indexed node, string label);
WindowSet
Emitted when the request window is set.
event WindowSet(uint64 openAt, uint64 closeAt);
Parameters
| Name | Type | Description |
|---|---|---|
openAt | uint64 | Timestamp requests start being accepted. |
closeAt | uint64 | Timestamp requests stop being accepted. |
MaxClaimantsSet
Emitted when the live-claim cap is set.
event MaxClaimantsSet(uint16 maxClaimants);
Parameters
| Name | Type | Description |
|---|---|---|
maxClaimants | uint16 | New per-name claim cap. |
MaxReasonBytesSet
Emitted when the reason byte cap is set.
event MaxReasonBytesSet(uint256 maxReasonBytes);
Parameters
| Name | Type | Description |
|---|---|---|
maxReasonBytes | uint256 | New reason byte cap. |
MaxGrantBatchSet
Emitted when the grant-batch cap is set.
event MaxGrantBatchSet(uint16 maxGrantBatch);
Parameters
| Name | Type | Description |
|---|---|---|
maxGrantBatch | uint16 | New grantNames batch cap. |
Errors
ZeroUser
Thrown when a claim names the zero-address beneficiary.
error ZeroUser();
InvalidLabel
Thrown when a label is not a canonical single DNS label.
error InvalidLabel();
ReasonTooLong
Thrown when a reason exceeds maxReasonBytes.
error ReasonTooLong();
NameNotOpen
Thrown when a name is not Open and the action requires it.
error NameNotOpen(bytes32 node);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | Namehash of the label under the active TLD. |
AlreadyClaimed
Thrown when user already holds a claim on the name.
error AlreadyClaimed(bytes32 node, address user);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | Namehash of the label under the active TLD. |
user | address | Beneficiary already holding a claim. |
TooManyClaimants
Thrown when a name already holds maxClaimants claims.
error TooManyClaimants(bytes32 node);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | Namehash of the label under the active TLD. |
MaxClaimantsOutOfRange
Thrown when the claim cap is set to zero or above
DotnsConstants.WHITELIST_MAX_CLAIMANTS_LIMIT.
error MaxClaimantsOutOfRange();
MaxReasonBytesOutOfRange
Thrown when the reason cap is set to zero or above
DotnsConstants.WHITELIST_MAX_REASON_LIMIT.
error MaxReasonBytesOutOfRange();
MaxGrantBatchOutOfRange
Thrown when the grant-batch cap is set to zero or above
DotnsConstants.WHITELIST_MAX_GRANT_BATCH_LIMIT.
error MaxGrantBatchOutOfRange();
NotRequested
Thrown when a claim is not in the Requested status.
error NotRequested(bytes32 node, address user);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | Namehash of the label under the active TLD. |
user | address | Beneficiary whose claim was expected to be pending. |
NotReserved
Thrown when releasing a name that is not reserved.
error NotReserved(bytes32 node);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | Namehash of the label under the active TLD. |
NothingToRevoke
Thrown when revoking a name that is not Claimed and holds no claims.
error NothingToRevoke(bytes32 node);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | Namehash of the label under the active TLD. |
NotController
Thrown when consume is called by any address other than a registrar controller.
error NotController(address caller);
Parameters
| Name | Type | Description |
|---|---|---|
caller | address | Rejected caller. |
NotWinner
Thrown when consume is called for a name not won by the registrant.
error NotWinner(address registrant, bytes32 node);
Parameters
| Name | Type | Description |
|---|---|---|
registrant | address | Address attempting to register the name. |
node | bytes32 | Namehash of the label under the active TLD. |
BadWindow
Thrown when the request window is set with a zero duration.
error BadWindow();
WindowClosed
Thrown when a claim is made outside the open window.
error WindowClosed();
TooManyLabels
Thrown when grantNames is passed more than maxGrantBatch labels.
error TooManyLabels();
Structs
Claim
A claim by one beneficiary on one name.
user, status and requestedAt co-locate in one storage slot; submitter takes the
next, and the dynamic reason is stored separately.
struct Claim {
address user;
ClaimStatus status;
uint64 requestedAt;
address submitter;
string reason;
}
Properties
| Name | Type | Description |
|---|---|---|
user | address | Beneficiary the name would bind to if this claim wins. |
status | ClaimStatus | Claim status; see ClaimStatus. |
requestedAt | uint64 | Timestamp the claim was made. |
submitter | address | Address that filed the claim, which may differ from the beneficiary. |
reason | string | Free-text justification for the claim. |
NameView
A name and its resolved state, for review.
struct NameView {
bytes32 node;
string label;
NameStatus status;
address winner;
}
Properties
| Name | Type | Description |
|---|---|---|
node | bytes32 | Namehash of the label under the active TLD. |
label | string | Bare label. |
status | NameStatus | Name status; see NameStatus. |
winner | address | Winning beneficiary when Claimed, otherwise the zero address. |
NameRecord
Stored resolved state of a name.
status and winner are ordered first so the 1-byte enum and 20-byte address share
one storage slot; the dynamic label is stored separately.
struct NameRecord {
NameStatus status;
address winner;
string label;
}
Properties
| Name | Type | Description |
|---|---|---|
status | NameStatus | Name status; see NameStatus. |
winner | address | Winning beneficiary when Claimed, otherwise the zero address. |
label | string | Bare label, kept so reserved and claimed names are reviewable. |
Enums
NameStatus
Status of a name.
Open is the zero-value default: claimable, not reserved, not won. Reserved is
withheld by governance. Claimed has a single winner.
enum NameStatus {
Open,
Reserved,
Claimed
}
ClaimStatus
Status of a single claim on a name.
None is the zero-value default of an absent claim. Rejected is sticky: it is kept
only when the beneficiary filed the claim themselves, so they cannot re-request; a
claim filed on their behalf is deleted on rejection and does not bind them.
enum ClaimStatus {
None,
Requested,
Rejected
}