[!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

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 lengthPriceOn public sale by default
6 to 810 DOTNo, held behind the short-name switch
9 or more10 DOTYes
5 or fewernot soldissued at zero base cost through the reserved path

Who can register a name

Three bands share the one deposit.

Base lengthWho may register on the public paid pathPrice
9 or moreanyone, as NoStatus10 DOT
6 to 8a verified person, and only while the short-name switch is on10 DOT
5 or fewernobody on the public pathnot 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.

NameBase lengthPathAmountHeld as
gavinwood9public, own key10 DOTrefundable deposit
gavinwood9public, someone else pays10 DOTprotocol fee
andrewsays10public, own key10 DOTrefundable deposit
andrew6public, own key (switch on, full person)10 DOTrefundable deposit
alicebob428public, own key (switch on, lite person)10 DOTrefundable deposit
any six-to-eight name6 to 8personhood gateway grantnoneno deposit, no fee
andrew, moved to a wallet that cannot clear its band6transfer10 DOTprotocol 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.

ClockLengthWhat it gates
withdrawAvailableAtrelease + cooldown (15 minutes at launch, at most 1 hour)When the holder may credit the deposit to themselves through withdraw
redeemableUntilrelease + 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:

  • redeem returns 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.
  • withdraw credits 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.

LabelStemTrailing digitsClassificationEligible public pathPriceNotes
alice12alice2ReservedWhitelist onlyNot sold; issued at 0The stem is five characters, so the two-digit suffix does not make it PopLite.
andrew01andrew2PopLitePop gateway only10 DOTValid 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.
alicebob42alicebob2PopLitePop gateway only10 DOTEight-character stem plus two digits; total length is ten. Gateway grant is free.
andrewandrew0PopFullPopFull user10 DOTCanonical full-person base name; priced only while the short-name switch is on.
andrew1andrew1RejectedNonen/aOne trailing digit has no protocol meaning.
andrewsaysandrewsays0NoStatusAnyone10 DOTBase length 10; the amount is the flat refundable deposit.
andrewsays01andrewsays2NoStatusAnyone10 DOTLong stem remains NoStatus even with a two-digit suffix, and prices at the same flat deposit.
andrew123andrew3RejectedNonen/aMore than two trailing digits is invalid.
andrew.01n/an/aRejected by public label validatorNonen/aDots are not valid in the public flat label. The Pop gateway accepts stem.suffix and normalises it to stemsuffix.
Andrew01n/an/aRejected by canonical label validatorNonen/aLabels 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:

GoalUseWhy
Delegate full control of one name, including the right to transfer it, automatically revoked on saleregistrar 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 themregistrar 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 transfercontent 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.

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

Git Source

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

NameTypeDescription
rolebytes32Role identifier declared in DotnsConstants.
accountaddressAccount whose role membership is updated.
enabledboolWhether 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 account must be non-zero (otherwise

  • reverts: InvalidRoleAccount). Delegates to OpenZeppelin's _grantRole or _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

Git Source

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); account must 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

NameTypeDescription
rolebytes32Role identifier declared in DotnsConstants.
accountaddressAccount whose role membership is updated.
enabledboolWhether 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

Git Source

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

NameTypeDescription
saltbytes32CREATE3 salt; combined with this factory's address to derive the target.
initCodebytesConcatenation of creation bytecode and ABI-encoded constructor args.

Returns

NameTypeDescription
deployedaddressAddress 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

NameTypeDescription
saltbytes32CREATE3 salt to look up.

Returns

NameTypeDescription
predictedaddressAddress 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

NameTypeDescription
saltbytes32The CREATE3 salt used to derive the deployed address.
deployedaddressAddress at which the contract was instantiated.
initCodeHashbytes32keccak256 of the init code passed to @custom:function deploy.
valueuint256Native 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

Git Source

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

NameTypeDescription
registryIDotnsProtocolRegistryProtocol registry used to resolve registrar and controller addresses.
cooldownSecondsuint256Delay after release before the deposit withdrawal may be credited.
redeemWindowSecondsuint256Period 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

NameTypeDescription
countuint256Number 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

NameTypeDescription
assetaddressAsset 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

NameTypeDescription
startuint256Start index into the released-token set.
limituint256Maximum 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

NameTypeDescription
recipientaddressAddress 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

NameTypeDescription
chargeduint256Amount 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

NameTypeDescription
positionReleasePositionStorage pointer to the position being settled.
tokenIduint256
recipientaddressAddress 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

NameTypeDescription
amountuint256Native 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

NameTypeDescription
amountuint256Native 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

NameTypeDescription
entryIduint256Identifier of the entry to claim.

Returns

NameTypeDescription
amountuint256Native 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

NameTypeDescription
entryIdsuint256[]List of entry identifiers to claim.

Returns

NameTypeDescription
totalAmountuint256Sum 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

NameTypeDescription
tokenIduint256
newOwneraddressAddress 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

NameTypeDescription
reclaimableboolTrue 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

NameTypeDescription
versionStringstringCurrent 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

Git Source

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

NameTypeDescription
assetaddressAsset 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

NameTypeDescription
countuint256Number 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

NameTypeDescription
startuint256Start index into the released-token set.
limituint256Maximum 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

NameTypeDescription
recipientaddressAddress 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

NameTypeDescription
chargeduint256Amount 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

NameTypeDescription
balanceuint256Current 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

NameTypeDescription
amountuint256Native amount transferred to the caller.

pendingWithdrawal

Returns the pending refund balance owed to recipient.

function pendingWithdrawal(address recipient) external view returns (uint256 amount);

Returns

NameTypeDescription
amountuint256Native 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

NameTypeDescription
tokenIduint256
newOwneraddressAddress 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

NameTypeDescription
reclaimableboolTrue 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

NameTypeDescription
durationuint256Current cooldown in seconds.

redeemWindow

Period after release during which only the previous holder may act.

function redeemWindow() external view returns (uint256 duration);

Returns

NameTypeDescription
durationuint256Current 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

NameTypeDescription
entryIduint256Identifier of the entry to claim.

Returns

NameTypeDescription
amountuint256Native 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

NameTypeDescription
entryIdsuint256[]List of entry identifiers to claim.

Returns

NameTypeDescription
totalAmountuint256Sum 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

NameTypeDescription
tokenIduint256
recipientaddressRefund recipient snapshotted at release time.
assetaddressDeposit asset. address(0) denotes native token.
amountuint256
withdrawAvailableAtuint256Earliest withdrawal timestamp.
redeemableUntiluint256Timestamp 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

NameTypeDescription
tokenIduint256
recipientaddress
assetaddressRefund asset. address(0) denotes native token.
amountuint256

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

NameTypeDescription
recipientaddressAddress that may claim the entry once availableAt has elapsed.
entryIduint256Newly-assigned identifier for the credited entry.
amountuint256Native value credited.
availableAtuint64Earliest block timestamp at which the recipient may claim.
tokenIduint256Token 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

NameTypeDescription
recipientaddressAddress that pulled the entry.
entryIduint256Identifier of the claimed entry, now deleted.
amountuint256Native 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

NameTypeDescription
tokenIduint256
previousRecipientaddressAddress that received the refund for the prior registration.
newOwneraddress

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

NameTypeDescription
tokenIduint256
recipientaddressAddress 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

NameTypeDescription
tokenIduint256
payeraddressOriginal msg.sender whose value funded the fee.
recipientaddress
amountuint256
isRegistrationboolTrue 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

NameTypeDescription
tokenIduint256
oweduint256
availableuint256Reserve 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

NameTypeDescription
supplieduint256Cooldown value the caller asked for.
maxAlloweduint256Upper 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

NameTypeDescription
supplieduint256Redeem window value the caller asked for.
minAlloweduint256Lower 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

NameTypeDescription
supplieduint256Redeem window value the caller asked for.
maxAlloweduint256Upper 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

NameTypeDescription
tokenIduint256
availableAtuint256Earliest withdrawal timestamp.
currentTimeuint256Current 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

NameTypeDescription
tokenIduint256
assetaddressDeposit asset. The zero address denotes the native token.
amountuint256
recipientaddressInitial 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

NameTypeDescription
tokenIduint256
payeraddressOriginal msg.sender of the controller's register call.
recipientaddressThe 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

NameTypeDescription
tokenIduint256Token whose escrow position is charged and rebound to the recipient.
transferFeeuint256The name's own curve price on a downward or cross-reach transfer.
payeraddressOriginal sender of the registrar transfer entrypoint.
toaddressNFT 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

NameTypeDescription
recipientaddress
assetaddressDeposit asset. address(0) denotes native token.
amountuint256
withdrawAvailableAtuint64Earliest timestamp at which withdrawal is permitted.
releasedbool
claimedbool
redeemableUntiluint64Timestamp 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

NameTypeDescription
recipientaddressAddress that may claim this entry once availableAt has elapsed.
amountuint256Native value credited.
availableAtuint64Earliest block timestamp at which the recipient may claim.
tokenIduint256Token this entry was produced for, retained for traceability.

