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

Current network addresses and deployment notes are listed in DEPLOYMENTS.md.

Economics

dotNS uses a single tunable constant, written D throughout the protocol. D is the starting price used by PopRules and equals ten DOT at launch; governance can adjust it under the same gate as the upgrade authority. D is the only money quantity the protocol charges; everything else is a composition of D with zero.

D plays two distinct roles. As a deposit it is the refundable lock a NoStatus user posts to register a NoStatus-tier label; the deposit is bound to the name, not to the depositor, so it travels with the NFT on every transfer and only unlocks when the current holder releases the name back to escrow. Transferring a funded name forfeits the deposit to the new holder, who inherits the locked D and the right to release later. As a friction charge it is the non-refundable amount a sender pays on a cross-tier downward or reach-floor transfer. The two flows are economically distinct: the deposit gates a count of names (one D per NoStatus name in existence), the friction gates the rate of tier laundering.

Registration matrix

The public controller computes the registration charge as the greater of the owner-side price and the payer-to-owner downward friction; it does not add the two together. The single charge becomes a refundable deposit on a direct NoStatus registration and becomes non-refundable reserve funding on a cross-payer registration.

Owner tierReserved (stem ≤5)PopFull-tier (stem 6-8, no digits)PopLite-tier (stem 6-8, two digits)NoStatus-tier (stem ≥9)
NoStatus userrejectedrejectedrejecteddirect: pays D into deposit
PopLite userrejectedrejectedgateway-only; freefree
PopFull userrejectedfreegateway-only; freefree
Whitelisted addressfreefreefreefree

Cross-payer registrations pay the greater of the owner-side price and the transfer-floor amount into the reserve. Reserved labels remain forbidden on the cross-payer path because the owner-side gate still rejects them. Whitelist registrations go through the same commit-reveal pipeline as the public path.

Transfer matrix

The registrar consults PopRules for the transfer floor. A transfer pays D whenever the recipient's tier is strictly below the sender's, or whenever the recipient cannot reach the label's required tier. A stale PopFull-tier name landing with a PopLite holder, for example, can still owe friction even when the holder-to-holder move otherwise looks same-tier. Same-tier and upward transfers between holders of the label's own class are free of friction.

The deposit, when present, is bound to the name and rides with it on every transfer. The escrow position is rebound to the new holder rather than refunded; only releasing the name back to escrow ever unlocks the locked D. Transferring a funded name is therefore a real forfeiture: the sender hands the locked deposit over to the recipient along with the NFT.

Sender → RecipientFriction (to insurance)Deposit movement
NoStatus → NoStatus (same tier)0Travels with the name; position rebinds to recipient
NoStatus → PopLite or PopFull0Travels with the name; position rebinds to recipient
PopLite → NoStatusDAny inherited deposit travels with the name
PopLite → PopLite (same)0Any inherited deposit stays bound to the name
PopLite → PopFull (upward)0Any inherited deposit stays bound to the name
PopFull → NoStatusDAny inherited deposit travels with the name
PopFull → PopLite (downward)DAny inherited deposit stays bound to the name
PopFull → PopFull (same)0Any inherited deposit stays bound to the name

The friction is constant and additive across downward hops. Every step that crosses a tier boundary downward charges D independently, so routing a name through intermediary tiers never costs less than the equivalent direct transfer. Laundering pays at least as much as the route it tries to avoid. Because the deposit follows the NFT, a NoStatus user cannot recover their D by handing the name to a fresh address and registering again; the only path back to D is releasing the current name into escrow. This binds Sybil cost to one D per live NoStatus name in existence, independent of how often names change hands.

Refund and cooldown model

The escrow maintains two separate pull-payment ledgers. The split is deliberate: one ledger is for immediate overpayment withdrawals, and the other is for refunds that must wait behind a cooldown.

  • Overpayment ledger. No cooldown. Used only as the fallback when a direct registration overpayment cannot be returned to the sender inline.
  • Refund ledger. Every refund has its own cooldown clock. Used for the deposit unlocked when a holder releases a funded name back to escrow, and for transfer-fee overpayments. Transfers never credit the refund ledger because the position rides with the name; only release-and-withdraw does.

Only registrations try to return surplus immediately. Every other refund path waits behind its own cooldown. The cooldown is bounded to minutes: it is the window between release and reclaim during which the original payer has an uncontested chance to pull their refund before the controller hands the name out again, not a long-lived lock. Governance can tune it within that band.

Clients can enumerate pending refunds through the escrow's public refund views. Pagination is capped so refund discovery remains bounded.

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 — verified-payer-for-unverified-owner sponsorship is rejected with OwnerStatusInsufficient, so 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 from the address resolved through the protocol registry under the POP_GATEWAY key, which is the RootGatewayDispatcher deployed against this controller; the dispatcher is the contract that actually proves substrate Root authority before forwarding here.

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 the user calls claimLabelStore once from their own address to settle the store. The pending-claim entries have a bounded TTL (expirePendingClaim is permissionless) so the slot frees itself if a user never claims. 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 held by a user who has not yet called claimLabelStore has no readable label on the sender side, so _quoteTransferFee returns zero regardless of the recipient's tier. Until the holder settles their 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 claimLabelStore as a prerequisite for accurate transfer-time pricing, not just for label discovery.

RootGatewayDispatcher

Non-upgradeable shim that translates a substrate Root-origin dispatch into an EVM-observable authority on the PoP controller. The dispatcher is the direct callee of the Root runtime origin, asks the revive System precompile whether its caller is Root, and forwards the calldata to the controller via a regular message call only when that check passes. The forwarded call lands on the controller proxy with the dispatcher as the immediate caller, which the controller authorises against the address registered on the protocol registry under POP_GATEWAY.

Hosting the Root check in a separate, non-proxy contract is what makes it work at all. The revive System precompile is only meaningful in the frame that is the direct callee of Root, and a UUPS implementation runs inside the proxy's delegatecall, so the controller cannot ask the precompile from its own frame. The dispatcher's target is immutable, set at construction to the controller proxy it serves, and the dispatcher holds no storage of its own and never delegatecalls, so it cannot be repurposed as an arbitrary-target proxy. Rotating the dispatcher is a single set call on the protocol registry; the controller picks up the new gateway on its next call without an upgrade.

The dispatcher exists to work around a runtime limitation: the substrate Root origin is not propagated through delegatecalls, so a UUPS implementation running inside its proxy's delegatecall frame cannot observe Root authority directly. Routing gateway calls through the non-proxy dispatcher restores a frame in which the Root check is meaningful. When the runtime propagates origin through delegatecalls, the controller can verify Root from its own frame and the dispatcher becomes unnecessary. This is a runtime limitation, not a protocol design choice.

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 for a flat deposit, 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. 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.

LabelStemTrailing digitsClassificationEligible public pathNotes
alice12alice2ReservedWhitelist onlyThe stem is five characters, so the two-digit suffix does not make it PopLite.
andrew01andrew2PopLitePop gateway onlyValid lite shape: six-character stem plus system-supplied two-digit suffix.
alicebob42alicebob2PopLitePop gateway onlyEight-character stem plus two digits; total length is ten.
andrewandrew0PopFullPopFull userCanonical full-person base name.
andrew1andrew1RejectedNoneOne trailing digit has no protocol meaning.
andrewsaysandrewsays0NoStatusAnyoneNoStatus self-registration pays the flat refundable deposit.
andrewsays01andrewsays2NoStatusAnyoneLong stem remains NoStatus even with a two-digit suffix.
andrew123andrew3RejectedNoneMore than two trailing digits is invalid.
andrew.01n/an/aRejected by public label validatorNoneDots 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 validatorNoneLabels 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, MULTICALL3, and POP_GATEWAY.

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

Current network addresses are listed in DEPLOYMENTS.md.

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 .dot names and manages the release/reclaim lifecycle.

Note: 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-reclaim window, not the long-lived deposit lock, 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

State Variables

protocolRegistry

The protocol registry for resolving sibling contract addresses.

IDotnsProtocolRegistry public protocolRegistry

cooldown

Cooldown period after release during which refunds can be claimed but not yet reclaimed.

Forces a delay between release and reclaim so the original payer has an uncontested window to pull their refund before the controller hands the name out again.

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

insuranceFund

Cumulative balance of cross-tier fees held against unreleased shortfalls.

Credited by cross-tier registration deposits, reach-floor friction, and transfer-fee deltas; debited only when withdraw needs to top up a refund that exceeds the asset's reserved balance.

uint256 public insuranceFund

_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

__gap

Reserved storage space to allow for layout changes in the future.

uint256[50] 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.

function initialize(
    IDotnsProtocolRegistry registry,
    uint256 cooldownSeconds
)
    external
    initializer;

Parameters

NameTypeDescription
registryIDotnsProtocolRegistryProtocol registry used to resolve registrar and controller addresses.
cooldownSecondsuint256Refund cooldown after release.

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;

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.

depositInsurance

Records a cross-tier registration fee into the insurance fund.

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 depositInsurance(InsuranceDepositParams calldata params)
    external
    payable
    override
    onlyController;

chargeTransferFee

Charges transfer friction 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 insurance, 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 insurance.

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.

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. Draws from the per-asset tokenReserved pool first and falls back to the shared insurance fund on shortfall; if even the combined balance is short, @custom:reverts InsufficientFunds. Funds are not transferred here, only credited to the pull-payment ledger. Emits

Note: emits: RefundWithdrawn once the credit lands, and @custom:emits InsuranceDraw whenever the insurance fund tops up a shortfall.

function withdraw(uint256 tokenId) external override nonReentrant;

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-and-claimed token from escrow custody 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 both released and claimed, otherwise @custom:reverts NotReclaimable. Emits

Note: emits: NameReclaimed once custody is transferred.

function reclaim(
    uint256 tokenId,
    address newOwner
)
    external
    override
    onlyController
    nonReentrant;

Parameters

NameTypeDescription
tokenIduint256
newOwneraddressAddress of the new registrant taking over the name.

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 .dot registrations 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;

depositInsurance