Contents

Contents

IPersonhood

Git Source

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

NameTypeDescription
accountaddressThe address to query.
contextbytes32A 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

NameTypeDescription
infoPersonhoodInfoThe 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

NameTypeDescription
statusuint8The personhood verification tier.
contextAliasbytes32Context-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

Git Source

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

Git Source

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

NameTypeDescription
owner_addressAddress 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

NameTypeDescription
modelIDotnsPricingThe 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

NameTypeDescription
versionuint256The already-registered version to make current.

current

Returns the current model.

function current() external view override returns (IDotnsPricing model);

Returns

NameTypeDescription
modelIDotnsPricingThe 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

NameTypeDescription
baseLengthuint256Digit-stripped length of the label being priced.

Returns

NameTypeDescription
weiPriceuint256Registration 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

NameTypeDescription
versionuint256The version to price against.
baseLengthuint256Digit-stripped length of the label being priced.

Returns

NameTypeDescription
weiPriceuint256Registration cost in wei at that version.

DotnsFlatPricing

Git Source

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

NameTypeDescription
depositValueuint256Deposit 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

NameTypeDescription
<none>uint256

Returns

NameTypeDescription
weiPriceuint256Registration 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

NameTypeDescription
modelVersionuint256Identifier derived from the model form and its parameters.

DotnsScarcityPricing

Git Source

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

NameTypeDescription
baseFeeValueuint256Base fee D in wei.
minPriceValueuint256Price 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

NameTypeDescription
baseLengthuint256Digit-stripped length of the label being priced.

Returns

NameTypeDescription
weiPriceuint256Registration 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

NameTypeDescription
modelVersionuint256Identifier derived from the model form and its parameters.

IDotnsCostModelRegistry

Git Source

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

NameTypeDescription
modelIDotnsPricingThe 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

NameTypeDescription
versionuint256The 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

NameTypeDescription
versionuint256The version to look up.

Returns

NameTypeDescription
modelIDotnsPricingThe model registered for that version.

currentVersion

Returns the version currently serving fresh pricing.

function currentVersion() external view returns (uint256 version);

Returns

NameTypeDescription
versionuint256The current version identifier.

current

Returns the current model.

function current() external view returns (IDotnsPricing model);

Returns

NameTypeDescription
modelIDotnsPricingThe model serving the current version.

priceForBaseLength

Prices a base length at the current version.

function priceForBaseLength(uint256 baseLength) external view returns (uint256 weiPrice);

Parameters

NameTypeDescription
baseLengthuint256Digit-stripped length of the label being priced.

Returns

NameTypeDescription
weiPriceuint256Registration 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

NameTypeDescription
versionuint256The version to price against.
baseLengthuint256Digit-stripped length of the label being priced.

Returns

NameTypeDescription
weiPriceuint256Registration 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

NameTypeDescription
versionuint256The model's version identifier.
modeladdressThe 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

NameTypeDescription
versionuint256The version now serving fresh pricing.

Errors

AlreadyRegistered

Thrown when registering a model whose version is already held.

error AlreadyRegistered(uint256 version);

Parameters

NameTypeDescription
versionuint256The version already registered.

UnknownVersion

Thrown when pricing against a version that was never registered.

error UnknownVersion(uint256 version);

Parameters

NameTypeDescription
versionuint256The 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

NameTypeDescription
committeduint256The version bound when the commitment was made.
revealeduint256The version supplied at reveal.

IDotnsPricing

Git Source

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

NameTypeDescription
baseLengthuint256Digit-stripped length of the label being priced.

Returns

NameTypeDescription
weiPriceuint256Registration 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

NameTypeDescription
modelVersionuint256Identifier 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

NameTypeDescription
reasonstringHuman-readable explanation of the failed invariant.

IPopRules

Git Source

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

NameTypeDescription
namestringThe name label being evaluated.

Returns

NameTypeDescription
requirementPopStatusRequired tier for registration.
messagestringExplanation 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

NameTypeDescription
enabledboolWhether 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

NameTypeDescription
accountaddressAddress whose tier is read.

Returns

NameTypeDescription
tierPopStatusThe 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

NameTypeDescription
stemstringThe base label with no trailing digits.
useraddressThe 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

NameTypeDescription
stemstringThe base label with no trailing digits.
useraddressThe 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

NameTypeDescription
stemstringThe 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

NameTypeDescription
stemstringThe base label whose reservation should be cleared (no trailing digits).
expectedOwneraddressThe 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

NameTypeDescription
baseNamestringThe base label without trailing digits.

Returns

NameTypeDescription
owneraddressThe address assigned to the reservation.
expiresuint64UNIX 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

NameTypeDescription
namestringFull label (with or without trailing digits).

Returns

NameTypeDescription
stemstringThe 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

NameTypeDescription
baseNamestringThe base label without trailing digits.

Returns

NameTypeDescription
reservedStatusboolTrue if a live reservation is active.
owneraddressThe reservation holder (zero when not reserved).
expiresuint64UNIX 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

NameTypeDescription
namestringDomain label.
userAddressaddressRegistering user for the given label.

Returns

NameTypeDescription
metadataPriceWithMetaPrice 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

NameTypeDescription
namestringDomain label.
userAddressaddressRegistering user for the given label.
pricingVersionValueuint256Cost-model version to price against.

Returns

NameTypeDescription
metadataPriceWithMetaPrice 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

NameTypeDescription
namestringDomain label.
userAddressaddressRegistering user for the given label.

Returns

NameTypeDescription
metadataPriceWithMetaPrice 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

NameTypeDescription
namestringDomain label.
userAddressaddressRegistering user for the given label.
pricingVersionValueuint256Cost-model version to price against.

Returns

NameTypeDescription
metadataPriceWithMetaPrice 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

NameTypeDescription
namestringDomain label being transferred.
fromaddressCurrent holder of the name.
toaddressIncoming holder of the name.

Returns

NameTypeDescription
flooruint256Transfer-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

NameTypeDescription
namestringThe label to check.

Returns

NameTypeDescription
isBaseboolTrue 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

NameTypeDescription
namestringDomain label to price.

Returns

NameTypeDescription
costuint256Registration 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

NameTypeDescription
modelVersionuint256Identifier 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

NameTypeDescription
baseNamestringThe digit-stripped label receiving the reservation.
owneraddressAddress obtaining the reservation right.
expiresuint64UNIX 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

NameTypeDescription
enabledboolWhether names shorter than nine characters may now be bought.

BaseNameReleased

Emitted when a base-name reservation is cleared.

event BaseNameReleased(string indexed baseName);

Parameters

NameTypeDescription
baseNamestringThe base label whose reservation was released.

Errors

PopError

Thrown when a name violates PoP-tier or reservation requirements.

error PopError(string reason);

Parameters

NameTypeDescription
reasonstringHuman-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

NameTypeDescription
labelstringCaller-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

NameTypeDescription
labelstringCaller-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

NameTypeDescription
labelstringLabel whose tier requirement was unmet.
userStatusPopStatusOwner's recorded tier.
requiredPopStatusRequired tier for the label.

Structs

PriceWithMeta

Bundle returned from metadata-aware pricing queries.

struct PriceWithMeta {
    uint256 price;
    PopStatus status;
    PopStatus userStatus;
    string message;
}

Properties

NameTypeDescription
priceuint256Registration cost from the current cost model for the label's base length.
statusPopStatusRequired PoP tier for this name.
userStatusPopStatusCurrent PoP status recorded for the querying user.
messagestringHuman-readable classification description.

Reservation

Reservation metadata for a base name (digits removed).

struct Reservation {
    address owner;
    uint64 expires;
    address controller;
}

Properties

NameTypeDescription
owneraddressAddress holding exclusive claim rights during the reservation window.
expiresuint64UNIX timestamp when the reservation expires.
controlleraddressAddress 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

Git Source

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

NameTypeDescription
registryIDotnsProtocolRegistryProtocol-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

NameTypeDescription
enabledboolWhether 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

NameTypeDescription
namestringThe name label being evaluated.

Returns

NameTypeDescription
requirementPopStatusRequired tier for registration.
messagestringExplanation 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

NameTypeDescription
stemstringThe base label with no trailing digits.
userAddressaddress

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

NameTypeDescription
baseNamestring

Returns

NameTypeDescription
isBaseboolTrue 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

NameTypeDescription
baseNamestringThe base label without trailing digits.

Returns

NameTypeDescription
reservationOwneraddressowner The address assigned to the reservation.
expiryTimestampuint64expires 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

NameTypeDescription
baseNamestringThe base label without trailing digits.

Returns

NameTypeDescription
isReservedboolreservedStatus True if a live reservation is active.
reservationOwneraddressowner The reservation holder (zero when not reserved).
expiryTimestampuint64expires 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

NameTypeDescription
namestringDomain label.
userAddressaddressRegistering user for the given label.

Returns