Records a cross-tier registration fee into the insurance fund.

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 depositInsurance(InsuranceDepositParams 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 transfer friction 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 insurance, 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 insurance.

insuranceFund

Returns the cumulative cross-tier fee balance held against future shortfalls.

function insuranceFund() external view returns (uint256 balance);

Returns

NameTypeDescription
balanceuint256Current insurance fund 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.

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. Draws from the per-asset tokenReserved pool first and falls back to the shared insurance fund on shortfall; if even the combined balance is short, @custom:reverts InsufficientFunds. Funds are not transferred here, only credited to the pull-payment ledger. Emits

Note: emits: RefundWithdrawn once the credit lands, and @custom:emits InsuranceDraw whenever the insurance fund tops up a shortfall.

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-and-claimed token from escrow custody 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 both released and claimed, otherwise @custom:reverts NotReclaimable. Emits

Note: emits: NameReclaimed once custody is transferred.

function reclaim(uint256 tokenId, address newOwner) external;

Parameters

NameTypeDescription
tokenIduint256
newOwneraddressAddress of the new registrant taking over the name.

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;

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

Parameters

NameTypeDescription
tokenIduint256
recipientaddressRefund recipient snapshotted at release time.
assetaddressDeposit asset. address(0) denotes native token.
amountuint256
withdrawAvailableAtuint256Earliest withdrawal timestamp.

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

CrossTierFeePaid

Emitted when a cross-tier fee is paid into the insurance fund.

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 depositInsurance; false from chargeTransferFee.

InsuranceDraw

Emitted when a withdrawal draws from the insurance fund to cover a shortfall in tokenReserved.

event InsuranceDraw(uint256 indexed tokenId, uint256 amount);

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 neither tokenReserved nor the insurance fund can cover the refund.

error InsufficientFunds(uint256 tokenId, uint256 owed, uint256 available);

Parameters

NameTypeDescription
tokenIduint256
oweduint256
availableuint256Combined balance available across reserves and insurance.

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.

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 (released + claimed).

error NotReclaimable(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.

InsuranceDepositParams

Parameters for recording a cross-tier registration fee into the insurance fund.

Funds the shared insurance pool used by withdraw to top up refunds whose per-asset reserve is short; payer is preserved purely for event accounting since the deposit itself is non-refundable.

struct InsuranceDepositParams {
    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 flat reach floor returned by @custom:function PopRules.transferFloor, settled to the insurance fund. 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 reachFloor;
    address payer;
    address to;
}

Properties

NameTypeDescription
tokenIduint256
reachFlooruint256Required fee paid by the sender 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;
}

Properties

NameTypeDescription
recipientaddress
assetaddressDeposit asset. address(0) denotes native token.
amountuint256
withdrawAvailableAtuint64Earliest timestamp at which withdrawal is permitted.
releasedbool
claimedbool

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

Contents

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. Pricing is a flat per-name deposit on the NoStatus tier; verified PopLite and PopFull users pay zero.

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.

updateStartingPrice

Updates the spam-deterrent starting price for NoStatus pricing.

Owner-only; unauthorised callers trigger @custom:reverts OwnableUnauthorizedAccount. newStartingPrice must be strictly positive, otherwise

Note: reverts: PopError. The new value flows into _priceValidatedName on the next pricing read; no redeploy. Emits @custom:emits StartingPriceUpdated with the prior and new values.

function updateStartingPrice(uint256 newStartingPrice) external;

Parameters

NameTypeDescription
newStartingPriceuint256New base price in wei.

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 a spam deterrent and is significant only for NoStatus users; verified users pay zero. 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.

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.

reachFee

Friction fee owed when account reaches into a label tier above its verification level.

Non-zero only when account cannot meet the label's required PoP tier; the value is the flat NoStatus deposit. Acts as cross-payer friction at registration time. Use

Note: function: transferFloor for transfer-time friction, which folds in the sender-tier-downgrade component as well. Non-canonical labels and labels with exactly one or more than two trailing digits trigger @custom:reverts PopError.

function reachFee(string calldata name, address account) external view returns (uint256 fee);

Parameters

NameTypeDescription
namestringDomain label being acted on.
accountaddressAccount whose verification reach is being measured.

transferFloor

Transfer-time friction floor: the greater of the recipient-reach component and the sender-tier-downgrade component.

Returns the flat NoStatus deposit 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. Returns zero when neither condition holds. 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.

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.

Returns zero for any label shorter than 9 characters; lengths >= 9 pay the flat startingPrice deposit. Ignores the caller's personhood status and reservation state. Non-canonical labels 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.

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.

StartingPriceUpdated

Emitted when the spam-deterrent NoStatus starting price is rotated.

Owner-only setter @custom:function updateStartingPrice; the new value is consumed by _priceValidatedName on the next pricing read.

event StartingPriceUpdated(uint256 oldPrice, uint256 newPrice);

Parameters

NameTypeDescription
oldPriceuint256Previous wei value.
newPriceuint256New wei value.

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();

Structs

PriceWithMeta

Bundle returned from metadata-aware pricing queries.

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

Properties

NameTypeDescription
priceuint256Registration cost; typically non-zero only for NoStatus users.
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, flat NoStatus pricing, and base-name reservations.

Tier shape: 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. NoStatus users pay a single flat deposit (startingPrice) per name; verified users pay zero on registration.

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

startingPrice

Wei price for names with 9 characters and up.

uint256 public startingPrice

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

__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. Seeds startingPrice through

Note: function: updateStartingPrice.

function initialize(
    uint256 _startingPrice,
    IDotnsProtocolRegistry registry
)
    public
    initializer;

Parameters

NameTypeDescription
_startingPriceuint256Base price in wei for NoStatus users.
registryIDotnsProtocolRegistryProtocol-level address registry used to resolve sibling contracts.

updateStartingPrice

Updates the spam-deterrent starting price for NoStatus pricing.

Owner-only; unauthorised callers trigger @custom:reverts OwnableUnauthorizedAccount. newStartingPrice must be strictly positive, otherwise

Note: reverts: PopError. The new value flows into _priceValidatedName on the next pricing read; no redeploy. Emits @custom:emits StartingPriceUpdated with the prior and new values.

function updateStartingPrice(uint256 newStartingPrice) public override onlyOwner;

Parameters

NameTypeDescription
newStartingPriceuint256New base price in wei.

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 a spam deterrent and is significant only for NoStatus users; verified users pay zero. 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.

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.

price

Calculates registration cost for a label.

Returns zero for any label shorter than 9 characters; lengths >= 9 pay the flat startingPrice deposit. Ignores the caller's personhood status and reservation state. Non-canonical labels 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.

reachFee

Friction fee owed when account reaches into a label tier above its verification level.

Non-zero only when account cannot meet the label's required PoP tier; the value is the flat NoStatus deposit. Acts as cross-payer friction at registration time. Use

Note: function: transferFloor for transfer-time friction, which folds in the sender-tier-downgrade component as well. Non-canonical labels and labels with exactly one or more than two trailing digits trigger @custom:reverts PopError.

function reachFee(
    string calldata name,
    address account
)
    external
    view
    override
    returns (uint256 fee);

Parameters

NameTypeDescription
namestringDomain label being acted on.
accountaddressAccount whose verification reach is being measured.

transferFloor

Transfer-time friction floor: the greater of the recipient-reach component and the sender-tier-downgrade component.

Returns the flat NoStatus deposit 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. Returns zero when neither condition holds. 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.

_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 reachFee and priceWithCheck build on this so the tier-eligibility rule lives in exactly one place and the two 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

function _priceValidatedName(uint256 namelength) internal view returns (uint256 priceValue);

_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);

_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

SELECTOR_RESERVE_LITE

Selector for the typed @custom:function reserveLiteName overload.

Hard-coded to disambiguate from the (bytes) overload at compile time. Must stay in sync with the @custom:struct LiteRegistration field layout.

bytes4 private constant SELECTOR_RESERVE_LITE =
    bytes4(keccak256("reserveLiteName((string,address,bytes))"))

SELECTOR_RESERVE_BASE

Selector for the typed @custom:function reserveBaseName overload.

BaseReservation is (LiteRegistration, string) and LiteRegistration is (string,address,bytes), hence the nested tuple in the canonical signature.

bytes4 private constant SELECTOR_RESERVE_BASE =
    bytes4(keccak256("reserveBaseName(((string,address,bytes),string))"))

SELECTOR_RESERVE_BASE_ONLY

Selector for the typed reservation-only gateway primitive.

bytes4 private constant SELECTOR_RESERVE_BASE_ONLY =
    bytes4(keccak256("reserveBaseNameOnly((address,string))"))

SELECTOR_REGISTER_BASE

Selector for the typed @custom:function registerBaseName overload.

Link is (uint8,string,bytes) because LinkKind is an enum.

bytes4 private constant SELECTOR_REGISTER_BASE =
    bytes4(keccak256("registerBaseName((string,address,(uint8,string,bytes)))"))

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

Notes:

  • function: claimLabelStore deploys the store and settles the whole pile, or

  • function: expirePendingClaim sweeps the lapsed entries. Each entry's expiry is measured from its own mintedAt.

mapping(address user => PendingClaim[] queue) internal _pendingClaimQueue

__gap

Reserved storage space to allow for layout changes in future upgrades.

uint256[50] private __gap

Functions

onlyGateway

Restricts calls to the address registered as the PoP gateway on the protocol registry.

Authority is delegated wholly to the registered gateway, which is the Root gateway dispatcher. Any caller other than the registered gateway is rejected with NotGateway. The Root-authority check itself lives in the dispatcher because the revive System precompile is only meaningful in the frame that is the direct callee of Root, which is the dispatcher and never this UUPS implementation.

modifier onlyGateway() ;

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 via the registered PoP gateway (otherwise @custom:reverts NotGateway); the gateway is responsible for asserting substrate Root authority before forwarding here. 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 @custom:emits LiteNameReserved and @custom:emits NameRegistered. On a cold-path mint emits @custom:emits LiteNameReserved and @custom:emits PendingClaimStashed, with

  • emits: NameRegistered deferred to @custom:function claimLabelStore when the user 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 override onlyGateway;

Parameters

NameTypeDescription
paramsLiteRegistrationRegistration request; see @custom:struct LiteRegistration.

reserveLiteName

Raw-payload variant of @custom:function reserveLiteName for cross-chain dispatch.

payload is abi.encode(LiteRegistration({...})), the bare ABI-encoded struct with NO function-selector prefix and NO leading bytes-length word. The contract prepends the typed selector and delegatecalls itself so the typed entrypoint runs in the original call context and remains the single source of truth, so the typed overload's revert surface bubbles up byte-for-byte: gateway-only access (otherwise

Note: reverts: NotGateway) and lite-label shape (otherwise

function reserveLiteName(bytes calldata payload) external override onlyGateway;

Parameters

NameTypeDescription
payloadbytesabi.encode(LiteRegistration) produced by the cross-chain caller.

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 via the registered PoP gateway (otherwise @custom:reverts NotGateway); the gateway is responsible for asserting substrate Root authority before forwarding here. 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 emits @custom:emits LiteNameReserved and @custom:emits NameRegistered; on a cold-path mint it emits @custom:emits LiteNameReserved and

Notes:

  • emits: PendingClaimStashed, with @custom:emits NameRegistered deferred to

  • function: claimLabelStore when the user 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) before any queue mutation so a bad reservation never touches the queue, advances the head past expired entries (emitting @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 (emitting

function reserveBaseName(BaseReservation calldata params) external override onlyGateway;

Parameters

NameTypeDescription
paramsBaseReservationReservation request; see @custom:struct BaseReservation.

reserveBaseName

Raw-payload variant of @custom:function reserveBaseName for cross-chain dispatch.

payload is abi.encode(BaseReservation({...})), the bare ABI-encoded struct with NO function-selector prefix and NO leading bytes-length word. The contract prepends the typed selector and delegatecalls itself so the typed entrypoint runs in the original call context and remains the single source of truth, which means the typed overload's full revert surface bubbles up byte-for-byte: gateway-only access (otherwise @custom:reverts NotGateway), lite-label shape (otherwise

Note: reverts: InvalidLiteLabel), base-label shape (otherwise

function reserveBaseName(bytes calldata payload) external override onlyGateway;

Parameters

NameTypeDescription
payloadbytesabi.encode(BaseReservation) produced by the cross-chain caller.

reserveBaseNameOnly

Enqueues only the full/base-name reservation for a user.

Callable only via the registered PoP gateway. 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. The caller remains agnostic about backend batching; it simply exposes a small retryable primitive.

function reserveBaseNameOnly(BaseNameReservation calldata params)
    external
    override
    onlyGateway;

Parameters

NameTypeDescription
paramsBaseNameReservationReservation request; see @custom:struct BaseNameReservation.

reserveBaseNameOnly

Raw-payload variant of @custom:function reserveBaseNameOnly for cross-chain dispatch. @param payload abi.encode(BaseNameReservation) produced by the cross-chain caller.

function reserveBaseNameOnly(bytes calldata payload) external override onlyGateway;

_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

Raw-payload variant of @custom:function registerBaseName for cross-chain dispatch.

payload is abi.encode(FullRegistration({...})), the bare ABI-encoded struct with NO function-selector prefix and NO leading bytes-length word. The contract prepends the typed selector and delegatecalls itself so the typed entrypoint runs in the original call context and remains the single source of truth, so the typed overload's revert surface bubbles up byte-for-byte: gateway-only access (otherwise

Note: reverts: NotGateway), base-label shape (otherwise @custom:reverts InvalidBaseLabel), lite-label shape on the LiteUsername branch (otherwise @custom:reverts InvalidLiteLabel), and the standalone-mint holder guard (otherwise @custom:reverts NotHolder). The success path emits the same events as the typed call: @custom:emits BaseNameClaimed on a claim or @custom:emits StandaloneNameRegistered otherwise, @custom:emits LiteToFullLinked on the LiteUsername branch, @custom:emits ReservationExpired for each entry reaped while advancing the queue head, and always @custom:emits NameRegistered. Note: abi.decode ignores trailing bytes past the encoded struct, so off-chain encoders MUST NOT assume strict length validation.

function registerBaseName(bytes calldata payload) external override onlyGateway;

Parameters

NameTypeDescription
payloadbytesabi.encode(FullRegistration) produced by the cross-chain caller.

registerBaseName

Registers a full-person username on behalf of the supplied user.

Callable only via the registered PoP gateway (otherwise @custom:reverts NotGateway); the gateway is responsible for asserting substrate Root authority before forwarding here. 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 emits @custom:emits BaseNameClaimed; a non-claim silently relinquishes any pending entry the user holds and emits @custom:emits StandaloneNameRegistered. Advancing the queue head past expired entries emits @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). Emits @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 onlyGateway;

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 emits @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 emit @custom:emits ReservationExpired for any stale entries reaped behind it.