NameTypeDescription
metadataPriceWithMetaPrice 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

NameTypeDescription
namestringDomain label.
userAddressaddressRegistering user for the given label.
pricingVersionValueuint256Cost-model version to price against.

Returns

NameTypeDescription
metadataPriceWithMetaPrice 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

NameTypeDescription
namestringDomain label.
userAddressaddressRegistering user for the given label.

Returns

NameTypeDescription
metadataPriceWithMetaPrice 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

NameTypeDescription
namestringDomain label.
userAddressaddressRegistering user for the given label.
pricingVersionValueuint256Cost-model version to price against.

Returns

NameTypeDescription
metadataPriceWithMetaPrice 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

NameTypeDescription
namestringDomain label to price.

Returns

NameTypeDescription
<none>uint256cost 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

NameTypeDescription
modelVersionuint256Identifier 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

NameTypeDescription
namestringDomain label being transferred.
fromaddressCurrent holder of the name.
toaddressIncoming holder of the name.

Returns

NameTypeDescription
flooruint256Transfer-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

NameTypeDescription
accountaddressAddress whose tier is read.

Returns

NameTypeDescription
tierPopStatusThe 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

NameTypeDescription
namestringDomain label.
userAddressaddressRegistering 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

NameTypeDescription
labelstringString to analyse.

Returns

NameTypeDescription
digitCountuint256Number of trailing digits.

_stripDigits

Strips trailing digits from a name.

function _stripDigits(string calldata name) internal pure returns (string memory baseName);

Parameters

NameTypeDescription
namestringDomain 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

NameTypeDescription
stemstringThe base label with no trailing digits.
userAddressaddress

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

NameTypeDescription
namestringFull label (with or without trailing digits).

Returns

NameTypeDescription
stemstringThe 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

NameTypeDescription
stemstringThe 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

NameTypeDescription
stemstringThe base label whose reservation should be cleared (no trailing digits).
expectedOwneraddressThe 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

Git Source

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 into IPopRules. The head of this controller's reservation queue is written through IPopRules.reserveBaseNameForPop on every head transition; the slot is cleared through IPopRules.releaseBaseName when the queue empties (claim, final relinquish, final expiry). The public commit-reveal controller routes through IPopRules.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

NameTypeDescription
paramsLiteRegistrationRegistration 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 reservedBaseLabel is 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-registered reservedBaseLabel aborts 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 reached MAX_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

NameTypeDescription
paramsBaseReservationReservation 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

NameTypeDescription
paramsBaseNameReservationReservation 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 LiteUsername branch validates the lite label's NAMEXX shape (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 (the LiteToFullLinked event 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

NameTypeDescription
paramsFullRegistrationRegistration 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.sender and 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; moreRemaining reports 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

NameTypeDescription
moreRemainingboolWhether 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

NameTypeDescription
useraddressAccount whose pending claims are settled.
limituint256Maximum number of entries to settle in this call.

Returns

NameTypeDescription
settledCountuint256Number of entries settled.
moreRemainingboolWhether 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

NameTypeDescription
labelhashbytes32Keccak-256 of the base label whose queue is being read.

Returns

NameTypeDescription
headuint64Index of the live queue head.
tailuint64Index 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

NameTypeDescription
labelhashbytes32Keccak-256 of the base label whose queue is being read.
indexuint64Queue index to look up.

Returns

NameTypeDescription
entryOwneraddressOwner of the slot (zero if empty/relinquished).
joinedAtuint64Timestamp 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

NameTypeDescription
useraddressAccount whose reservation pointer is being read.

Returns

NameTypeDescription
reservationUserReservationPer-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

NameTypeDescription
useraddressAccount whose pending claims are read.
offsetuint256Start index into the queue.
limituint256Maximum entries to return.

Returns

NameTypeDescription
claimsPendingClaim[]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

NameTypeDescription
useraddressAccount whose pending claims are counted.

Returns

NameTypeDescription
countuint256Number 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

NameTypeDescription
countuint256Number 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

NameTypeDescription
offsetuint256Start index.
limituint256Maximum entries to return.

Returns

NameTypeDescription
usersaddress[]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

NameTypeDescription
labelhashbytes32Keccak-256 of the base label.

Returns

NameTypeDescription
baseLabelstringThe 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

NameTypeDescription
versionStringstringCurrent 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 the LabelStore constructor under pallet-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 a LabelStore. 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

NameTypeDescription
storeaddressOwner's LabelStore proxy.
nodebytes32namehash(labelhash) for the entry.
labelstringBare 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

Git Source

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

NameTypeDescription
registryIDotnsProtocolRegistryProtocol 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

NameTypeDescription
registryaddressThe 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

NameTypeDescription
useraddressAccount whose lite names are listed.
offsetuint256Start index into the filtered sequence.
limituint256Maximum entries to return.

Returns

NameTypeDescription
namesName[]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

NameTypeDescription
useraddressAccount whose full names are listed.
offsetuint256Start index into the filtered sequence.
limituint256Maximum entries to return.

Returns

NameTypeDescription
namesName[]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

NameTypeDescription
useraddressAccount whose lite names are counted.

Returns

NameTypeDescription
countuint256Number 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

NameTypeDescription
useraddressAccount whose full names are counted.

Returns

NameTypeDescription
countuint256Number 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

NameTypeDescription
namestringBare DNS label (no TLD).

Returns

NameTypeDescription
<none>NameDetaildetail 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

NameTypeDescription
nodebytes32namehash of the name.

Returns

NameTypeDescription
<none>NameDetaildetail 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

NameTypeDescription
useraddressAccount being summarised.

Returns

NameTypeDescription
profilePopProfileThe 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

Git Source

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

NameTypeDescription
iduint256
owneraddress
labelstringThe 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 tokenId with

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

NameTypeDescription
versionStringstringCurrent 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