function relinquishReservation() external override;

claimLabelStore

Settles the caller's deferred bindings by writing every stashed label into the caller's LabelStore, deploying the store first if the caller doesn't yet have one.

User-signed entrypoint: pallet-revive charges any LabelStore storage deposit against msg.sender's balance through the runtime's configured deposit backend. This is the only path that can create the store, because the Root gateway origin cannot instantiate contracts. Reverts with @custom:reverts NoPendingClaim when the caller holds no live stashed entries. Reuses any existing LabelStore returned by the factory (settling via this controller after a concurrent public-flow mint, or settling twice through this controller, both find a live store and skip deployment), otherwise deploys a fresh store via the protocol-registered factory. Writes each live label keyed by its node (namehash), clears the pending-claim entries, and emits

Note: emits: PendingClaimSettled and @custom:emits NameRegistered per settled name. Chat-key and lite-link records are not touched here; they are persisted on the PoP resolver at mint time, not at settlement.

function claimLabelStore() external override;

claimLabelStoreFor

Gateway-driven variant of @custom:function claimLabelStore for split workflows.

Callable only via the registered PoP gateway. It settles the pending LabelStore claim for user without requiring a user transaction. The user-signed

Note: function: claimLabelStore remains as a permissioned-by-origin fallback if gateway dispatch fails.

function claimLabelStoreFor(address user) external override onlyGateway;

Parameters

NameTypeDescription
useraddressAccount whose pending claim should be settled.

_claimLabelStoreFor

function _claimLabelStoreFor(address user) internal;

_settlePendingLabel

Writes a single pending label into the user's store, deploying the store lazily.

Deferring the deploy to the first settled label means a pile of only-lapsed entries never leaves a fresh store behind with nothing written. 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);

expirePendingClaim

Permissionlessly reaps a user's deferred bindings that sat unsettled past reservationDuration.

Permissionless on purpose: anyone (typically a UI or a bot) can poke stale entries so the user's pile cannot grow without bound. Sweeps every expired entry, leaving any still-live ones in place; the user is removed from the enumeration set only when no entries remain. Reverts with @custom:reverts NoPendingClaim when the user holds no entries and with @custom:reverts PendingClaimNotExpired when none of the held entries have lapsed. Emits @custom:emits PendingClaimExpired per swept name.

function expirePendingClaim(address user) external override;

Parameters

NameTypeDescription
useraddressAddress whose pending claims are being swept.

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 user's pending-claim entries.

An empty array means the user has no pending claims. A user accumulates one entry per deferred name until a signed-origin @custom:function claimLabelStore settles them.

function pendingClaims(address user)
    external
    view
    override
    returns (PendingClaim[] memory claims_);

Parameters

NameTypeDescription
useraddressAccount whose pending claims are being read.

Returns

NameTypeDescription
claims_PendingClaim[]claims Per-user pending-claim entries; see @custom:struct PendingClaim.

pendingClaimUserCount

Returns the number of users with at least one live pending claim.

Exact live count, not an all-time tally: fully settled and fully expired 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.

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.

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 claimLabelStore when the user 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 the user-signed @custom:function claimLabelStore. 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 claimLabelStore settles 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;

_clearPendingClaim

Clears a user's entire pending pile and removes them from the enumeration set.

function _clearPendingClaim(address user) 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
    pure
    returns (bytes32 labelhash, bytes32 node);

_validateBaseLabel

Validates a base (full-person) DNS label and derives (labelhash, node).

function _validateBaseLabel(string calldata baseLabel)
    internal
    pure
    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;

_onlyGateway

Internal check enforcing PoP-gateway-only access.

Authorises a call when the caller matches the address registered as the PoP gateway on the protocol registry. The dispatcher registered there is responsible for proving substrate Root authority via the revive System precompile; this contract trusts that forwarded calls already carry that authority. Reverts with NotGateway on failure, including when the registry key is unset.

function _onlyGateway() internal view;

_dispatchTyped

Routes a raw cross-chain payload to the typed entrypoint identified by selector.

Prepends selector to payload and delegatecalls address(this) so the typed overload runs in the original call context, making the typed path the single source of truth. The bytes payload from the cross-chain caller is already abi.encode(StructTuple), so concatenating selector with payload is exactly the calldata the typed overload expects. Reverts bubble up byte-for-byte so the caller sees the same error it would have seen on a direct typed call. The delegatecall target is hard-coded to address(this) and selector is one of three module-private constants pointing at this contract's own typed entrypoints, so storage context is preserved and no external code can run in this contract's frame. @custom:function _onlyGateway runs on both the outer bytes overload and the inner typed overload; both checks read the same registry slot.

Note: oz-upgrades-unsafe-allow: delegatecall

function _dispatchTyped(bytes4 selector, bytes calldata payload) private;

_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;

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) AND when the current owner is the configured escrow (the controller must then route through

Note: function: IDotnsNameEscrow.reclaim instead of @custom: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.

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 reach floor from @custom:function PopRules.transferFloor: the maximum of (i) the flat 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 reach floor 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 reach floor 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 reach floor; 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 reach floor returned by @custom:function PopRules.transferFloor. It is paid by the sender on every downward or cross-reach transfer and settles to the insurance fund. 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 reachFloor, 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 reachFloor, 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 .dot 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(DOT_NODE, 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

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[50] 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, and reserved fields is unambiguous, binding the commitment to the exact tuple.

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. Emits @custom:emits NameCommitted on success.

function commit(bytes32 commitment) external override;

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 insurance fund via depositInsurance 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
    pure
    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 via the registered PoP gateway (otherwise @custom:reverts NotGateway); the gateway is responsible for asserting substrate Root authority before forwarding here. 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 emits @custom:emits LiteNameReserved and @custom:emits NameRegistered; on a cold-path mint it emits @custom:emits LiteNameReserved and

Notes:

  • emits: PendingClaimStashed, with @custom:emits NameRegistered deferred to

  • function: claimLabelStore when the user 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) before any queue mutation so a bad reservation never touches the queue, advances the head past expired entries (emitting @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 (emitting

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

reserveBaseName

Raw-payload variant of @custom:function reserveBaseName for cross-chain dispatch.

payload is abi.encode(BaseReservation({...})), the bare ABI-encoded struct with NO function-selector prefix and NO leading bytes-length word. The contract prepends the typed selector and delegatecalls itself so the typed entrypoint runs in the original call context and remains the single source of truth, which means the typed overload's full revert surface bubbles up byte-for-byte: gateway-only access (otherwise @custom:reverts NotGateway), lite-label shape (otherwise

Notes:

  • reverts: InvalidLiteLabel), base-label shape (otherwise

  • reverts: InvalidBaseLabel), duplicate-reservation guard (otherwise

  • reverts: AlreadyReserved), and queue capacity (otherwise

  • reverts: QueueFull). The success path likewise emits the same events as the typed call: @custom:emits LiteNameReserved and @custom:emits NameRegistered on the lite leg, plus @custom:emits ReservationQueued and any @custom:emits ReservationExpired observed while advancing the queue head when the base-name leg runs. Note: abi.decode ignores trailing bytes past the encoded struct, so off-chain encoders MUST NOT assume strict length validation.

function reserveBaseName(bytes calldata payload) external;

Parameters

NameTypeDescription
payloadbytesabi.encode(BaseReservation) produced by the cross-chain caller.

reserveBaseNameOnly

Enqueues only the full/base-name reservation for a user.

Callable only via the registered PoP gateway. 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. 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.

reserveBaseNameOnly

Raw-payload variant of @custom:function reserveBaseNameOnly for cross-chain dispatch. @param payload abi.encode(BaseNameReservation) produced by the cross-chain caller.

function reserveBaseNameOnly(bytes calldata payload) external;

reserveLiteName

Registers a lite-person username on behalf of the supplied user without touching the base-name reservation queue.

Callable only via the registered PoP gateway (otherwise @custom:reverts NotGateway); the gateway is responsible for asserting substrate Root authority before forwarding here. 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 @custom:emits LiteNameReserved and @custom:emits NameRegistered. On a cold-path mint emits @custom:emits LiteNameReserved and @custom:emits PendingClaimStashed, with

  • emits: NameRegistered deferred to @custom:function claimLabelStore when the user 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.

reserveLiteName

Raw-payload variant of @custom:function reserveLiteName for cross-chain dispatch.

payload is abi.encode(LiteRegistration({...})), the bare ABI-encoded struct with NO function-selector prefix and NO leading bytes-length word. The contract prepends the typed selector and delegatecalls itself so the typed entrypoint runs in the original call context and remains the single source of truth, so the typed overload's revert surface bubbles up byte-for-byte: gateway-only access (otherwise

Notes:

  • reverts: NotGateway) and lite-label shape (otherwise

  • reverts: InvalidLiteLabel). The success path emits the same events as the typed call: @custom:emits LiteNameReserved and @custom:emits NameRegistered. Note: abi.decode ignores trailing bytes past the encoded struct, so off-chain encoders MUST NOT assume strict length validation; pad-only junk past the tail is silently dropped (no state corruption; decoded values are unchanged). Worked example off-chain: bytes payload = abi.encode(LiteRegistration({liteLabel: "alice42", user: u, chatKey: k}));

function reserveLiteName(bytes calldata payload) external;

Parameters

NameTypeDescription
payloadbytesabi.encode(LiteRegistration) produced by the cross-chain caller.

registerBaseName

Registers a full-person username on behalf of the supplied user.

Callable only via the registered PoP gateway (otherwise @custom:reverts NotGateway); the gateway is responsible for asserting substrate Root authority before forwarding here. 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 emits @custom:emits BaseNameClaimed; a non-claim silently relinquishes any pending entry the user holds and emits @custom:emits StandaloneNameRegistered. Advancing the queue head past expired entries emits @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). Emits @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 claimLabelStore. 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.

registerBaseName

Raw-payload variant of @custom:function registerBaseName for cross-chain dispatch.

payload is abi.encode(FullRegistration({...})), the bare ABI-encoded struct with NO function-selector prefix and NO leading bytes-length word. The contract prepends the typed selector and delegatecalls itself so the typed entrypoint runs in the original call context and remains the single source of truth, so the typed overload's revert surface bubbles up byte-for-byte: gateway-only access (otherwise

Note: reverts: NotGateway), base-label shape (otherwise @custom:reverts InvalidBaseLabel), lite-label shape on the LiteUsername branch (otherwise @custom:reverts InvalidLiteLabel), and the standalone-mint holder guard (otherwise @custom:reverts NotHolder). The success path emits the same events as the typed call: @custom:emits BaseNameClaimed on a claim or @custom:emits StandaloneNameRegistered otherwise, @custom:emits LiteToFullLinked on the LiteUsername branch, @custom:emits ReservationExpired for each entry reaped while advancing the queue head, and always @custom:emits NameRegistered. Note: abi.decode ignores trailing bytes past the encoded struct, so off-chain encoders MUST NOT assume strict length validation.

function registerBaseName(bytes calldata payload) external;

Parameters

NameTypeDescription
payloadbytesabi.encode(FullRegistration) produced by the cross-chain caller.

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 emits @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 emit @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.

claimLabelStore