Git Source

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; maxAge must exceed minAge (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

NameTypeDescription
pricingVersionuint256The 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

NameTypeDescription
versionStringstringCurrent 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

Git Source

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

Git Source

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 reservedBaseLabel is 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-registered reservedBaseLabel aborts 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 reached MAX_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

NameTypeDescription
paramsBaseReservationReservation 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

NameTypeDescription
paramsBaseNameReservationReservation 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

NameTypeDescription
paramsLiteRegistrationRegistration 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 LiteUsername branch validates the lite label's NAMEXX shape (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 (the LiteToFullLinked event 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

NameTypeDescription
paramsFullRegistrationRegistration 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

NameTypeDescription
labelhashbytes32Keccak-256 of the base label whose queue is being read.

Returns

NameTypeDescription
headuint64Index of the live queue head.
tailuint64Index 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

NameTypeDescription
labelhashbytes32Keccak-256 of the base label whose queue is being read.
indexuint64Queue index to look up.

Returns

NameTypeDescription
entryOwneraddressOwner of the slot (zero if empty/relinquished).
joinedAtuint64Timestamp 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

NameTypeDescription
useraddressAccount whose reservation pointer is being read.

Returns

NameTypeDescription
reservationUserReservationPer-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

NameTypeDescription
labelhashbytes32Keccak-256 of the base label.

Returns

NameTypeDescription
baseLabelstringThe 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

NameTypeDescription
durationuint64Reservation 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

NameTypeDescription
useraddressAccount whose pending claims are settled.
limituint256Maximum number of entries to settle in this call.

Returns

NameTypeDescription
settledCountuint256Number of entries settled.
moreRemainingboolWhether 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.sender and 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; moreRemaining reports 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

NameTypeDescription
moreRemainingboolWhether 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

NameTypeDescription
useraddressAccount whose pending claims are read.
offsetuint256Start index into the queue.
limituint256Maximum entries to return.

Returns

NameTypeDescription
claimsPendingClaim[]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

NameTypeDescription
useraddressAccount whose pending claims are counted.

Returns

NameTypeDescription
countuint256Number 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

NameTypeDescription
countuint256Number 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

NameTypeDescription
offsetuint256Start index.
limituint256Maximum entries to return.

Returns

NameTypeDescription
usersaddress[]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

NameTypeDescription
reservedLabelhashbytes32
useraddress
positionuint64Position 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

NameTypeDescription
labelstring
labelhashbytes32
owneraddress
storeaddressThe 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

NameTypeDescription
useraddressAccount the settled name belongs to.
labelhashbytes32Labelhash of the settled name.
storeaddressThe LabelStore the label was written into.
settledByaddressCaller 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

NameTypeDescription
labelhashbytes32Base-label hash whose queue head changed.
newHeadaddressAddress 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

NameTypeDescription
lengthuint256Caller-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

NameTypeDescription
useraddressRegistrant supplied by the gateway.
liteLabelhashbytes32Lite 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

NameTypeDescription
durationuint64Caller-supplied duration, in seconds.

Structs

Tagged union selecting the chat-key source for a full-person registration.

struct Link {
    LinkKind kind;
    string liteLabel;
    bytes chatKey;
}

Properties

NameTypeDescription
kindLinkKind
liteLabelstringLite-person NAMEXX label (only read when kind == LiteUsername).
chatKeybytesChat 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

NameTypeDescription
labelhashbytes32Non-zero when the user holds a live reservation; zero otherwise.
indexuint64Monotonic 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

NameTypeDescription
labelstringBare DNS label (no TLD); the TLD is appended at settlement time.
mintedAtuint64Timestamp 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

NameTypeDescription
liteLabelstringLite-person NAMEXX label being minted.
useraddressBeneficiary account on this chain.
chatKeybytesChat-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

NameTypeDescription
liteLiteRegistrationLite-person registration request; see LiteRegistration.
reservedBaseLabelstringBase 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

NameTypeDescription
useraddressBeneficiary account that will hold the reservation.
reservedBaseLabelstringBase label to enqueue for a later full-person claim.

FullRegistration

Full-person registration payload.

struct FullRegistration {
    string label;
    address user;
    Link link;
}

Properties

NameTypeDescription
labelstringBase DNS label being minted.
useraddressBeneficiary account on this chain.
linkLinkChat-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

Git Source

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

NameTypeDescription
registryaddressThe 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

NameTypeDescription
useraddressAccount whose lite names are listed.
offsetuint256Start index into the filtered sequence.
limituint256Maximum entries to return.

Returns

NameTypeDescription
namesName[]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

NameTypeDescription
useraddressAccount whose full names are listed.
offsetuint256Start index into the filtered sequence.
limituint256Maximum entries to return.

Returns

NameTypeDescription
namesName[]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

NameTypeDescription
useraddressAccount whose lite names are counted.

Returns

NameTypeDescription
countuint256Number 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

NameTypeDescription
useraddressAccount whose full names are counted.

Returns

NameTypeDescription
countuint256Number 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

NameTypeDescription
namestringBare DNS label (no TLD).

Returns

NameTypeDescription
detailNameDetailThe 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

NameTypeDescription
nodebytes32namehash of the name.

Returns

NameTypeDescription
detailNameDetailThe 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

NameTypeDescription
useraddressAccount being summarised.

Returns

NameTypeDescription
profilePopProfileThe 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

NameTypeDescription
nodebytes32namehash of the name; the key for chat-key, link, and detail lookups.
labelstringFull name string.
settledboolWhether the label is written into a LabelStore.
deadlineuint64Pending 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

NameTypeDescription
nodebytes32namehash of the name.
labelstringFull name string, or empty when the name is unminted or its claim is unsettled.
owneraddressCurrent registrar owner, or the zero address when the name does not exist.
existsboolWhether the name is minted.
settledboolWhether the label is written into the current owner's LabelStore.
tierIPopRules.PopStatusPopRules classification of the label.
chatKeybytesChat-key bytes recorded on the PoP resolver for the node.
liteLinkbytes32For a full name, the linked lite labelhash; zero otherwise.
fullClaimbytes32For 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

NameTypeDescription
hasLabelStoreboolWhether the account has a deployed LabelStore.
pendingClaimCountuint256Number of claims still staged in the pending queue.
reservationLabelhashbytes32The base label the account holds a live reservation on, or zero when none.

IDotnsRegistrar

Git Source

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 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: 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

NameTypeDescription
iduint256
owneraddress
labelstringThe 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

NameTypeDescription
controllerIDotnsControllerCandidate controller.

Returns

NameTypeDescription
authorisedboolTrue 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 to with

  • reverts: ERC721InvalidReceiver, an unminted tokenId with

  • 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_FACTORY configured 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

Git Source

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 runs priceWithCheck (personhood

  • reservation gate) and routes the charge to a refundable escrow deposit owned by registration.owner. The cross-payer path skips the personhood revert in priceWithCheck but 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 committed pricingVersion, 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 to msg.sender inline 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

NameTypeDescription
labelstring
labelhashbytes32
owneraddress
baseCostuint256The price returned by the oracle for this registration.
storeaddressThe 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

NameTypeDescription
labelstringCaller-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

NameTypeDescription
labelstringLabel whose charge exceeded the ceiling.
chargeduint256Total charge computed at reveal.
maxPriceuint256Ceiling 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

NameTypeDescription
labelstringLabel being registered (e.g. "alice").
owneraddressBeneficiary the registered name is minted to.
secretbytes32Caller-chosen entropy that hides the registration intent in the commit hash; revealed verbatim at registration time.
reservedboolTrue when the registration flows through the whitelisted reserved pipeline (registerReserved); false for the standard public flow (register).
maxPriceuint256Ceiling 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.
pricingVersionuint256Cost-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

Git Source

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

NameTypeDescription
tldLabelstringBare 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

NameTypeDescription
versionStringstringCurrent 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

Git Source

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. registry must be non-zero, otherwise

  • reverts: NotAllowed.

function initialize(IDotnsProtocolRegistry registry) external initializer;

Parameters

NameTypeDescription
registryIDotnsProtocolRegistryProtocol-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].resolver to 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. Stores owner = address(0) as a sentinel so reads delegate to IDotnsRegistrar.ownerOf and 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

NameTypeDescription
nodebytes32
newResolveraddress

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

NameTypeDescription
nodebytes32Node identifier.
accountaddressAddress whose authority is being checked.

Returns

NameTypeDescription
authorisedFlagboolTrue 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

NameTypeDescription
versionStringstringCurrent 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

Git Source

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

Git Source

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.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

  • reverts: ParentLabelMismatch). Subnodes are parent-sovereign: the current record.parentNode owner 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's LabelStore keyed by the namehashed subnode so 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 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

  • 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].resolver to 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. Stores owner = address(0) as a sentinel so reads delegate to IDotnsRegistrar.ownerOf and 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

NameTypeDescription
nodebytes32
resolverAddraddressResolver 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

NameTypeDescription
nodebytes32Node identifier.
accountaddressAddress whose authority is being checked.

Returns

NameTypeDescription
authorisedFlagboolTrue 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

NameTypeDescription
nodebytes32Parent node.
labelbytes32Labelhash of the created subnode.
owneraddress

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

NameTypeDescription
parentNodebytes32
subLabelstringHuman readable subnode label e.g "alice".
parentLabelstringCanonical parent name without the TLD suffix e.g. bob or child.bob.
owneraddressAddress 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

NameTypeDescription
owneraddressAddress that owns the node, or address(0) sentinel for tokenised nodes.
resolveraddressAddress of the resolver associated with the node.
existsboolWhether 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

NameTypeDescription
parentNodebytes32
subLabelstringHuman-readable subnode label e.g "alice".
parentLabelstringCanonical parent name without the TLD suffix e.g bob or child.bob.
resolveraddressResolver contract address (zero clears).

Contents

DotnsContentResolver

Git Source

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

NameTypeDescription
registryIDotnsProtocolRegistryProtocol-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

NameTypeDescription
nodebytes32The node whose content hash is being set.
hashbytesOpaque content hash bytes.

contenthash

Returns the content hash associated with a node.

function contenthash(bytes32 node) external view override returns (bytes memory hash);

Parameters

NameTypeDescription
nodebytes32The node to query.

Returns

NameTypeDescription
hashbytesThe 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

NameTypeDescription
nodebytes32The node whose text record is being set.
keystringText record key (e.g., "ipfs", "avatar").
valuestringText 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

NameTypeDescription
nodebytes32The node to query.
keystringText record key.

Returns

NameTypeDescription
valuestringStored 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

NameTypeDescription
operatoraddressAddress to authorise or revoke.
approvedboolTrue 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

NameTypeDescription
owneraddressThe owner of the nodes.
operatoraddressThe address acting on behalf of the owner.

Returns

NameTypeDescription
<none>boolTrue 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

NameTypeDescription
nodebytes32Node identifier.

version

Returns implementation version.

function version() external pure virtual returns (string memory versionString);

Returns

NameTypeDescription
versionStringstringCurrent 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

Git Source

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

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

NameTypeDescription
registryIDotnsProtocolRegistryProtocol-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

NameTypeDescription
nodebytes32The node whose chat key is being written.
chatKeyBytesbytes

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 same fullNode to a new liteLabelhash clears fullClaim(oldLite), and re-linking the same liteLabelhash to a new fullNode clears liteLink(oldFull). The invariant fullClaim(liteLink(node)) == node always holds after the call. Emits

  • emits: LiteLinkUpdated on every successful write.

function setLiteLink(
    bytes32 fullNode,
    bytes32 liteLabelhash
)
    external
    override
    onlyPopController;

Parameters

NameTypeDescription
fullNodebytes32The full-person node carrying the link.
liteLabelhashbytes32The 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

NameTypeDescription
nodebytes32The node to query.

Returns

NameTypeDescription
<none>byteschatKey The stored chat key bytes, or empty if unset.

Returns the lite-person labelhash linked to a full-person node.

function liteLink(bytes32 fullNode) external view override returns (bytes32);

Parameters