Settles the caller's deferred bindings by writing every stashed label into the caller's LabelStore, deploying the store first if the caller doesn't yet have one.

User-signed entrypoint: pallet-revive charges any LabelStore storage deposit against msg.sender's balance through the runtime's configured deposit backend. This is the only path that can create the store, because the Root gateway origin cannot instantiate contracts. Reverts with @custom:reverts NoPendingClaim when the caller holds no live stashed entries. Reuses any existing LabelStore returned by the factory (settling via this controller after a concurrent public-flow mint, or settling twice through this controller, both find a live store and skip deployment), otherwise deploys a fresh store via the protocol-registered factory. Writes each live label keyed by its node (namehash), clears the pending-claim entries, and emits

Note: emits: PendingClaimSettled and @custom:emits NameRegistered per settled name. Chat-key and lite-link records are not touched here; they are persisted on the PoP resolver at mint time, not at settlement.

function claimLabelStore() external;

claimLabelStoreFor

Gateway-driven variant of @custom:function claimLabelStore for split workflows.

Callable only via the registered PoP gateway. It settles the pending LabelStore claim for user without requiring a user transaction. The user-signed

Note: function: claimLabelStore remains as a permissioned-by-origin fallback if gateway dispatch fails.

function claimLabelStoreFor(address user) external;

Parameters

NameTypeDescription
useraddressAccount whose pending claim should be settled.

expirePendingClaim

Permissionlessly reaps a user's deferred bindings that sat unsettled past reservationDuration.

Permissionless on purpose: anyone (typically a UI or a bot) can poke stale entries so the user's pile cannot grow without bound. Sweeps every expired entry, leaving any still-live ones in place; the user is removed from the enumeration set only when no entries remain. Reverts with @custom:reverts NoPendingClaim when the user holds no entries and with @custom:reverts PendingClaimNotExpired when none of the held entries have lapsed. Emits @custom:emits PendingClaimExpired per swept name.

function expirePendingClaim(address user) external;

Parameters

NameTypeDescription
useraddressAddress whose pending claims are being swept.

pendingClaims

Returns user's pending-claim entries.

An empty array means the user has no pending claims. A user accumulates one entry per deferred name until a signed-origin @custom:function claimLabelStore settles them.

function pendingClaims(address user) external view returns (PendingClaim[] memory claims);

Parameters

NameTypeDescription
useraddressAccount whose pending claims are being read.

Returns

NameTypeDescription
claimsPendingClaim[]Per-user pending-claim entries; see @custom:struct PendingClaim.

pendingClaimUserCount

Returns the number of users with at least one live pending claim.

Exact live count, not an all-time tally: fully settled and fully expired 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.

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 user settles a deferred binding by deploying their LabelStore and backfilling the stashed label and chat key.

event PendingClaimSettled(address indexed user, bytes32 indexed labelhash, address store);

PendingClaimExpired

Emitted when a deferred binding is reaped because it sat unsettled past reservationDuration.

event PendingClaimExpired(address indexed user, bytes32 indexed labelhash);

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

NotGateway

Thrown when a gated entrypoint is reached from an address that is not the gateway registered on the protocol registry under the PoP gateway key.

The controller delegates substrate Root-authority verification to the registered gateway, which is the Root gateway dispatcher, and authorises calls solely against the address resolved from the protocol registry. The caller parameter carries the immediate EVM caller observed by this contract for off-chain diagnostics.

error NotGateway(address caller);

Parameters

NameTypeDescription
calleraddressImmediate EVM caller observed by this contract.

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();

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

NoPendingClaim

Thrown when @custom:function claimLabelStore is called by a user with no recorded pending-claim entries.

error NoPendingClaim(address user);

Parameters

NameTypeDescription
useraddressCaller observed by the controller.

PendingClaimNotExpired

Thrown when @custom:function expirePendingClaim is invoked but the user holds no entry past its mintedAt + reservationDuration deadline.

error PendingClaimNotExpired(address user);

Parameters

NameTypeDescription
useraddressAddress whose entries are being inspected.

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 user later settles the binding via @custom:function claimLabelStore, 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

Note: 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 claimLabelStore deploys the store and settles every entry at once. Each entry's expiry 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: claimLabelStoreFor or the user fallback @custom:function claimLabelStore.

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
}

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) AND when the current owner is the configured escrow (the controller must then route through

Note: function: IDotnsNameEscrow.reclaim instead of @custom: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.

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 reach floor from @custom:function PopRules.transferFloor: the maximum of (i) the flat 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 reach floor; 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 reach floor 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 reach floor 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 reach floor 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 .dot 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, and reserved fields is unambiguous, binding the commitment to the exact tuple.

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. 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 total charge on the cross-payer path is the greater of the owner-side registration price and the owner-tier transferFloor friction (never their sum); friction is computed against the owner's tier so a verified payer cannot pay around an unverified owner. The entire charge routes to the escrow insurance fund while seeding a zero-amount deposit slot so the release lifecycle stays reachable. 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.

NameReserved

Thrown when attempting to register a name whose base stem is held as a live reservation by another user.

error NameReserved(string label);

GovernanceReserved

Thrown when attempting to register 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 the rules engine 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 @custom:contract IPopRules priceWithCheck covers this same condition via its own revert.

error OwnerStatusInsufficient(
    string label, IPopRules.PopStatus userStatus, IPopRules.PopStatus required
);

Parameters

NameTypeDescription
labelstringLabel whose tier requirement was unmet.
userStatusIPopRules.PopStatusOwner's recorded tier.
requiredIPopRules.PopStatusRequired tier for the label.

InvalidLabel

Thrown when a label is not a canonical lowercase ASCII DNS label.

error InvalidLabel();

InsufficientValue

Thrown when supplied payment is insufficient.

error InsufficientValue();

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;
}

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

RootGatewayDispatcher

Git Source

Title: RootGatewayDispatcher

Non-upgradeable shim that translates a substrate Root-origin dispatch into an EVM-observable authority on the PoP controller proxy. The dispatcher is the direct callee of the Root runtime origin, asks the revive System precompile whether its caller is Root, and forwards the calldata to the controller via a regular message call when, and only when, that check passes.