NameTypeDescription
fullNodebytes32The full-person node to query.

Returns

NameTypeDescription
<none>bytes32liteLabelhash 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

NameTypeDescription
liteLabelhashbytes32The labelhash of the lite-person username to query.

Returns

NameTypeDescription
<none>bytes32fullNode 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

NameTypeDescription
versionStringstringCurrent 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

Git Source

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

NameTypeDescription
nodebytes32Node 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

NameTypeDescription
registryIDotnsProtocolRegistryProtocol-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

NameTypeDescription
nodebytes32The node identifier.
valueaddressThe 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

NameTypeDescription
nodebytes32The node identifier.

Returns

NameTypeDescription
valueaddressThe 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

NameTypeDescription
nodebytes32Node identifier.

version

Returns implementation version.

function version() external pure virtual returns (string memory versionString);

Returns

NameTypeDescription
versionStringstringCurrent 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

Git Source

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

NameTypeDescription
registryIDotnsProtocolRegistryProtocol-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

NameTypeDescription
addraddressThe address for which the reverse name is being set.
namestringThe 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

NameTypeDescription
labelstringThe 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

NameTypeDescription
addraddressThe address to query.

Returns

NameTypeDescription
namestringThe 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

NameTypeDescription
versionStringstringCurrent 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

Git Source

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

NameTypeDescription
nodebytes32The node whose content hash is being set.
hashbytesOpaque content hash bytes.

contenthash

Returns the content hash associated with a node.

function contenthash(bytes32 node) external view returns (bytes memory hash);

Parameters

NameTypeDescription
nodebytes32The node to query.

Returns

NameTypeDescription
hashbytesThe 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

NameTypeDescription
nodebytes32The node whose text record is being set.
keystringText record key (e.g., "ipfs", "avatar").
valuestringText record value.

text

Returns a text record for a node.

function text(bytes32 node, string calldata key) external view returns (string memory value);

Parameters

NameTypeDescription
nodebytes32The node to query.
keystringText record key.

Returns

NameTypeDescription
valuestringStored 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

NameTypeDescription
operatoraddressAddress to authorise or revoke.
approvedboolTrue 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

NameTypeDescription
owneraddressThe owner of the nodes.
operatoraddressThe address acting on behalf of the owner.

Returns

NameTypeDescription
<none>boolTrue 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

NameTypeDescription
nodebytes32The node whose content hash was updated.
hashbytesThe 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

NameTypeDescription
nodebytes32The node whose text record was updated.
keystringThe text record key.
valuestringThe new text record value.

ApprovalForAll

Emitted when an operator is approved or revoked.

event ApprovalForAll(address indexed owner, address indexed operator, bool approved);

Parameters

NameTypeDescription
owneraddressThe owner of the nodes.
operatoraddressThe operator address.
approvedboolTrue 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

NameTypeDescription
nodebytes32The node being modified.
calleraddressThe address attempting the modification.

IDotnsPopResolver

Git Source

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 liteLink on every write so a caller that holds a lite labelhash can resolve the full-person node without scanning events. Lives separately from the per-user LabelStore so 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_CONTROLLER at call time, so rotating the PoP controller is a single set on 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

NameTypeDescription
nodebytes32The node whose chat key is being written.
chatKeybytesECDH public key bytes (pallet-side type is [u8; 65]).

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 same fullNode to a new liteLabelhash clears fullClaim(oldLite), and re-linking the same liteLabelhash to a new fullNode clears liteLink(oldFull). The invariant fullClaim(liteLink(node)) == node always holds after the call. Emits

  • emits: LiteLinkUpdated on every successful write.

function setLiteLink(bytes32 fullNode, bytes32 liteLabelhash) external;

Parameters

NameTypeDescription
fullNodebytes32The full-person node carrying the link.
liteLabelhashbytes32The 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

NameTypeDescription
nodebytes32The node to query.

Returns

NameTypeDescription
chatKeybytesThe stored chat key bytes, or empty if unset.

Returns the lite-person labelhash linked to a full-person node.

function liteLink(bytes32 fullNode) external view returns (bytes32 liteLabelhash);

Parameters

NameTypeDescription
fullNodebytes32The full-person node to query.

Returns

NameTypeDescription
liteLabelhashbytes32The 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

NameTypeDescription
liteLabelhashbytes32The labelhash of the lite-person username to query.

Returns

NameTypeDescription
fullNodebytes32The 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

NameTypeDescription
nodebytes32The node whose chat key was written.
chatKeybytesThe 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

NameTypeDescription
fullNodebytes32The full-person node carrying the link.
liteLabelhashbytes32The labelhash of the linked lite-person username.

Errors

NotPopController

Thrown when the caller is not the authorised PoP controller.

error NotPopController(address caller);

Parameters

NameTypeDescription
calleraddressThe 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

NameTypeDescription
lengthuint256The length of the payload that was rejected.

IDotnsResolver

Git Source

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

NameTypeDescription
nodebytes32The node identifier.
valueaddressThe address to associate with the node.

addressOf

Returns the resolved address for a node.

function addressOf(bytes32 node) external view returns (address value);

Parameters

NameTypeDescription
nodebytes32The node identifier.

Returns

NameTypeDescription
valueaddressThe resolved address, or zero if unset.

Events

AddressSet

Emitted when an address record is updated.

event AddressSet(bytes32 indexed node, address value);

Parameters

NameTypeDescription
nodebytes32The node whose address record changed.
valueaddressThe new resolved address.

Errors

NotAuthorised

Thrown when a caller is not authorised to modify a node.

error NotAuthorised(bytes32 node, address caller);

Parameters

NameTypeDescription
nodebytes32The node being modified.
calleraddressThe address attempting the modification.

IDotnsReverseResolver

Git Source

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

NameTypeDescription
addraddressThe address for which the reverse name is being set.
namestringThe 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

NameTypeDescription
labelstringThe 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

NameTypeDescription
addraddressThe address to query.

Returns

NameTypeDescription
namestringThe 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

NameTypeDescription
addraddressThe address for which the reverse name is being set.
namestringThe 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

NameTypeDescription
calleraddressThe 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

NameTypeDescription
calleraddressThe address attempting the claim.
tokenIduint256The token identifier derived from the claimed label.

Contents

IDotnsStore

Git Source

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

NameTypeDescription
owner_addressThe bound user address.

ILabelStore

Git Source

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

NameTypeDescription
user_address
protocolRegistry_addressThe 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. labelhash must 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

NameTypeDescription
labelhashbytes32The labelhash key.
labelstringThe label string to store.

protocolRegistry

Returns the protocol registry this store queries for write authorisation.

function protocolRegistry() external view returns (address protocolRegistry_);

Returns

NameTypeDescription
protocolRegistry_addressThe registry address.

hasLabel

Returns true iff a label has been stored under labelhash.

function hasLabel(bytes32 labelhash) external view returns (bool exists);

Parameters

NameTypeDescription
labelhashbytes32The labelhash to check.

Returns

NameTypeDescription
existsboolTrue 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

NameTypeDescription
labelhashbytes32The labelhash to check.

Returns

NameTypeDescription
lockedboolTrue 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

NameTypeDescription
labelhashbytes32The labelhash to look up.

Returns

NameTypeDescription
labelstringThe stored label string.

getLabelCount

Returns the total number of labels ever stored.

function getLabelCount() external view returns (uint256 count);

Returns

NameTypeDescription
countuint256Current 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

NameTypeDescription
indexuint256Zero-based index into the insertion-order list.

Returns

NameTypeDescription
labelstringThe 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

NameTypeDescription
indexuint256Zero-based index into the insertion-order list.

Returns

NameTypeDescription
labelhashbytes32The 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

NameTypeDescription
offsetuint256Start index.
limituint256Maximum entries to return.

Returns

NameTypeDescription
labelsstring[]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

NameTypeDescription
offsetuint256Start index.
limituint256Maximum entries to return.

Returns

NameTypeDescription
labelhashesbytes32[]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

NameTypeDescription
owneraddress
labelhashbytes32The labelhash key.
labelstringThe 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

NameTypeDescription
calleraddressThe msg.sender that failed the isRegisteredAddress check.

InvalidUser

Thrown when initialize is called with a zero user address.

error InvalidUser(address user);

Parameters

NameTypeDescription
useraddressThe invalid user argument.

InvalidProtocolRegistry

Thrown when initialize is called with a zero protocol registry address.

error InvalidProtocolRegistry(address protocolRegistry);

Parameters

NameTypeDescription
protocolRegistryaddressThe invalid registry argument.

InvalidLabel

Thrown when storeLabel is called with a zero labelhash.

error InvalidLabel(bytes32 labelhash);

Parameters

NameTypeDescription
labelhashbytes32The 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

NameTypeDescription
labelhashbytes32The conflicting labelhash.

IStoreFactory

Git Source

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

NameTypeDescription
beaconaddressAddress of the beacon contract.

userStoreBeacon

Returns the UpgradeableBeacon address backing all UserStore proxies.

function userStoreBeacon() external view returns (address beacon);

Returns

NameTypeDescription
beaconaddressAddress of the beacon contract.

protocolRegistry

Returns the protocol registry address used for writer authorisation.

function protocolRegistry() external view returns (address registry);

Returns

NameTypeDescription
registryaddressAddress 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

NameTypeDescription
useraddressThe user the store is bound to forever.

Returns

NameTypeDescription
storeaddressThe 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

NameTypeDescription
useraddressThe user to look up.

Returns

NameTypeDescription
storeaddressThe bound store address, or zero.

getLabelStoreCount

Returns the total number of LabelStore proxies ever deployed.

function getLabelStoreCount() external view returns (uint256 count);

Returns

NameTypeDescription
countuint256Length 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

NameTypeDescription
offsetuint256Start index.
limituint256Maximum entries to return.

Returns

NameTypeDescription
storesaddress[]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. newImplementation must be non-zero, otherwise @custom:reverts InvalidImplementation. The candidate is sentinel-probed by calling ILabelStore.protocolRegistry on 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 to UpgradeableBeacon.upgradeTo and emits

  • emits: LabelStoreImplementationUpgraded on success.

function upgradeLabelStoreImplementation(address newImplementation) external;

Parameters

NameTypeDescription
newImplementationaddressThe 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

NameTypeDescription
storeaddressThe 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

NameTypeDescription
useraddressThe user to look up.

Returns

NameTypeDescription
storeaddressThe bound store address, or zero.

getUserStoreCount

Returns the total number of UserStore proxies ever claimed.

function getUserStoreCount() external view returns (uint256 count);

Returns

NameTypeDescription
countuint256Length 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

NameTypeDescription
offsetuint256Start index.
limituint256Maximum entries to return.

Returns

NameTypeDescription
storesaddress[]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. newImplementation must be non-zero, otherwise @custom:reverts InvalidImplementation. The candidate is sentinel-probed by calling IUserStore.getKeyCount on 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 to UpgradeableBeacon.upgradeTo and emits

  • emits: UserStoreImplementationUpgraded on success.

function upgradeUserStoreImplementation(address newImplementation) external;

Parameters

NameTypeDescription
newImplementationaddressThe new implementation address.

Events

LabelStoreDeployed

Emitted when a LabelStore beacon-proxy is deployed for user.

event LabelStoreDeployed(address indexed user, address indexed store);

Parameters

NameTypeDescription
useraddressThe user the store is bound to.
storeaddressThe deployed store address.

UserStoreClaimed

Emitted when user claims their UserStore beacon-proxy.

event UserStoreClaimed(address indexed user, address indexed store);

Parameters

NameTypeDescription
useraddressThe user the store is bound to.
storeaddressThe deployed store address.

LabelStoreImplementationUpgraded

Emitted when the LabelStore implementation behind the label beacon is upgraded.

event LabelStoreImplementationUpgraded(address indexed newImplementation);

Parameters

NameTypeDescription
newImplementationaddressThe new implementation address.

UserStoreImplementationUpgraded

Emitted when the UserStore implementation behind the user beacon is upgraded.

event UserStoreImplementationUpgraded(address indexed newImplementation);

Parameters

NameTypeDescription
newImplementationaddressThe new implementation address.

Errors

AlreadyDeployed

Thrown when attempting to deploy or claim a store that already exists.

error AlreadyDeployed(address user, address existingStore);

Parameters

NameTypeDescription
useraddressThe user for whom the store exists.
existingStoreaddressThe already-deployed store address.

InvalidUser

Thrown when a zero user address is supplied.

error InvalidUser(address user);

Parameters

NameTypeDescription
useraddressThe invalid user argument.

InvalidProtocolRegistry

Thrown when a zero protocol registry address is supplied to the constructor.

error InvalidProtocolRegistry(address protocolRegistry);

Parameters

NameTypeDescription
protocolRegistryaddressThe 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

NameTypeDescription
calleraddressThe unauthorised msg.sender.

ImplementationBindingMismatch

Thrown when a freshly deployed proxy does not report the expected owner.

error ImplementationBindingMismatch();

IUserStore

Git Source

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

NameTypeDescription
user_addressThe 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

NameTypeDescription
keybytes32The key to write.
valuebytesThe 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

NameTypeDescription
keybytes32The key to read.

Returns

NameTypeDescription
valuebytesThe 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

NameTypeDescription
keybytes32The key to check.

Returns

NameTypeDescription
presentboolTrue 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

NameTypeDescription
keybytes32The key to read.

Returns

NameTypeDescription
countuint256Length 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

NameTypeDescription
keybytes32The key to read.
indexuint256Zero-based index into the history list.

Returns