The Root-authority check must live in the frame that is the direct callee of Root. A UUPS implementation runs inside the proxy's delegatecall, so the controller cannot ask the precompile from its own frame. The dispatcher hosts that check in a non-proxy contract and converts the result into the immediate-caller predicate the controller checks on the forwarded call. Lifecycle:

  • Deployed once per controller proxy with its target bound to that proxy address.
  • Registered on the protocol registry under the PoP gateway key; the controller resolves this key on every gated call.
  • The PoP gateway pallet sends Root-origin dispatches at the dispatcher's address rather than the controller's. Security:
  • The target is immutable, so a deployed dispatcher can only ever forward to the controller it was constructed against. Rotating the controller proxy means deploying a new dispatcher and pointing the gateway pallet at it.
  • The dispatcher holds no storage and never delegatecalls, so it cannot be used as an arbitrary-target proxy.
  • The fallback is non-payable: gated controller entrypoints are non-payable, and rejecting value transfers at the dispatcher boundary keeps the forwarded call shape identical to a direct controller call.

Note: security-contact: admin@parity.io

Constants

TARGET

Controller proxy address this dispatcher forwards to.

Set once at construction and never reassigned.

address public immutable TARGET

Functions

constructor

constructor(address target_) ;

Parameters

NameTypeDescription
target_addressAddress of the PoP controller proxy.

fallback

Verifies Root authority through the revive System precompile and forwards the raw calldata to the controller proxy via a regular message call.

The precompile check is evaluated in this contract's frame, which is the direct callee of Root, so the precompile resolves the origin walk successfully. The forwarded call lands on the controller proxy with this contract as the immediate caller, which the controller authorises against the gateway address registered on the protocol registry.

fallback() external;

Errors

NotRoot

Thrown when the immediate substrate origin is not Root.

The revive System precompile returns false rather than reverting on a non-Root origin, so the gate has to surface its own error.

error NotRoot();

Contents

DotnsProtocolRegistry

Git Source

Inherits: Initializable, UUPSUpgradeable, OwnableUpgradeable, IDotnsProtocolRegistry

Title: Dotns Protocol Registry

Author: Parity

Upgradeable address registry for all DotNS protocol contracts.

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.

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

__gap

uint256[50] private __gap

Functions

constructor

Note: oz-upgrades-unsafe-allow: constructor

constructor() ;

initialize

Initialises the protocol registry.

Callable exactly once via Initializable, otherwise

Note: reverts: InvalidInitialization. Sets the deployer as owner.

function initialize() external initializer;

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

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

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.

function _parentNamehash(string calldata parentLabel) internal pure 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.

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

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();

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

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

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

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 .dot) 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 namehash of the .dot TLD, the TLD suffix string, and 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.

Note: security-contact: admin@parity.io

Constants

DOT_NODE

Namehash of the .dot TLD node.

keccak256(abi.encodePacked(bytes32(0), keccak256("dot"))).

bytes32 internal constant DOT_NODE =
    0x3fce7d1364a893e213bc4212792b517ffc88f5b13b86c8ef9c8d390c3a1370ce

TLD

TLD suffix appended to labels when building full domain names.

string internal constant TLD = ".dot"

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 to authenticate Root-origin dispatches via ISystem.callerIsRoot().

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")

RENT_PRICE

Default deploy-time NoStatus rent price passed into PopRules.initialize as _startingPrice.

10 DOT under revive's 18-decimal Asset Hub convention. Single source of truth for deploy scripts and tests so the value cannot drift between call sites. Live deployments rotate the runtime startingPrice on PopRules rather than rebuilding consumers against a new constant.

uint256 internal constant RENT_PRICE = 10 ether

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")

REGISTRAR

Well-known key for the ERC721 registrar backing name ownership.

Role: token-of-record for .dot 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")

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")

POP_GATEWAY

Well-known key for the address authorised to invoke the PoP controller's gated entrypoints.

Role: substrate Root-origin shim. Resolves to the Root gateway dispatcher deployed against the PoP controller. The dispatcher verifies Root authority through the revive System precompile in its own frame and forwards calldata to the controller via a regular message call; the controller authorises against this registry key, so rotating the dispatcher is a single write on the protocol registry. forge-lint: disable-next-line(unsafe-typecast)

bytes32 internal constant POP_GATEWAY = bytes32("popGateway")

LabelUtils

Git Source

Title: DotNS Label Utilities Library

Canonical keccak labelhash and .dot-TLD 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.

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.

General form of @custom:function namehash; use when the parent is not the .dot TLD (e.g. subnode derivation under a non-TLD parent in the forward registry, or a PoP namespace root). For top-level .dot registrations prefer

Note: function: namehash which hard-codes DOT_NODE.

function namehashUnder(bytes32 parent, bytes32 labelhash_)
    internal
    pure
    returns (bytes32 node);

Parameters

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

Returns

NameTypeDescription
nodebytes32namehash(parent, labelhash).

namehash

Computes namehash(DOT_NODE, labelhash) via memory-safe scratch space.

Specialised to the .dot TLD; cheaper than @custom:function namehashUnder because the parent constant is folded in at compile time.

function namehash(bytes32 labelhash_) internal pure returns (bytes32 node);

Parameters

NameTypeDescription
labelhash_bytes32keccak256(bytes(label)).

Returns

NameTypeDescription
nodebytes32The node identifier under the .dot TLD.

deriveNode

Derives (labelhash, node) from a label in one call.

Convenience combinator for registration flows: hashes the label exactly once and returns both identifiers callers need without recomputing.

function deriveNode(string calldata label) internal pure returns (bytes32 hash, bytes32 node);

Parameters

NameTypeDescription
labelstringLabel string.

Returns

NameTypeDescription
hashbytes32keccak256(bytes(label)).
nodebytes32namehash(DOT_NODE, hash).

stripDotTld

Strips the configured .dot TLD suffix from a stored full name.

Returns the empty string when the input does not end in the TLD; callers treat an empty return as a "do not trust this record" signal.

function stripDotTld(string memory fullName) internal pure returns (string memory label);

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(DOT_NODE, 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).