NameTypeDescription
entryEntryThe (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

NameTypeDescription
keybytes32The key to read.
offsetuint256Start index.
limituint256Maximum entries to return.

Returns

NameTypeDescription
entriesEntry[]Slice of history entries.

getKeyCount

Returns the number of distinct keys ever written.

function getKeyCount() external view returns (uint256 count);

Returns

NameTypeDescription
countuint256Length 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

NameTypeDescription
indexuint256Zero-based index into the key list.

Returns

NameTypeDescription
keybytes32The 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

NameTypeDescription
offsetuint256Start index.
limituint256Maximum entries to return.

Returns

NameTypeDescription
keysbytes32[]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

NameTypeDescription
owneraddressThe user this store is bound to.
keybytes32The key being written.
valuebytesThe new current value.

Errors

NotOwner

Thrown when any caller other than the bound owner attempts a write.

error NotOwner(address caller);

Parameters

NameTypeDescription
calleraddressThe unauthorised msg.sender.

InvalidUser

Thrown when initialize is called with a zero user address.

error InvalidUser(address user);

Parameters

NameTypeDescription
useraddressThe 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

NameTypeDescription
valuebytes
timestampuint256Block timestamp at the moment of supersession.

LabelStore

Git Source

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

NameTypeDescription
user_address
protocolRegistry_addressThe 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

NameTypeDescription
labelhashbytes32The labelhash key.
labelstringThe 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

NameTypeDescription
owner_addressThe bound user address.

protocolRegistry

Returns the protocol registry this store queries for write authorisation.

function protocolRegistry() external view override returns (address protocolRegistry_);

Returns

NameTypeDescription
protocolRegistry_addressThe registry address.

hasLabel

Returns true iff a label has been stored under labelhash.

function hasLabel(bytes32 labelhash) external view override returns (bool exists);

Parameters

NameTypeDescription
labelhashbytes32The labelhash to check.

Returns

NameTypeDescription
existsboolTrue 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

NameTypeDescription
labelhashbytes32The labelhash to check.

Returns

NameTypeDescription
lockedboolTrue 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

NameTypeDescription
labelhashbytes32The labelhash to look up.

Returns

NameTypeDescription
labelstringThe stored label string.

getLabelCount

Returns the total number of labels ever stored.

function getLabelCount() external view override returns (uint256 count);

Returns

NameTypeDescription
countuint256Current 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

NameTypeDescription
indexuint256Zero-based index into the insertion-order list.

Returns

NameTypeDescription
labelstringThe 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

NameTypeDescription
indexuint256Zero-based index into the insertion-order list.

Returns

NameTypeDescription
labelhashbytes32The 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

NameTypeDescription
offsetuint256Start index.
limituint256Maximum entries to return.

Returns

NameTypeDescription
labelsstring[]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

NameTypeDescription
offsetuint256Start index.
limituint256Maximum entries to return.

Returns

NameTypeDescription
labelhashesbytes32[]Slice of labelhash keys.

version

Returns implementation version.

function version() external pure virtual returns (string memory versionString);

Returns

NameTypeDescription
versionStringstringCurrent version string.

_onlyAuthorisedProtocol

Internal authorisation check deferred from the onlyAuthorisedProtocol modifier.

function _onlyAuthorisedProtocol() internal view;

StoreFactory

Git Source

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 via deployLabelStoreFor by a protocol-registered caller during registration; and
  • a UserStore, claimed via claimUserStore by the user themselves. Both are BeaconProxy instances pointing at their respective UpgradeableBeacon. 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 LabelStore implementation.
  • Deploys a fresh UserStore implementation.
  • Constructs both UpgradeableBeacon instances, owned by address(this) so upgrade*Implementation can delegate to beacon.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

NameTypeDescription
protocolRegistry_addressThe protocol registry for writer auth on label stores.
owner_addressAccount 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

NameTypeDescription
useraddressThe user the store is bound to forever.

Returns

NameTypeDescription
storeaddressThe 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

NameTypeDescription
useraddressThe user to look up.

Returns

NameTypeDescription
storeaddressThe bound store address, or zero.

getLabelStoreCount

Returns the total number of LabelStore proxies ever deployed.

function getLabelStoreCount() external view override returns (uint256 count);

Returns

NameTypeDescription
countuint256Length 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

NameTypeDescription
offsetuint256Start index.
limituint256Maximum entries to return.

Returns

NameTypeDescription
storesaddress[]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. newImplementation must be non-zero, otherwise @custom:reverts InvalidImplementation. The candidate is sentinel-probed by calling ILabelStore.protocolRegistry on 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 to UpgradeableBeacon.upgradeTo and emits

  • emits: LabelStoreImplementationUpgraded on success.

function upgradeLabelStoreImplementation(address newImplementation)
    external
    override
    onlyOwner;

Parameters

NameTypeDescription
newImplementationaddressThe 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

NameTypeDescription
storeaddressThe 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

NameTypeDescription
useraddressThe user to look up.

Returns

NameTypeDescription
storeaddressThe bound store address, or zero.

getUserStoreCount

Returns the total number of UserStore proxies ever claimed.

function getUserStoreCount() external view override returns (uint256 count);

Returns

NameTypeDescription
countuint256Length 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

NameTypeDescription
offsetuint256Start index.
limituint256Maximum entries to return.

Returns

NameTypeDescription
storesaddress[]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. newImplementation must be non-zero, otherwise @custom:reverts InvalidImplementation. The candidate is sentinel-probed by calling IUserStore.getKeyCount on 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 to UpgradeableBeacon.upgradeTo and emits

  • emits: UserStoreImplementationUpgraded on success.

function upgradeUserStoreImplementation(address newImplementation) external override onlyOwner;

Parameters

NameTypeDescription
newImplementationaddressThe new implementation address.

version

Returns implementation version.

function version() external pure virtual returns (string memory versionString);

Returns

NameTypeDescription
versionStringstringCurrent 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

NameTypeDescription
sourceaddress[]Storage array to slice.
offsetuint256Start index.
limituint256Maximum entries to return.

Returns

NameTypeDescription
sliceaddress[]Result slice; empty when offset >= source.length.

UserStore

Git Source

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

NameTypeDescription
user_addressThe 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

NameTypeDescription
keybytes32The key to write.
valuebytesThe 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

NameTypeDescription
owner_addressThe 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

NameTypeDescription
keybytes32The key to read.

Returns

NameTypeDescription
valuebytesThe 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

NameTypeDescription
keybytes32The key to check.

Returns

NameTypeDescription
presentboolTrue 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

NameTypeDescription
keybytes32The key to read.

Returns

NameTypeDescription
countuint256Length 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

NameTypeDescription
keybytes32The key to read.
indexuint256Zero-based index into the history list.

Returns

NameTypeDescription
entryEntryThe (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

NameTypeDescription
keybytes32The key to read.
offsetuint256Start index.
limituint256Maximum entries to return.

Returns

NameTypeDescription
entriesEntry[]Slice of history entries.

getKeyCount

Returns the number of distinct keys ever written.

function getKeyCount() external view override returns (uint256 count);

Returns

NameTypeDescription
countuint256Length 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

NameTypeDescription
indexuint256Zero-based index into the key list.

Returns

NameTypeDescription
keybytes32The 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

NameTypeDescription
offsetuint256Start index.
limituint256Maximum entries to return.

Returns

NameTypeDescription
keysbytes32[]Slice of keys.

version

Returns implementation version.

function version() external pure virtual returns (string memory versionString);

Returns

NameTypeDescription
versionStringstringCurrent version string.

_onlyOwner

Internal owner check deferred from the onlyOwner modifier.

function _onlyOwner() internal view;

Contents

DotnsConstants

Git Source

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

Git Source

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

NameTypeDescription
labelstringLabel string.

Returns

NameTypeDescription
hashbytes32keccak256(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

NameTypeDescription
labelstringLabel string held in memory.

Returns

NameTypeDescription
hashbytes32keccak256(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

NameTypeDescription
parentbytes32Parent node.
labelhash_bytes32keccak256(bytes(label)).

Returns

NameTypeDescription
nodebytes32namehash(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

NameTypeDescription
tldNodebytes32TLD node to root the name under, read from the protocol registry.
labelstringLabel string.

Returns

NameTypeDescription
hashbytes32keccak256(bytes(label)).
nodebytes32namehash(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

NameTypeDescription
tldSuffixstringTLD suffix including the leading dot, read from the protocol registry.
fullNamestringStored full name to strip.

Multicall3

Git Source

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

NameTypeDescription
callsCall[]An array of Call structs.

Returns

NameTypeDescription
blockNumberuint256The block number where the calls were executed.
returnDatabytes[]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

NameTypeDescription
requireSuccessboolIf true, require all calls to succeed.
callsCall[]An array of Call structs.

Returns

NameTypeDescription
returnDataResult[]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

NameTypeDescription
requireSuccessboolIf true, require all calls to succeed.
callsCall[]An array of Call structs.

Returns

NameTypeDescription
blockNumberuint256The block number where the calls were executed.
blockHashbytes32The hash of the block where the calls were executed.
returnDataResult[]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

NameTypeDescription
callsCall[]An array of Call structs.

Returns

NameTypeDescription
blockNumberuint256The block number where the calls were executed.
blockHashbytes32The hash of the block where the calls were executed.
returnDataResult[]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

NameTypeDescription
callsCall3[]An array of Call3 structs.

Returns

NameTypeDescription
returnDataResult[]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

NameTypeDescription
callsCall3Value[]An array of Call3Value structs.

Returns

NameTypeDescription
returnDataResult[]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

NameTypeDescription
blockNumberuint256The 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

Git Source

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:

  1. Mint the ERC721 name token on the base registrar.
  2. Write the forward registry entry (node => owner + default resolver).
  3. 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

NameTypeDescription
contextRegistrationContextRegistration inputs. See @custom:struct RegistrationContext.

Returns

NameTypeDescription
labelStoreaddressThe 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

NameTypeDescription
protocolRegistryIDotnsProtocolRegistryThe protocol-level address registry for sibling lookups.
useraddressAddress receiving the name.
labelstringHuman-readable label (without the TLD).
labelhashbytes32keccak256(bytes(label)).
nodebytes32namehash(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

Git Source

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

NameTypeDescription
factoryIStoreFactoryThe store factory.
useraddressThe user whose label store is being resolved.

Returns

NameTypeDescription
storeaddressThe 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

NameTypeDescription
factoryIStoreFactoryThe store factory.
useraddressThe label store owner.
labelhashbytes32The labelhash key.
labelstringThe label string (typically the full name, e.g. "alice.dot").

Returns

NameTypeDescription
storeaddressThe resolved or newly deployed store address.

StringUtils

Git Source

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

NameTypeDescription
valuestringThe UTF-8 encoded string to measure.

Returns

NameTypeDescription
lenuint256The 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

NameTypeDescription
valuestringCandidate label.

Returns

NameTypeDescription
isValidboolTrue 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

NameTypeDescription
valuestringCandidate label held in memory.

Returns

NameTypeDescription
isValidboolTrue 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

NameTypeDescription
valuestringCandidate dotted label.

Returns

NameTypeDescription
strippedstringLabel 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

NameTypeDescription
valuestringCandidate dotted lite label.

Returns

NameTypeDescription
isValidboolTrue 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

NameTypeDescription
valuestringCandidate label.

Returns

NameTypeDescription
isValidboolTrue 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

NameTypeDescription
valuestringCandidate label held in memory.

Returns

NameTypeDescription
isValidboolTrue 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

NameTypeDescription
valuestringCandidate name path.

Returns

NameTypeDescription
isValidboolTrue 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

NameTypeDescription
valueuint256The unsigned integer to convert.

Returns

NameTypeDescription
<none>stringThe 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

NameTypeDescription
accountaddressThe address to convert.

Returns

NameTypeDescription
<none>stringThe 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

NameTypeDescription
_bytes32bytes32The bytes32 value containing a null-terminated ASCII string.

Returns

NameTypeDescription
<none>stringThe extracted string (up to 32 characters).

SystemUtils

Git Source

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

NameTypeDescription
rootboolTrue when the transaction origin is Root.

Contents

DotnsNameWhitelist

Git Source

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

NameTypeDescription
registryIDotnsProtocolRegistryProtocol 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

NameTypeDescription
accountaddressAddress whose operator role is changed.
enabledboolTrue 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 newMax is zero or above the ceiling.

  • emits: MaxClaimantsSet.

function setMaxClaimants(uint16 newMax) external override onlyGovernance;

Parameters

NameTypeDescription
newMaxuint16New 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

NameTypeDescription
newMaxuint256New 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

NameTypeDescription
newMaxuint16New 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

NameTypeDescription
labelstringBare label to claim.
reasonstringFree-text justification, at most maxReasonBytes bytes.
useraddressBeneficiary 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

NameTypeDescription
labelstringBare label to resolve.
useraddressBeneficiary 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

NameTypeDescription
labelstringBare label.
useraddressBeneficiary 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

NameTypeDescription
labelstringBare label to grant.
useraddressBeneficiary 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

NameTypeDescription
labelsstring[]Bare labels to grant.
useraddressBeneficiary 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

NameTypeDescription
labelstringBare 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

NameTypeDescription
labelstring
reservedboolTrue 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 label is not won by registrant.

  • emits: NameConsumed.

function consume(string calldata label, address registrant) external override onlyController;

Parameters

NameTypeDescription
labelstringBare label being registered.
registrantaddressAddress 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

NameTypeDescription
startsInuint64Seconds from now until requests start being accepted.
durationuint64Seconds the window stays open.

statusOf

Returns the status of label.

function statusOf(string calldata label) external view override returns (NameStatus status);

Parameters

NameTypeDescription
labelstringBare label to look up.

Returns

NameTypeDescription
statusNameStatusName status; see NameStatus.

isReserved

Returns whether label is reserved.

function isReserved(string calldata label) external view override returns (bool reserved);

Parameters

NameTypeDescription
labelstringBare label to look up.

Returns

NameTypeDescription
reservedboolTrue 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

NameTypeDescription
labelstringBare label to look up.

Returns

NameTypeDescription
winneraddressWinning 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

NameTypeDescription
labelstringBare label to look up.
accountaddressAddress to test against the winner.

Returns

NameTypeDescription
grantedboolTrue 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

NameTypeDescription
labelstringBare label to look up.
useraddressBeneficiary to look up.

Returns

NameTypeDescription
claimClaimThe 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

NameTypeDescription
labelstringBare label to look up.

Returns

NameTypeDescription
countuint256Live 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

NameTypeDescription
labelstringBare label to look up.
offsetuint256Index of the first claim.
limituint256Maximum number of claims to return.

Returns

NameTypeDescription
pageClaim[]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

NameTypeDescription
countuint256Active 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

NameTypeDescription
offsetuint256Index of the first name.
limituint256Maximum number of names to return.

Returns

NameTypeDescription
pageNameView[]Names in the window.

window

Returns the request window.

function window() external view override returns (uint64 openAt, uint64 closeAt);

Returns

NameTypeDescription
openAtuint64Timestamp requests start being accepted.
closeAtuint64Timestamp requests stop being accepted.

isWindowOpen

Returns whether requests are currently accepted.

function isWindowOpen() external view override returns (bool open);

Returns

NameTypeDescription
openboolTrue 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

NameTypeDescription
labelstringBare label to grant.
useraddressBeneficiary 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

NameTypeDescription
nodebytes32Namehash of the label under the active TLD.
winneraddressBeneficiary the name binds to.
labelstringBare 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

NameTypeDescription
nodebytes32Namehash of the label under the active TLD.
winneraddressClaimant spared a rejection event; the zero address rejects every claimant.
labelstringBare 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

NameTypeDescription
nodebytes32Namehash of the label under the active TLD.
labelstringBare 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

NameTypeDescription
nodebytes32Namehash 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

NameTypeDescription
labelstringBare label to hash.

Returns

NameTypeDescription
nodebytes32Namehash 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

NameTypeDescription
openboolTrue 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

Git Source

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

NameTypeDescription
labelstringBare label to claim.
reasonstringFree-text justification, at most maxReasonBytes bytes.
useraddressBeneficiary 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

NameTypeDescription
labelstringBare label to resolve.
useraddressBeneficiary 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

NameTypeDescription
labelstringBare label.
useraddressBeneficiary 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

NameTypeDescription
labelstringBare label to grant.
useraddressBeneficiary 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

NameTypeDescription
labelsstring[]Bare labels to grant.
useraddressBeneficiary 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

NameTypeDescription
labelstringBare 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

NameTypeDescription
labelstring
reservedboolTrue 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 label is not won by registrant.

  • emits: NameConsumed.

function consume(string calldata label, address registrant) external;

Parameters

NameTypeDescription
labelstringBare label being registered.
registrantaddressAddress 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

NameTypeDescription
startsInuint64Seconds from now until requests start being accepted.
durationuint64Seconds 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

NameTypeDescription
accountaddressAddress whose operator role is changed.
enabledboolTrue 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 newMax is zero or above the ceiling.

  • emits: MaxClaimantsSet.

function setMaxClaimants(uint16 newMax) external;

Parameters

NameTypeDescription
newMaxuint16New 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

NameTypeDescription
newMaxuint256New 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

NameTypeDescription
newMaxuint16New batch cap.

maxClaimants

Returns the live-claim cap per name.

function maxClaimants() external view returns (uint16 cap);

Returns

NameTypeDescription
capuint16Current per-name claim cap.

maxReasonBytes

Returns the reason byte cap.

function maxReasonBytes() external view returns (uint256 cap);

Returns

NameTypeDescription
capuint256Current reason byte cap.

maxGrantBatch

Returns the cap on labels per grantNames call.

function maxGrantBatch() external view returns (uint16 cap);

Returns

NameTypeDescription
capuint16Current batch cap.

statusOf

Returns the status of label.

function statusOf(string calldata label) external view returns (NameStatus status);

Parameters

NameTypeDescription
labelstringBare label to look up.

Returns

NameTypeDescription
statusNameStatusName status; see NameStatus.

isReserved

Returns whether label is reserved.

function isReserved(string calldata label) external view returns (bool reserved);

Parameters

NameTypeDescription
labelstringBare label to look up.

Returns

NameTypeDescription
reservedboolTrue 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

NameTypeDescription
labelstringBare label to look up.

Returns

NameTypeDescription
winneraddressWinning 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

NameTypeDescription
labelstringBare label to look up.
accountaddressAddress to test against the winner.

Returns

NameTypeDescription
grantedboolTrue 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

NameTypeDescription
labelstringBare label to look up.
useraddressBeneficiary to look up.

Returns

NameTypeDescription
claimClaimThe 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

NameTypeDescription
labelstringBare label to look up.

Returns

NameTypeDescription
countuint256Live 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

NameTypeDescription
labelstringBare label to look up.
offsetuint256Index of the first claim.
limituint256Maximum number of claims to return.

Returns

NameTypeDescription
pageClaim[]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

NameTypeDescription
countuint256Active 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

NameTypeDescription
offsetuint256Index of the first name.
limituint256Maximum number of names to return.

Returns

NameTypeDescription
pageNameView[]Names in the window.

window

Returns the request window.

function window() external view returns (uint64 openAt, uint64 closeAt);

Returns

NameTypeDescription
openAtuint64Timestamp requests start being accepted.
closeAtuint64Timestamp requests stop being accepted.

isWindowOpen

Returns whether requests are currently accepted.

function isWindowOpen() external view returns (bool open);

Returns

NameTypeDescription
openboolTrue 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

NameTypeDescription
openAtuint64Timestamp requests start being accepted.
closeAtuint64Timestamp requests stop being accepted.

MaxClaimantsSet

Emitted when the live-claim cap is set.

event MaxClaimantsSet(uint16 maxClaimants);

Parameters

NameTypeDescription
maxClaimantsuint16New per-name claim cap.

MaxReasonBytesSet

Emitted when the reason byte cap is set.

event MaxReasonBytesSet(uint256 maxReasonBytes);

Parameters

NameTypeDescription
maxReasonBytesuint256New reason byte cap.

MaxGrantBatchSet

Emitted when the grant-batch cap is set.

event MaxGrantBatchSet(uint16 maxGrantBatch);

Parameters

NameTypeDescription
maxGrantBatchuint16New 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

NameTypeDescription
nodebytes32Namehash 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

NameTypeDescription
nodebytes32Namehash of the label under the active TLD.
useraddressBeneficiary already holding a claim.

TooManyClaimants

Thrown when a name already holds maxClaimants claims.

error TooManyClaimants(bytes32 node);

Parameters

NameTypeDescription
nodebytes32Namehash 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

NameTypeDescription
nodebytes32Namehash of the label under the active TLD.
useraddressBeneficiary whose claim was expected to be pending.

NotReserved

Thrown when releasing a name that is not reserved.

error NotReserved(bytes32 node);

Parameters

NameTypeDescription
nodebytes32Namehash 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

NameTypeDescription
nodebytes32Namehash 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

NameTypeDescription
calleraddressRejected caller.

NotWinner

Thrown when consume is called for a name not won by the registrant.

error NotWinner(address registrant, bytes32 node);

Parameters

NameTypeDescription
registrantaddressAddress attempting to register the name.
nodebytes32Namehash 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

NameTypeDescription
useraddressBeneficiary the name would bind to if this claim wins.
statusClaimStatusClaim status; see ClaimStatus.
requestedAtuint64Timestamp the claim was made.
submitteraddressAddress that filed the claim, which may differ from the beneficiary.
reasonstringFree-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

NameTypeDescription
nodebytes32Namehash of the label under the active TLD.
labelstringBare label.
statusNameStatusName status; see NameStatus.
winneraddressWinning 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

NameTypeDescription
statusNameStatusName status; see NameStatus.
winneraddressWinning beneficiary when Claimed, otherwise the zero address.
labelstringBare 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
}