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

Deployment and operations
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 tier | Reserved (stem ≤5) | PopFull-tier (stem 6-8, no digits) | PopLite-tier (stem 6-8, two digits) | NoStatus-tier (stem ≥9) |
|---|---|---|---|---|
| NoStatus user | rejected | rejected | rejected | direct: pays D into deposit |
| PopLite user | rejected | rejected | gateway-only; free | free |
| PopFull user | rejected | free | gateway-only; free | free |
| Whitelisted address | free | free | free | free |
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 → Recipient | Friction (to insurance) | Deposit movement |
|---|---|---|
| NoStatus → NoStatus (same tier) | 0 | Travels with the name; position rebinds to recipient |
| NoStatus → PopLite or PopFull | 0 | Travels with the name; position rebinds to recipient |
| PopLite → NoStatus | D | Any inherited deposit travels with the name |
| PopLite → PopLite (same) | 0 | Any inherited deposit stays bound to the name |
| PopLite → PopFull (upward) | 0 | Any inherited deposit stays bound to the name |
| PopFull → NoStatus | D | Any inherited deposit travels with the name |
| PopFull → PopLite (downward) | D | Any inherited deposit stays bound to the name |
| PopFull → PopFull (same) | 0 | Any 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.
| Label | Stem | Trailing digits | Classification | Eligible public path | Notes |
|---|---|---|---|---|---|
| alice12 | alice | 2 | Reserved | Whitelist only | The stem is five characters, so the two-digit suffix does not make it PopLite. |
| andrew01 | andrew | 2 | PopLite | Pop gateway only | Valid lite shape: six-character stem plus system-supplied two-digit suffix. |
| alicebob42 | alicebob | 2 | PopLite | Pop gateway only | Eight-character stem plus two digits; total length is ten. |
| andrew | andrew | 0 | PopFull | PopFull user | Canonical full-person base name. |
| andrew1 | andrew | 1 | Rejected | None | One trailing digit has no protocol meaning. |
| andrewsays | andrewsays | 0 | NoStatus | Anyone | NoStatus self-registration pays the flat refundable deposit. |
| andrewsays01 | andrewsays | 2 | NoStatus | Anyone | Long stem remains NoStatus even with a two-digit suffix. |
| andrew123 | andrew | 3 | Rejected | None | More than two trailing digits is invalid. |
| andrew.01 | n/a | n/a | Rejected by public label validator | None | Dots are not valid in the public flat label. The Pop gateway accepts stem.suffix and normalises it to stemsuffix. |
| Andrew01 | n/a | n/a | Rejected by canonical label validator | None | Labels must be lowercase ASCII DNS labels. |
Tier assignment is read on every pricing call, not stored: PopRules queries the alias-accounts personhood precompile at DotnsConstants.PERSONHOOD with the dotns context (bytes32("dotns")), and translates the returned status byte into a PopStatus (0=NoStatus, 1=PopLite, 2=PopFull). Unknown tier bytes collapse to NoStatus, so a future precompile addition fails closed rather than silently being treated as a higher tier. There is no on-chain self-attestation; users obtain personhood off-chain through the People-chain ring proof and the alias-accounts pallet propagates the result via XCM.
Classification is not the same thing as effective holder context. A long label such as andrewsays is always a NoStatus-tier label by shape, but it may be held by a PopFull, PopLite, or NoStatus account. Consumers that need to know what rules apply to that live name should combine three reads: classify the label, query the registrar owner, then query the owner's dotns-context PoP status through the precompile or gateway-written state. The escrow position is the economic qualifier: if the token has an active release position with a non-zero amount, it came through the refundable NoStatus deposit path; if no such deposit exists, a verified holder can own the same long label without it being deposit-backed. In other words, andrewsays does not become a PopFull-tier label when a PopFull user owns it, but the owner can still be PopFull for transfer pricing, reverse resolution, and UI display.
Whitelisting is the exception path for users or organisations that need to register without satisfying the live PoP tier check. DotNS still does not accept self-attestation: the contracts only consume PoP status from the personhood precompile, and a user cannot set or prove their own status inside DotNS. Instead, the public registrar controller has an owner-managed whitelist for registerReserved, which bypasses the PoP pricing gate for approved addresses while still using the normal commit-reveal and availability checks.
To request a whitelist entry, open a Whitelist Request issue in this repository. The issue is labelled whitelist-request by the template and must include the address, address type, target network (Paseo V2 or Paseo Review), and a clear description of why the PoP bypass is needed. A maintainer can approve the request by applying whitelist-approved, after which the workflow checks account mapping on the selected network and executes the on-chain whitelist transaction. Whitelisting does not register a name, reserve a label, or bypass ownership rules; it only allows the approved address to use the reserved registration path without a PoP status.
For the operator-side mechanics, granting the whitelist-operator role, granting it to many operators, and whitelisting one or many addresses through the dotNS SDK CLI, the hosted app, or cast, see the Whitelisting section in DEPLOYMENTS.md.
PopRules also holds the cross-flow reservation table for base names. Two write paths share one mapping keyed by the bare stem. The first is used by the commit-reveal controller during a lite registration: it classifies the incoming label, strips the trailing digits, and writes the bare stem. The second is used by the PoP controller on every reservation-queue head transition: it takes a bare stem directly and rejects the update when the slot is held by a different user, so the caller's local queue bookkeeping never silently diverges from the PopRules state.
Two read paths, priceWithCheck and priceWithoutCheck, are what the public flow consults. Both strip trailing digits before looking up the reservation, so any live entry on a bare stem blocks registrations of any variant under that stem for the reservation window (12 weeks by default).
DotnsReverseResolver
Reverse records mapping an address to its primary name, with two write paths. The first, setReverseName, is the controller-only seeder: when a direct reserved registration lands and the registrant has no existing primary, the commit-reveal controller calls this entry point so a subsequent reserved registration does not silently overwrite the primary. Writes through this path are restricted to the addresses registered under CONTROLLER and REGISTRAR on the protocol registry. The second, claimReverseRecord, is the self-service path open to any current name owner: the caller hands in a label and the resolver checks that registrar.ownerOf(namehash(label)) equals the caller before overwriting their reverse entry. Past ownership is never sufficient; the registrar is the single source of truth for the gate.
Reads are open but fail-closed: nameOf(address) re-validates current ownership against the registrar before returning the stored name, so an address that has transferred their primary away resolves to the empty string until they claim a new name they currently hold. The protocol still best-effort clears reverse entries on transfer (cheaper reads, no behavioural change), but the security guarantee is the fail-closed read, not the eager clear.
DotnsContentResolver
Stores contenthash and text records per node. This is where external content links (for example IPFS hashes) and arbitrary key-value text records (for example social handles, verification metadata) live. Writes accept the node owner, any address the registry recognises as authorised for the node through isAuthorised (the ERC-721 holder, a single-token approvee, or an operator-for-all on the registrar), or an operator approved directly on this resolver; reads are open. The registry-recognised path lets a registrar-level name admin manage records without a separate grant, while the resolver-local operator is a narrower record-only delegation that confers no power over ownership or transfer. Authority is evaluated against the current owner on every write, so transferring the name reassigns write access automatically.
Choosing a delegation mechanism:
| Goal | Use | Why |
|---|---|---|
| Delegate full control of one name, including the right to transfer it, automatically revoked on sale | registrar approve(operator, tokenId) | Single-token approval. ERC-721 clears it on every transfer, so it cannot follow the name to a buyer. |
| Delegate full control of all your names, current and future, including transferring them | registrar setApprovalForAll(operator, true) | The name-admin role. Persists until revoked and spans every name you hold. It grants transfer power, so grant it only to fully trusted managers; this is the approval marketplaces and escrows require. |
| Delegate record edits only, with no power over ownership or transfer | content resolver setApprovalForAll(operator, true) | The narrowest grant. Resolver-local and record-scoped: the operator can set text and contenthash but cannot transfer the name or change its owner. |
Revoke any grant with the inverse call (approve(address(0), tokenId) or setApprovalForAll(operator, false)). Because every write re-reads the current owner, a transfer drops all delegates the prior owner had set.
DotnsResolver
Stores forward-resolution address records per node. This is the conventional "name to address" lookup: a client has a .dot name and wants to know the Ethereum address behind it. Writes require node ownership; reads are open.
DotnsPopResolver
Per-node resolver for records produced by the Proof-of-Personhood flow. Three record kinds. The chat key is ECDH public-key bytes keyed by node; it is written by the PoP controller during a lite reservation and during any claim path that inherits from a prior lite entry, and is what gives verified users an on-chain discovery channel for end-to-end encrypted messaging. The lite link answers "which lite username did this full name claim from?" and is keyed by the full-person node. The full claim is the reverse direction: it answers "which full name did this lite user claim?" and is keyed by the lite labelhash. The forward and reverse links are written by the same call, so they stay in lockstep; downstream consumers that look up by lite username (Nova's pallet, for one) resolve the full name without scanning events.
Writer authorisation is dynamic: the PoP controller address is fetched from the protocol registry on every write. Rotating the PoP controller is a single set call on the protocol registry with no resolver upgrade required.
DotnsProtocolRegistry
On-chain lookup table mapping well-known bytes32 keys (declared in DotnsConstants) to contract addresses. Every DotNS contract resolves its siblings through this registry at runtime.
Without it, each contract would store direct addresses to every contract it calls. An upgrade that changes one address would require a separate owner transaction for every contract that references it. The protocol registry reduces this to one: update the key in the registry, and every caller picks up the new address on its next call. The indirection also means a governance-driven rotation of, say, the PoP controller does not break any consumer that has already been deployed.
The registered keys include REGISTRAR, CONTROLLER, REGISTRY, REVERSE_RESOLVER, RESOLVER, CONTENT_RESOLVER, POP_RULES, STORE_FACTORY, POP_CONTROLLER, POP_RESOLVER, NAME_ESCROW, 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.
- Deferred LabelStore deployment (runtime). See DotnsPopController.
- Transfer fee is zero until the store is settled (runtime). See DotnsPopController.
- Root origin is not propagated through delegatecalls (runtime). See RootGatewayDispatcher.
- No standalone user-status mapping (current implementation). See DotnsPopController.
License
Licensed under the MIT License. See LICENSE. External interface definitions under contracts/external/ retain their upstream licences (the SPDX header in each file is authoritative). Security policy and disclosure: see SECURITY.md.
Contents
DotnsRoleManager
Inherits: AccessControlUpgradeable, OwnableUpgradeable, IDotnsRoleManager
Title: Dotns Role Manager
Shared owner-administered role layer for DotNS contracts with operational roles.
Consuming contracts define their supported role set in @custom:function _isSupportedRole. The owner remains the only account that can grant or revoke roles; role holders receive only the operational permissions each consuming contract explicitly gates with
Notes:
-
function: _checkRoleOrOwner.
-
security-contact: admin@parity.io
Functions
_dotnsRoleManagerInit
Initialises the OpenZeppelin access-control state for consuming contracts.
Must be called during the consuming contract initialiser.
function _dotnsRoleManagerInit() internal onlyInitializing;
setRole
Grants or revokes an operational role.
Only the owner can manage roles (otherwise @custom:reverts OwnableUnauthorizedAccount);
role must be one of the roles recognised by the consuming contract (otherwise
Note:
reverts: UnsupportedRole); account must not be the zero address (otherwise
function setRole(bytes32 role, address account, bool enabled) external override onlyOwner;
Parameters
| Name | Type | Description |
|---|---|---|
role | bytes32 | Role identifier declared in DotnsConstants. |
account | address | Account whose role membership is updated. |
enabled | bool | Whether the role should be granted or revoked. |
grantRole
Grants role to account.
If account had not been already granted role, emits a {RoleGranted}
event.
Requirements:
- the caller must have
role's admin role.
function grantRole(
bytes32 role,
address account
)
public
override(AccessControlUpgradeable, IAccessControl)
onlyOwner;
revokeRole
Revokes role from account.
If account had been granted role, emits a {RoleRevoked} event.
Requirements:
- the caller must have
role's admin role.
function revokeRole(
bytes32 role,
address account
)
public
override(AccessControlUpgradeable, IAccessControl)
onlyOwner;
supportsInterface
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override(AccessControlUpgradeable)
returns (bool supported);
_checkRoleOrOwner
Reverts unless the caller is the owner or holds role.
Consuming contracts use this for operational paths where the owner keeps super-user access and role holders receive a narrower permission; any other caller is rejected with @custom:reverts NotRoleOrOwner.
function _checkRoleOrOwner(bytes32 role) internal view;
_setRole
Grants or revokes a supported role for account.
role must be recognised by the consuming contract (otherwise
Notes:
-
reverts: UnsupportedRole) and
accountmust be non-zero (otherwise -
reverts: InvalidRoleAccount). Delegates to OpenZeppelin's
_grantRoleor_revokeRole, which emit @custom:emits IAccessControl.RoleGranted on grant and -
emits: IAccessControl.RoleRevoked on revoke.
function _setRole(bytes32 role, address account, bool enabled) internal;
_isSupportedRole
Returns whether role is recognised by the consuming contract.
Implemented by each consuming contract so unsupported role identifiers fail closed.
function _isSupportedRole(bytes32 role) internal view virtual returns (bool supported);
IDotnsRoleManager
Inherits: IAccessControl
Title: DotNS Role Manager
Shared owner-administered role API for DotNS contracts with operational roles.
Role identifiers are declared in DotnsConstants. Ownership remains the source of
super-user authority: the owner grants and revokes supported roles, while role holders
receive only the operational permissions each consuming contract recognises.
Note: security-contact: admin@parity.io
Functions
setRole
Grants or revokes an operational role.
Only the owner can manage roles (otherwise @custom:reverts OwnableUnauthorizedAccount);
role must be one of the roles recognised by the consuming contract (otherwise
Notes:
-
reverts: UnsupportedRole);
accountmust not be the zero address (otherwise -
reverts: InvalidRoleAccount). Emits @custom:emits IAccessControl.RoleGranted on grant and @custom:emits IAccessControl.RoleRevoked on revoke.
function setRole(bytes32 role, address account, bool enabled) external;
Parameters
| Name | Type | Description |
|---|---|---|
role | bytes32 | Role identifier declared in DotnsConstants. |
account | address | Account whose role membership is updated. |
enabled | bool | Whether the role should be granted or revoked. |
Errors
NotRoleOrOwner
Thrown when a caller is neither the contract owner nor a holder of role.
error NotRoleOrOwner(address caller, bytes32 role);
UnsupportedRole
Thrown when role management is attempted for a role the contract does not use.
error UnsupportedRole(bytes32 role);
InvalidRoleAccount
Thrown when role management is attempted for the zero address.
error InvalidRoleAccount(address account);
Contents
Create3Factory
Title: Dotns CREATE3 Factory
Permissionless wrapper around Solady's audited CREATE3 library.
Anyone may call @custom:function deploy. CREATE3 addresses are a pure function of
(factory_address, salt) — the caller never enters the derivation — so caller-gating
would not strengthen address determinism. Salt-squatting griefing is in-scope for the
caller, not the factory: salts in the DotNS namespace (CREATE3_SALT_NAMESPACE in
BaseDeployer) get bumped end-to-end if a slot is ever found occupied. Keeping the
factory permissionless removes the cross-chain ownership-coordination burden and lets
CI keys, ops keys, and recovery flows all coexist without ownership transfers.
Note: security-contact: admin@parity.io
Functions
receive
Accepts native transfers so callers can pre-fund the factory if they want
constructors that take msg.value.
The dotNS pipeline never forwards native value; this exists purely so the factory is compatible with payable-constructor callers from other consumers.
receive() external payable;
deploy
Deploys initCode at the deterministic address derived from salt.
Permissionless: any caller may invoke. The deployed address is
CREATE3.predictDeterministicAddress(salt) and is independent of msg.sender. The
call reverts if initCode is empty (@custom:reverts EmptyInitCode) or if the
predicted slot is already occupied (Solady raises its own revert on collision). Emits
Note: emits: Deployed on success. Native value attached to the call is forwarded to the new contract's constructor.
function deploy(
bytes32 salt,
bytes calldata initCode
)
external
payable
returns (address deployed);
Parameters
| Name | Type | Description |
|---|---|---|
salt | bytes32 | CREATE3 salt; combined with this factory's address to derive the target. |
initCode | bytes | Concatenation of creation bytecode and ABI-encoded constructor args. |
Returns
| Name | Type | Description |
|---|---|---|
deployed | address | Address of the newly deployed contract. |
predict
Returns the address that @custom:function deploy would target for salt.
Pure projection of (factory_address, salt) — does not check whether the address
is currently occupied. Callers that need occupancy info should follow up with an
addr.code.length check.
function predict(bytes32 salt) external view returns (address predicted);
Parameters
| Name | Type | Description |
|---|---|---|
salt | bytes32 | CREATE3 salt to look up. |
Returns
| Name | Type | Description |
|---|---|---|
predicted | address | Address at which a @custom:function deploy call with this salt would instantiate. |
Events
Deployed
Emitted on every successful CREATE3 deployment through this factory.
salt and deployed are indexed so consumers can filter by either. initCodeHash
is published alongside the deployment so downstream tooling can pin codehash
expectations against the on-chain artefact without re-fetching bytecode.
event Deployed(
bytes32 indexed salt, address indexed deployed, bytes32 initCodeHash, uint256 value
);
Parameters
| Name | Type | Description |
|---|---|---|
salt | bytes32 | The CREATE3 salt used to derive the deployed address. |
deployed | address | Address at which the contract was instantiated. |
initCodeHash | bytes32 | keccak256 of the init code passed to @custom:function deploy. |
value | uint256 | Native value forwarded to the constructor (zero for the dotNS pipeline). |
Errors
EmptyInitCode
Thrown when @custom:function deploy is called with an empty initCode blob.
CREATE3 with empty init code would deploy a zero-byte contract at the predicted address; the factory rejects the call early so callers fail fast on a malformed artefact lookup rather than land a useless deployment.
error EmptyInitCode();
Contents
DotnsNameEscrow
Inherits: Initializable, UUPSUpgradeable, OwnableUpgradeable, ReentrancyGuardTransient, ERC165Upgradeable, IERC721Receiver, IDotnsNameEscrow
Title: Dotns Name Escrow
Holds refundable deposits for .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
| Name | Type | Description |
|---|---|---|
registry | IDotnsProtocolRegistry | Protocol registry used to resolve registrar and controller addresses. |
cooldownSeconds | uint256 | Refund 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
| Name | Type | Description |
|---|---|---|
count | uint256 | Number of released tokens not yet reclaimed. |
reserves
Returns total amount of assets liabilities reserved for withdrawals.
function reserves(address asset) external view returns (uint256 amount);
Parameters
| Name | Type | Description |
|---|---|---|
asset | address | Asset address. address(0) denotes native token. |
releasedTokens
Returns a bounded paginated slice of released token identifiers.
limit must be non-zero and at most MAX_RELEASED_PAGE_SIZE, otherwise
Note: reverts: InvalidPageSize.
function releasedTokens(
uint256 start,
uint256 limit
)
external
view
override
returns (uint256[] memory tokenIds);
Parameters
| Name | Type | Description |
|---|---|---|
start | uint256 | Start index into the released-token set. |
limit | uint256 | Maximum number of token identifiers to return. |
deposit
Records an asset deposit position for a token.
Only the configured controller may call this, otherwise @custom:reverts NotController.
params.amount must equal msg.value, otherwise @custom:reverts InvalidAmount; a
zero amount is accepted so cross-payer and free-tier registrations can still seed a
position that the release lifecycle can advance. Only native deposits are accepted
today, so a non-zero params.asset triggers @custom:reverts AssetNotSupported, and
a zero params.recipient triggers @custom:reverts InvalidRecipient. The slot for
params.tokenId must be empty (sentinel: position.recipient == address(0)):
a previously funded position triggers @custom:reverts PositionAlreadyFunded, and a
position already in the released phase triggers @custom:reverts AlreadyReleased.
Emits @custom:emits NativeDepositRecorded once the deposit is booked.
function deposit(DepositParams calldata params) external payable override onlyController;
creditOverpayment
Credits msg.value to recipient's pull-payment ledger so the caller can later
pull the balance with @custom:func claimWithdrawal.
Only the configured controller may call this, otherwise @custom:reverts NotController.
recipient must be non-zero (@custom:reverts InvalidRecipient) and msg.value must
be non-zero (@custom:reverts InvalidAmount). Used by the registrar controller to
refund overpayment without pushing native value into a potentially reverting
contract receiver. Emits @custom:emits OverpaymentRefunded once the credit lands.
function creditOverpayment(address recipient) external payable override onlyController;
Parameters
| Name | Type | Description |
|---|---|---|
recipient | address | Address whose pending balance should grow by msg.value. |
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
| Name | Type | Description |
|---|---|---|
charged | uint256 | Amount 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
| Name | Type | Description |
|---|---|---|
amount | uint256 | Native amount transferred to the caller. |
pendingWithdrawal
Returns the pending refund balance owed to recipient.
function pendingWithdrawal(address recipient) external view override returns (uint256 amount);
Returns
| Name | Type | Description |
|---|---|---|
amount | uint256 | Native amount currently credited to recipient and pullable via claimWithdrawal. |
claimRefund
Pulls a single time-locked refund entry.
Caller must be the entry's recipient (@custom:reverts NotRefundRecipient otherwise),
the entry must exist (@custom:reverts NoSuchRefundEntry on a deleted or unknown id),
and block.timestamp must have reached availableAt (@custom:reverts
RefundLocked otherwise). The entry is deleted before the transfer; a failing native
transfer triggers @custom:reverts RefundFailed. Emits @custom:emits RefundClaimed.
function claimRefund(uint256 entryId) external override nonReentrant returns (uint256 amount);
Parameters
| Name | Type | Description |
|---|---|---|
entryId | uint256 | Identifier of the entry to claim. |
Returns
| Name | Type | Description |
|---|---|---|
amount | uint256 | Native amount transferred to the caller. |
claimRefundsBatch
Pulls multiple time-locked refund entries in one call.
Atomic: any invalid entry in the batch (wrong recipient, missing, or locked) reverts
the entire call. The batch size is bounded by MAX_REFUND_PAGE_SIZE
(@custom:reverts InvalidPageSize otherwise). Aggregates the per-entry amounts and
transfers once; on transfer failure @custom:reverts RefundFailed. Emits
Note: emits: RefundClaimed once per entry.
function claimRefundsBatch(uint256[] calldata entryIds)
external
override
nonReentrant
returns (uint256 totalAmount);
Parameters
| Name | Type | Description |
|---|---|---|
entryIds | uint256[] | List of entry identifiers to claim. |
Returns
| Name | Type | Description |
|---|---|---|
totalAmount | uint256 | Sum of the credited amounts transferred to the caller. |
pendingRefundCount
Returns the number of pending refund entries owed to recipient.
function pendingRefundCount(address recipient) external view override returns (uint256 count);
pendingRefundIds
Returns up to limit pending refund entry ids for recipient, starting at
offset.
Limit must be in (0, MAX_REFUND_PAGE_SIZE], otherwise @custom:reverts
InvalidPageSize.
function pendingRefundIds(
address recipient,
uint256 offset,
uint256 limit
)
external
view
override
returns (uint256[] memory entryIds);
pendingRefunds
Returns up to limit pending refund entries for recipient, paired with their
entry ids.
Limit must be in (0, MAX_REFUND_PAGE_SIZE], otherwise @custom:reverts
InvalidPageSize.
function pendingRefunds(
address recipient,
uint256 offset,
uint256 limit
)
external
view
override
returns (uint256[] memory entryIds, RefundEntry[] memory entries);
refundEntry
Returns a single refund entry by id, or a zero-filled struct if the id is unknown.
function refundEntry(uint256 entryId)
external
view
override
returns (RefundEntry memory entry);
_creditRefund
Internal helper: allocate a new entryId and credit a refund to recipient.
Assigns the next monotonic entryId, stores the entry, appends to the recipient's
enumeration array, and emits @custom:emits RefundCredited. The cooldown is read from the
configured cooldown storage value; @custom:constant MAX_COOLDOWN bounds it so the cast to
uint64 cannot truncate for any plausible block timestamp.
function _creditRefund(
address recipient,
uint256 amount,
uint256 tokenId
)
internal
returns (uint256 entryId);
_removeRefundEntry
Internal helper: delete a refund entry and swap-pop its slot in the recipient's enumeration array.
function _removeRefundEntry(uint256 entryId, address recipient) internal;
reclaim
Transfers a released-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
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | |
newOwner | address | Address 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
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
_registrar
Returns the configured registrar from the protocol registry.
function _registrar() internal view returns (IDotnsRegistrar registrar);
_onlyController
Restricts calls to the configured controller from the protocol registry.
function _onlyController() internal view;
_onlyRegistrar
Restricts calls to the configured registrar from the protocol registry.
function _onlyRegistrar() internal view;
_addReleasedToken
Adds a token to the released-token set if absent.
function _addReleasedToken(uint256 tokenId) internal;
_removeReleasedToken
Removes a token from the released-token set if present.
function _removeReleasedToken(uint256 tokenId) internal;
_authorizeUpgrade
Function that should revert when msg.sender is not authorized to upgrade the contract.
Called by
{upgradeToAndCall}.
Normally, this function will use an xref:access.adoc[access control] modifier such as
{Ownable-onlyOwner}.
function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;
IDotnsNameEscrow
Title: Dotns Name Escrow Interface
Escrows refundable deposits for .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
| Name | Type | Description |
|---|---|---|
asset | address | Asset address. address(0) denotes native token. |
getReleasePosition
Returns the escrow state for a token.
function getReleasePosition(uint256 tokenId)
external
view
returns (ReleasePosition memory position);
releasedTokenCount
Returns the number of tokens currently held by escrow pending reclaim or withdrawal.
function releasedTokenCount() external view returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Number of released tokens not yet reclaimed. |
releasedTokens
Returns a bounded paginated slice of released token identifiers.
limit must be non-zero and at most MAX_RELEASED_PAGE_SIZE, otherwise
Note: reverts: InvalidPageSize.
function releasedTokens(
uint256 start,
uint256 limit
)
external
view
returns (uint256[] memory tokenIds);
Parameters
| Name | Type | Description |
|---|---|---|
start | uint256 | Start index into the released-token set. |
limit | uint256 | Maximum number of token identifiers to return. |
deposit
Records an asset deposit position for a token.
Only the configured controller may call this, otherwise @custom:reverts NotController.
params.amount must equal msg.value, otherwise @custom:reverts InvalidAmount; a
zero amount is accepted so cross-payer and free-tier registrations can still seed a
position that the release lifecycle can advance. Only native deposits are accepted
today, so a non-zero params.asset triggers @custom:reverts AssetNotSupported, and
a zero params.recipient triggers @custom:reverts InvalidRecipient. The slot for
params.tokenId must be empty (sentinel: position.recipient == address(0)):
a previously funded position triggers @custom:reverts PositionAlreadyFunded, and a
position already in the released phase triggers @custom:reverts AlreadyReleased.
Emits @custom:emits NativeDepositRecorded once the deposit is booked.
function deposit(DepositParams calldata params) external payable;
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
| Name | Type | Description |
|---|---|---|
recipient | address | Address 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
| Name | Type | Description |
|---|---|---|
charged | uint256 | Amount actually credited to insurance. |
insuranceFund
Returns the cumulative cross-tier fee balance held against future shortfalls.
function insuranceFund() external view returns (uint256 balance);
Returns
| Name | Type | Description |
|---|---|---|
balance | uint256 | Current 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
| Name | Type | Description |
|---|---|---|
amount | uint256 | Native amount transferred to the caller. |
pendingWithdrawal
Returns the pending refund balance owed to recipient.
function pendingWithdrawal(address recipient) external view returns (uint256 amount);
Returns
| Name | Type | Description |
|---|---|---|
amount | uint256 | Native amount currently credited to recipient and pullable via claimWithdrawal. |
reclaim
Transfers a released-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
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | |
newOwner | address | Address 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
| Name | Type | Description |
|---|---|---|
entryId | uint256 | Identifier of the entry to claim. |
Returns
| Name | Type | Description |
|---|---|---|
amount | uint256 | Native amount transferred to the caller. |
claimRefundsBatch
Pulls multiple time-locked refund entries in one call.
Atomic: any invalid entry in the batch (wrong recipient, missing, or locked) reverts
the entire call. The batch size is bounded by MAX_REFUND_PAGE_SIZE
(@custom:reverts InvalidPageSize otherwise). Aggregates the per-entry amounts and
transfers once; on transfer failure @custom:reverts RefundFailed. Emits
Note: emits: RefundClaimed once per entry.
function claimRefundsBatch(uint256[] calldata entryIds) external returns (uint256 totalAmount);
Parameters
| Name | Type | Description |
|---|---|---|
entryIds | uint256[] | List of entry identifiers to claim. |
Returns
| Name | Type | Description |
|---|---|---|
totalAmount | uint256 | Sum of the credited amounts transferred to the caller. |
pendingRefundCount
Returns the number of pending refund entries owed to recipient.
function pendingRefundCount(address recipient) external view returns (uint256 count);
pendingRefundIds
Returns up to limit pending refund entry ids for recipient, starting at
offset.
Limit must be in (0, MAX_REFUND_PAGE_SIZE], otherwise @custom:reverts
InvalidPageSize.
function pendingRefundIds(
address recipient,
uint256 offset,
uint256 limit
)
external
view
returns (uint256[] memory entryIds);
pendingRefunds
Returns up to limit pending refund entries for recipient, paired with their
entry ids.
Limit must be in (0, MAX_REFUND_PAGE_SIZE], otherwise @custom:reverts
InvalidPageSize.
function pendingRefunds(
address recipient,
uint256 offset,
uint256 limit
)
external
view
returns (uint256[] memory entryIds, RefundEntry[] memory entries);
refundEntry
Returns a single refund entry by id, or a zero-filled struct if the id is unknown.
function refundEntry(uint256 entryId) external view returns (RefundEntry memory entry);
Events
NativeDepositRecorded
Emitted when a native-token deposit is recorded.
event NativeDepositRecorded(uint256 indexed tokenId, uint256 amount);
NameReleased
Emitted when a token is released into escrow.
event NameReleased(
uint256 indexed tokenId,
address indexed recipient,
address indexed asset,
uint256 amount,
uint256 withdrawAvailableAt
);
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | |
recipient | address | Refund recipient snapshotted at release time. |
asset | address | Deposit asset. address(0) denotes native token. |
amount | uint256 | |
withdrawAvailableAt | uint256 | Earliest withdrawal timestamp. |
RefundWithdrawn
Emitted when a refund is credited to the recipient's pending balance.
event RefundWithdrawn(
uint256 indexed tokenId, address indexed recipient, address indexed asset, uint256 amount
);
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | |
recipient | address | |
asset | address | Refund asset. address(0) denotes native token. |
amount | uint256 |
WithdrawalClaimed
Emitted when a recipient pulls their accumulated pending refund balance.
event WithdrawalClaimed(address indexed recipient, uint256 amount);
RefundCredited
Emitted when a refund is credited to the time-locked refund ledger.
event RefundCredited(
address indexed recipient,
uint256 indexed entryId,
uint256 amount,
uint64 availableAt,
uint256 indexed tokenId
);
Parameters
| Name | Type | Description |
|---|---|---|
recipient | address | Address that may claim the entry once availableAt has elapsed. |
entryId | uint256 | Newly-assigned identifier for the credited entry. |
amount | uint256 | Native value credited. |
availableAt | uint64 | Earliest block timestamp at which the recipient may claim. |
tokenId | uint256 | Token associated with the credit, retained for traceability. |
RefundClaimed
Emitted when a recipient claims a single refund entry.
event RefundClaimed(address indexed recipient, uint256 indexed entryId, uint256 amount);
Parameters
| Name | Type | Description |
|---|---|---|
recipient | address | Address that pulled the entry. |
entryId | uint256 | Identifier of the claimed entry, now deleted. |
amount | uint256 | Native value transferred to recipient. |
NameReclaimed
Emitted when a released token is reclaimed by a new owner via registration.
event NameReclaimed(
uint256 indexed tokenId, address indexed previousRecipient, address indexed newOwner
);
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | |
previousRecipient | address | Address that received the refund for the prior registration. |
newOwner | address |
CooldownUpdated
Emitted when the cooldown duration for future releases is updated.
event CooldownUpdated(uint256 indexed currentCooldown, uint256 indexed newCooldown);
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
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | |
payer | address | Original msg.sender whose value funded the fee. |
recipient | address | |
amount | uint256 | |
isRegistration | bool | True when emitted from 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
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | |
owed | uint256 | |
available | uint256 | Combined 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
| Name | Type | Description |
|---|---|---|
supplied | uint256 | Cooldown value the caller asked for. |
maxAllowed | uint256 | Upper bound enforced by the contract. |
InvalidAmount
Thrown when the supplied amount is invalid.
error InvalidAmount();
InvalidAsset
Thrown when the supplied ERC20 asset is invalid.
error InvalidAsset();
PositionAlreadyFunded
Thrown when a deposit position is already funded.
error PositionAlreadyFunded(uint256 tokenId);
DepositNotConfigured
Thrown when no deposit is configured for the token.
error DepositNotConfigured(uint256 tokenId);
AlreadyReleased
Thrown when the token has already been released.
error AlreadyReleased(uint256 tokenId);
NotReleased
Thrown when the token has not been released.
error NotReleased(uint256 tokenId);
AlreadyClaimed
Thrown when the refund has already been claimed.
error AlreadyClaimed(uint256 tokenId);
NotReclaimable
Thrown when a token is not in a reclaimable state (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
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | |
availableAt | uint256 | Earliest withdrawal timestamp. |
currentTime | uint256 | Current block timestamp. |
RefundFailed
Thrown when a refund transfer fails.
error RefundFailed(uint256 tokenId);
NoPendingWithdrawal
Thrown when claimWithdrawal() is called but the caller has no pending balance.
error NoPendingWithdrawal();
NoSuchRefundEntry
Thrown when a refund entry is referenced but does not exist.
error NoSuchRefundEntry(uint256 entryId);
RefundLocked
Thrown when a refund entry is claimed before its availableAt cooldown has
elapsed.
error RefundLocked(uint256 entryId, uint64 availableAt);
NotAcceptedTransfer
Thrown when escrow receives an ERC721 transfer from a non-registrar source.
error NotAcceptedTransfer(address caller);
UnsolicitedDeposit
Thrown when escrow receives a registrar-sourced ERC721 transfer that does not correspond to a live release. Blocks holders who try to push a token into custody by calling @custom:function safeTransferFrom directly without going through @custom:function release, which would otherwise trap the NFT and any deposit permanently.
error UnsolicitedDeposit(uint256 tokenId);
Structs
DepositParams
Parameters for recording a deposit position.
The refund recipient is seeded at deposit time but is not locked: it rebinds to the current NFT holder on every transfer that moves the name off the prior recipient, so the deposit follows the name rather than the original payer. Only the current holder can release into escrow and pull the refund.
struct DepositParams {
uint256 tokenId;
address asset;
uint256 amount;
address recipient;
}
Properties
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | |
asset | address | Deposit asset. The zero address denotes the native token. |
amount | uint256 | |
recipient | address | Initial refund recipient; rebound to the current NFT holder on transfer. |
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
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | |
payer | address | Original msg.sender of the controller's register call. |
recipient | address | The NFT registrant the fee was paid on behalf of. |
ChargeTransferFeeParams
Inputs for charging transfer friction and rebinding the escrow position.
The fee charged is the 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
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | |
reachFloor | uint256 | Required fee paid by the sender on a downward or cross-reach transfer. |
payer | address | Original sender of the registrar transfer entrypoint. |
to | address | NFT recipient. Becomes the new position recipient whenever a position exists. |
ReleasePosition
Canonical escrow state for a token.
Tracks the phased lifecycle as two flags: released flips on release (NFT in escrow,
cooldown started); claimed flips on withdraw (refund credited to the pull-payment
ledger). The position is deleted on reclaim, freeing the slot for re-registration.
struct ReleasePosition {
address recipient;
address asset;
uint256 amount;
uint64 withdrawAvailableAt;
bool released;
bool claimed;
}
Properties
| Name | Type | Description |
|---|---|---|
recipient | address | |
asset | address | Deposit asset. address(0) denotes native token. |
amount | uint256 | |
withdrawAvailableAt | uint64 | Earliest timestamp at which withdrawal is permitted. |
released | bool | |
claimed | bool |
RefundEntry
Time-locked refund entry produced when the protocol owes a recipient value outside the registration-overpayment path.
Every credit creates a fresh entry with its own availableAt; later credits do not
reset earlier entries' clocks. Recipients claim entries individually or in batches.
struct RefundEntry {
address recipient;
uint256 amount;
uint64 availableAt;
uint256 tokenId;
}
Properties
| Name | Type | Description |
|---|---|---|
recipient | address | Address that may claim this entry once availableAt has elapsed. |
amount | uint256 | Native value credited. |
availableAt | uint64 | Earliest block timestamp at which the recipient may claim. |
tokenId | uint256 | Token this entry was produced for, retained for traceability. |
Contents
Contents
IPersonhood
Title: IPersonhood - Proof of Personhood Precompile
Query personhood status of an account.
Available at address 0x000000000000000000000000000000000a010000. The precompile reads from the alias-accounts pallet which stores per-context alias mappings backed by ring membership proofs. Ring roots are received from the People chain via XCM pub/sub. Example usage: IPersonhood personhood = IPersonhood(0x000000000000000000000000000000000a010000); IPersonhood.PersonhoodInfo memory info = personhood.personhoodStatus(someAddress, bytes32("dotns")); if (info.status == 2) { // full person ... }
Note: security-contact: admin@parity.io
Functions
personhoodStatus
Returns personhood info for an account within a specific application context.
function personhoodStatus(
address account,
bytes32 context
)
external
view
returns (PersonhoodInfo memory info);
Parameters
| Name | Type | Description |
|---|---|---|
account | address | The address to query. |
context | bytes32 | A 32-byte application identifier. Each application picks a fixed constant (e.g. bytes32("dotns")). The same person receives a different contextAlias in each context, preventing cross-application linkability. |
Returns
| Name | Type | Description |
|---|---|---|
info | PersonhoodInfo | The personhood info struct. All fields are zero when the account has no personhood. |
Structs
PersonhoodInfo
Personhood information for an account in a given context.
status tiers are defined incrementally: 0=None, 1=Lite, 2=Full. If we add more types in the future, the existing ones remain unchanged.
struct PersonhoodInfo {
uint8 status;
bytes32 contextAlias;
}
Properties
| Name | Type | Description |
|---|---|---|
status | uint8 | The personhood verification tier. |
contextAlias | bytes32 | Context-specific 32-byte pseudonym derived from ring membership proof. Unique per person per context, preventing cross-application linkability. Zero when status is None. |
Contents
ISystem
Title: ISystem
Minimal subset of revive's System precompile, vendored from
polkadot-sdk's substrate/frame/revive/uapi/sol/ISystem.sol.
Exposed at @custom:address SYSTEM_ADDR on every revive runtime that opts the
precompile in.
Kept intentionally minimal. New methods should be added on demand rather than mirrored wholesale, so the audit surface stays small and any upstream change is a deliberate review event.
Functions
callerIsRoot
Returning true iff the immediate caller's substrate origin is Root.
Reverts on a RuntimeOrigin::Signed(_) or non-Root origin.
function callerIsRoot() external view returns (bool);
Contents
IPopRules
Title: Proof of Personhood Rules for Dotns
Proof of personhood interface defining Dotns price calculation, PoP-tier requirements, and base-name reservation rules.
Classifies labels into the PoP tier required for registration and exposes reservation
metadata. Length <= 5 is reserved for governance; lengths 6-8 require PopFull unless they
carry exactly two trailing digits (PopLite, gateway-issued); lengths >= 9 are open to
every caller as NoStatus when they carry zero or exactly two trailing digits. Any one-digit
suffix, and any suffix longer than two digits, is invalid; internal digits do not affect
classification. Reservations are keyed by the digit-stripped stem so alice and alice42
share a slot.
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
| Name | Type | Description |
|---|---|---|
name | string | The name label being evaluated. |
Returns
| Name | Type | Description |
|---|---|---|
requirement | PopStatus | Required tier for registration. |
message | string | Explanation of the classification result. |
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
| Name | Type | Description |
|---|---|---|
newStartingPrice | uint256 | New 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
| Name | Type | Description |
|---|---|---|
stem | string | The base label with no trailing digits. |
user | address | The address receiving reservation rights. |
reserveBaseNameForPop
Writes or refreshes a reservation for a bare base-name stem.
Gateway-driven reservation path used by the PoP controller. Only a controller in the
registrar's controllers set may call this, otherwise @custom:reverts NotRegistry.
Does not apply the lite-format length window that @custom:function reserveBaseName
enforces, but does require the input to be canonical and stem-shaped (no trailing
digits); a non-canonical or non-stem label triggers @custom:reverts PopError. If the
slot is already live and held by a different user, @custom:reverts PopError so the
caller's local bookkeeping and PopRules state stay in lockstep; if it is live for the
same user, expiry is refreshed to block.timestamp + MAX_RESERVATION_TIME. Emits
Note: emits: BaseNameReserved on every successful write.
function reserveBaseNameForPop(string calldata stem, address user) external;
Parameters
| Name | Type | Description |
|---|---|---|
stem | string | The base label with no trailing digits. |
user | address | The address receiving reservation rights. |
releaseBaseName
Clears a reservation for a base-name stem.
Only a controller in the registrar's controllers set may call this, otherwise
Notes:
-
reverts: NotRegistry. Non-canonical or non-stem labels trigger
-
reverts: PopError. Live reservations may only be cleared by the same controller that wrote them; another authorised controller attempting to clear a live slot triggers
-
reverts: PopError. Expired reservations may be cleared by any authorised controller as garbage collection. Used by the PoP controller when a reservation is claimed, relinquished, or a queue head promotion leaves the slot empty. Emits
-
emits: BaseNameReleased once the slot is cleared.
function releaseBaseName(string calldata stem) external;
Parameters
| Name | Type | Description |
|---|---|---|
stem | string | The base label whose reservation should be cleared (no trailing digits). |
releaseReservationForReclaim
Clears a reservation when the slot owner matches expectedOwner, allowing any
registrar-authorised controller (not only the stamping one) to release the slot.
Narrower than @custom:function releaseBaseName: callers must prove they know the
slot owner, so cross-controller release is gated on a positive match rather than on
caller identity. Intended for the public registrar controller's reclaim path, where
a prior occupant has handed the name back to escrow and the new registrant needs
the cross-flow guard cleared regardless of which controller originally stamped it.
Only a registrar-authorised controller may call this (@custom:reverts NotRegistry).
Non-canonical or non-stem labels trigger @custom:reverts PopError. A live reservation
whose owner does not match expectedOwner triggers @custom:reverts PopError; expired
reservations are cleared regardless. Emits @custom:emits BaseNameReleased.
function releaseReservationForReclaim(string calldata stem, address expectedOwner) external;
Parameters
| Name | Type | Description |
|---|---|---|
stem | string | The base label whose reservation should be cleared (no trailing digits). |
expectedOwner | address | The address the caller expects to be the current reservation owner. |
getBaseNameReservation
Retrieves reservation information for a base name.
Raw accessor: returns the stored slot regardless of expiry. Use
Notes:
-
function: isBaseNameReserved when live-window semantics are needed. Non-canonical labels trigger
-
reverts: PopError.
function getBaseNameReservation(string calldata baseName)
external
view
returns (address owner, uint64 expires);
Parameters
| Name | Type | Description |
|---|---|---|
baseName | string | The base label without trailing digits. |
Returns
| Name | Type | Description |
|---|---|---|
owner | address | The address assigned to the reservation. |
expires | uint64 | UNIX timestamp when the reservation expires. |
stripDigits
Returns the bare stem of a label, i.e. the label with any trailing ASCII digits removed.
Mirrors the normalisation that @custom:function reserveBaseName applies before writing a reservation, so callers can look up or release a reservation by passing the full label without re-implementing the digit-stripping rule. Non-canonical labels trigger @custom:reverts PopError.
function stripDigits(string calldata name) external pure returns (string memory stem);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Full label (with or without trailing digits). |
Returns
| Name | Type | Description |
|---|---|---|
stem | string | The label with trailing digits removed. |
isBaseNameReserved
Indicates whether a base name is currently reserved.
Applies the live-window predicate to the stored slot so an expired reservation reads as free. Non-canonical labels trigger @custom:reverts PopError.
function isBaseNameReserved(string calldata baseName)
external
view
returns (bool reservedStatus, address owner, uint64 expires);
Parameters
| Name | Type | Description |
|---|---|---|
baseName | string | The base label without trailing digits. |
Returns
| Name | Type | Description |
|---|---|---|
reservedStatus | bool | True if a live reservation is active. |
owner | address | The reservation holder (zero when not reserved). |
expires | uint64 | UNIX timestamp when the reservation expires. |
priceWithCheck
Calculates price with PoP classification and reservation enforcement.
Reverting pricing path used by the commit-reveal controller. Price is 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
| Name | Type | Description |
|---|---|---|
name | string | Domain label. |
userAddress | address | Registering user for the given label. |
Returns
| Name | Type | Description |
|---|---|---|
metadata | PriceWithMeta | Price with PoP requirements and classification. |
priceWithoutCheck
Calculates price with PoP classification and reservation metadata, without reverting on conflicts.
Non-reverting counterpart to priceWithCheck: surfaces the same fields, but reports
a Reserved status through metadata instead of reverting when the base stem is
held by another user. Used by front-ends that need to present a price and eligibility
preview without forcing a transaction attempt. Governance-reserved names are not
rejected here either; the caller decides what to do. Non-canonical labels still
trigger @custom:reverts PopError because the input is malformed rather than just
contested.
function priceWithoutCheck(
string calldata name,
address userAddress
)
external
view
returns (PriceWithMeta memory metadata);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label. |
userAddress | address | Registering user for the given label. |
Returns
| Name | Type | Description |
|---|---|---|
metadata | PriceWithMeta | Price with PoP requirements and classification. |
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
| Name | Type | Description |
|---|---|---|
name | string | Domain label being acted on. |
account | address | Account 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
| Name | Type | Description |
|---|---|---|
name | string | Domain label being transferred. |
from | address | Current holder of the name. |
to | address | Incoming 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
| Name | Type | Description |
|---|---|---|
name | string | The label to check. |
Returns
| Name | Type | Description |
|---|---|---|
isBase | bool | True when the label has no trailing digits. |
price
Calculates registration cost for a label.
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
| Name | Type | Description |
|---|---|---|
name | string | Domain label to price. |
Returns
| Name | Type | Description |
|---|---|---|
cost | uint256 | Registration cost in wei. |
Events
BaseNameReserved
Emitted when a base name receives a reservation.
event BaseNameReserved(string indexed baseName, address indexed owner, uint64 expires);
Parameters
| Name | Type | Description |
|---|---|---|
baseName | string | The digit-stripped label receiving the reservation. |
owner | address | Address obtaining the reservation right. |
expires | uint64 | UNIX timestamp when the reservation expires. |
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
| Name | Type | Description |
|---|---|---|
oldPrice | uint256 | Previous wei value. |
newPrice | uint256 | New wei value. |
BaseNameReleased
Emitted when a base-name reservation is cleared.
event BaseNameReleased(string indexed baseName);
Parameters
| Name | Type | Description |
|---|---|---|
baseName | string | The base label whose reservation was released. |
Errors
PopError
Thrown when a name violates PoP-tier or reservation requirements.
error PopError(string reason);
Parameters
| Name | Type | Description |
|---|---|---|
reason | string | Human-readable explanation of the failure condition. |
NotRegistry
Thrown when a caller is not an authorised controller on the registrar.
error NotRegistry();
Structs
PriceWithMeta
Bundle returned from metadata-aware pricing queries.
struct PriceWithMeta {
uint256 price;
PopStatus status;
PopStatus userStatus;
string message;
}
Properties
| Name | Type | Description |
|---|---|---|
price | uint256 | Registration cost; typically non-zero only for NoStatus users. |
status | PopStatus | Required PoP tier for this name. |
userStatus | PopStatus | Current PoP status recorded for the querying user. |
message | string | Human-readable classification description. |
Reservation
Reservation metadata for a base name (digits removed).
struct Reservation {
address owner;
uint64 expires;
address controller;
}
Properties
| Name | Type | Description |
|---|---|---|
owner | address | Address holding exclusive claim rights during the reservation window. |
expires | uint64 | UNIX timestamp when the reservation expires. |
controller | address | Address that wrote the reservation; the only address permitted to release it before expiry. |
Enums
PopStatus
Proof-of-Personhood eligibility tier.
NoStatus is the default for unverified users; PopLite and PopFull are the two
personhood tiers; Reserved covers both governance-held names and base stems held by
another user through the reservation table.
enum PopStatus {
NoStatus,
PopLite,
PopFull,
Reserved
}
PopRules
Inherits: Initializable, UUPSUpgradeable, OwnableUpgradeable, ERC165Upgradeable, IPopRules
Title: PopRules
Implements DotNS classification, 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
| Name | Type | Description |
|---|---|---|
_startingPrice | uint256 | Base price in wei for NoStatus users. |
registry | IDotnsProtocolRegistry | Protocol-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
| Name | Type | Description |
|---|---|---|
newStartingPrice | uint256 | New 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
| Name | Type | Description |
|---|---|---|
name | string | The name label being evaluated. |
Returns
| Name | Type | Description |
|---|---|---|
requirement | PopStatus | Required tier for registration. |
message | string | Explanation of the classification result. |
reserveBaseName
Creates or refreshes a reservation entry for a PopLite-eligible stem.
Commit-reveal reservation path. Only an authorised controller on the registrar may
call this, otherwise @custom:reverts NotRegistry. The caller passes the
already-stripped stem; the contract enforces stem shape (no trailing digits) and
PopLite-eligibility
(length in [6, 8]), and a non-canonical label or a label outside that shape triggers
Note: reverts: PopError. Cross-user collision on a live slot triggers @custom:reverts PopError so the caller cannot silently overwrite another user's reservation; same-user refresh and writes into an empty or expired slot emit @custom:emits BaseNameReserved.
function reserveBaseName(
string calldata stem,
address userAddress
)
external
override
onlyRegistry;
Parameters
| Name | Type | Description |
|---|---|---|
stem | string | The base label with no trailing digits. |
userAddress | address |
isBaseName
Returns whether name is a base name under PoP rules.
A base name has no trailing digits; lite-person labels always have exactly two trailing digits, so the two spaces are disjoint. Non-canonical labels trigger
Note: reverts: PopError.
function isBaseName(string calldata baseName) external pure override returns (bool isBase);
Parameters
| Name | Type | Description |
|---|---|---|
baseName | string |
Returns
| Name | Type | Description |
|---|---|---|
isBase | bool | True when the label has no trailing digits. |
getBaseNameReservation
Retrieves reservation information for a base name.
Raw accessor: returns the stored slot regardless of expiry. Use
Notes:
-
function: isBaseNameReserved when live-window semantics are needed. Non-canonical labels trigger
-
reverts: PopError.
function getBaseNameReservation(string calldata baseName)
external
view
override
returns (address reservationOwner, uint64 expiryTimestamp);
Parameters
| Name | Type | Description |
|---|---|---|
baseName | string | The base label without trailing digits. |
Returns
| Name | Type | Description |
|---|---|---|
reservationOwner | address | owner The address assigned to the reservation. |
expiryTimestamp | uint64 | expires UNIX timestamp when the reservation expires. |
isBaseNameReserved
Indicates whether a base name is currently reserved.
Applies the live-window predicate to the stored slot so an expired reservation reads as free. Non-canonical labels trigger @custom:reverts PopError.
function isBaseNameReserved(string calldata baseName)
external
view
override
returns (bool isReserved, address reservationOwner, uint64 expiryTimestamp);
Parameters
| Name | Type | Description |
|---|---|---|
baseName | string | The base label without trailing digits. |
Returns
| Name | Type | Description |
|---|---|---|
isReserved | bool | reservedStatus True if a live reservation is active. |
reservationOwner | address | owner The reservation holder (zero when not reserved). |
expiryTimestamp | uint64 | expires UNIX timestamp when the reservation expires. |
priceWithCheck
Calculates price with PoP classification and reservation enforcement.
Reverting pricing path used by the commit-reveal controller. Price is 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
| Name | Type | Description |
|---|---|---|
name | string | Domain label. |
userAddress | address | Registering user for the given label. |
Returns
| Name | Type | Description |
|---|---|---|
metadata | PriceWithMeta | Price with PoP requirements and classification. |
priceWithoutCheck
Calculates price with PoP classification and reservation metadata, without reverting on conflicts.
Non-reverting counterpart to priceWithCheck: surfaces the same fields, but reports
a Reserved status through metadata instead of reverting when the base stem is
held by another user. Used by front-ends that need to present a price and eligibility
preview without forcing a transaction attempt. Governance-reserved names are not
rejected here either; the caller decides what to do. Non-canonical labels still
trigger @custom:reverts PopError because the input is malformed rather than just
contested.
function priceWithoutCheck(
string calldata name,
address userAddress
)
external
view
override
returns (PriceWithMeta memory metadata);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label. |
userAddress | address | Registering user for the given label. |
Returns
| Name | Type | Description |
|---|---|---|
metadata | PriceWithMeta | Price with PoP requirements and classification. |
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
| Name | Type | Description |
|---|---|---|
name | string | Domain label to price. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | uint256 | cost 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
| Name | Type | Description |
|---|---|---|
name | string | Domain label being acted on. |
account | address | Account 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
| Name | Type | Description |
|---|---|---|
name | string | Domain label being transferred. |
from | address | Current holder of the name. |
to | address | Incoming 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
| Name | Type | Description |
|---|---|---|
name | string | Domain label. |
userAddress | address | Registering user. |
_isLive
Returns whether reservation is live at block.timestamp.
function _isLive(Reservation memory reservation) internal view returns (bool);
_countTrailingDigits
Counts trailing digits in a string.
function _countTrailingDigits(string calldata label)
internal
pure
returns (uint256 digitCount);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | String to analyse. |
Returns
| Name | Type | Description |
|---|---|---|
digitCount | uint256 | Number of trailing digits. |
_stripDigits
Strips trailing digits from a name.
function _stripDigits(string calldata name) internal pure returns (string memory baseName);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label. |
_classifyValidatedName
function _classifyValidatedName(string calldata name)
internal
pure
returns (PopStatus requirement, string memory message);
_requireCanonicalLabel
function _requireCanonicalLabel(string calldata name) internal pure;
supportsInterface
function supportsInterface(bytes4 interfaceId)
public
view
virtual
override
returns (bool supported);
_authorizeUpgrade
Function that should revert when msg.sender is not authorized to upgrade the contract.
Called by
{upgradeToAndCall}.
Normally, this function will use an xref:access.adoc[access control] modifier such as
{Ownable-onlyOwner}.
function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;
version
Returns implementation version.
function version() external pure virtual returns (string memory versionString);
_onlyRegistry
Ensures the caller is any controller authorised on the registrar.
function _onlyRegistry() internal view;
reserveBaseNameForPop
Writes or refreshes a reservation for a bare base-name stem.
Gateway-driven reservation path used by the PoP controller. Only a controller in the
registrar's controllers set may call this, otherwise @custom:reverts NotRegistry.
Does not apply the lite-format length window that @custom:function reserveBaseName
enforces, but does require the input to be canonical and stem-shaped (no trailing
digits); a non-canonical or non-stem label triggers @custom:reverts PopError. If the
slot is already live and held by a different user, @custom:reverts PopError so the
caller's local bookkeeping and PopRules state stay in lockstep; if it is live for the
same user, expiry is refreshed to block.timestamp + MAX_RESERVATION_TIME. Emits
Note: emits: BaseNameReserved on every successful write.
function reserveBaseNameForPop(
string calldata stem,
address userAddress
)
external
override
onlyRegistry;
Parameters
| Name | Type | Description |
|---|---|---|
stem | string | The base label with no trailing digits. |
userAddress | address |
stripDigits
Returns the bare stem of a label, i.e. the label with any trailing ASCII digits removed.
Mirrors the normalisation that @custom:function reserveBaseName applies before writing a reservation, so callers can look up or release a reservation by passing the full label without re-implementing the digit-stripping rule. Non-canonical labels trigger @custom:reverts PopError.
function stripDigits(string calldata name) external pure override returns (string memory stem);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Full label (with or without trailing digits). |
Returns
| Name | Type | Description |
|---|---|---|
stem | string | The label with trailing digits removed. |
releaseBaseName
Clears a reservation for a base-name stem.
Only a controller in the registrar's controllers set may call this, otherwise
Notes:
-
reverts: NotRegistry. Non-canonical or non-stem labels trigger
-
emits: BaseNameReleased once the slot is cleared.
function releaseBaseName(string calldata stem) external override onlyRegistry;
Parameters
| Name | Type | Description |
|---|---|---|
stem | string | The base label whose reservation should be cleared (no trailing digits). |
releaseReservationForReclaim
Clears a reservation when the slot owner matches expectedOwner, allowing any
registrar-authorised controller (not only the stamping one) to release the slot.
Narrower than @custom:function releaseBaseName: callers must prove they know the
slot owner, so cross-controller release is gated on a positive match rather than on
caller identity. Intended for the public registrar controller's reclaim path, where
a prior occupant has handed the name back to escrow and the new registrant needs
the cross-flow guard cleared regardless of which controller originally stamped it.
Only a registrar-authorised controller may call this (@custom:reverts NotRegistry).
Non-canonical or non-stem labels trigger @custom:reverts PopError. A live reservation
whose owner does not match expectedOwner triggers @custom:reverts PopError; expired
reservations are cleared regardless. Emits @custom:emits BaseNameReleased.
function releaseReservationForReclaim(
string calldata stem,
address expectedOwner
)
external
override
onlyRegistry;
Parameters
| Name | Type | Description |
|---|---|---|
stem | string | The base label whose reservation should be cleared (no trailing digits). |
expectedOwner | address | The address the caller expects to be the current reservation owner. |
_writeReservation
Internal single-source-of-truth writer for stem reservations.
Routes both @custom:function reserveBaseName and @custom:function reserveBaseNameForPop
through one path so the cross-user collision semantics stay identical: a live slot held
by a different user @custom:reverts PopError, and any other case writes a fresh expiry
and emits @custom:emits BaseNameReserved. Same-owner re-reservations refresh the expiry
to block.timestamp + MAX_RESERVATION_TIME. Callers are responsible for validating
stem is canonical and stem-shaped (no trailing digits); this helper does no input
validation of its own so each public entry can layer additional eligibility checks.
function _writeReservation(string calldata stem, address userAddress) internal;
Contents
- DotnsPopController
- DotnsRegistrar
- DotnsRegistrarController
- IDotnsController
- IDotnsPopController
- IDotnsRegistrar
- IDotnsRegistrarController
- RootGatewayDispatcher
DotnsPopController
Inherits: Initializable, UUPSUpgradeable, OwnableUpgradeable, ERC165Upgradeable, IDotnsPopController
Title: DotnsPopController
Dedicated PoP controller orchestrating lite-person and full-person username issuance on behalf of the PoP gateway pallet.
Lives behind its own UUPS proxy with its own storage. Registered on DotnsRegistrar
via addController, which is how multiple controllers coexist on the same registrar
without interfering with each other.
Enforcement:
Personhood is attested off-chain by the gateway pallet before the call reaches this
contract, so the on-chain personhood precompile is not re-queried on the gateway path.
Every base-label mint path still calls @custom:function IPopRules.classifyName to reject
governance-reserved labels (@custom:reverts InvalidBaseLabel on the base path,
Notes:
-
reverts: InvalidLiteLabel on the lite path). The lite leg accepts any two-digit lite label whose stem is not governance-reserved, regardless of stem length. Native-token pricing is bypassed entirely; the gateway pays no rent. Decoupling: This contract does not import or call
IDotnsRegistrarController. The public commit-reveal controller is equally unaware of this one. Cross-flow collision handling relies on two distinct properties, neither of which requires the two controllers to know about each other: (1) Lite-person labels (NAMEXX) share the public namespace: they are just DNS labels with exactly two trailing digits. First-to-mint wins at the ERC721 layer, so a lite-user and a public registrant cannot hold the same flat label simultaneously. Keeping one namespace removes the ambiguity downstream tooling (dotli, dweb) would see with a separate separator form. (2) Base-name reservations are synchronised intoIPopRules. The head of this controller's reservation queue is written throughIPopRules.reserveBaseNameForPopon every head transition; the slot is cleared throughIPopRules.releaseBaseNamewhen the queue empties (claim, final relinquish, final expiry). The public commit-reveal controller routes throughIPopRules.priceWithCheck, which rejects any registration targeting a base-name stem reserved for another user, so the public flow respects gateway reservations without ever importing this contract. PopRules is the single cross-flow authority; the queue here is the intra-PoP ordering layer on top of it. Shared primitives: labelhash / namehash via @custom:contract LabelUtils; the mint + forward-registry + store-write triad via @custom:contract RegistrationUtils; chat-key and lite-to-full link persistence via -
contract: IDotnsPopResolver. Keeping per-name records on the resolver preserves the "Store = labels only" invariant.
-
security-contact: admin@parity.io
Constants
MAX_RESERVATION_QUEUE
Upper bound for the number of simultaneously queued reservations per label.
Keeps expireReservation gas bounded.
uint16 public constant MAX_RESERVATION_QUEUE = 64
MIN_RESERVATION_DURATION
Minimum value accepted by @custom:function setReservationDuration.
Prevents owner misconfiguration from instantly expiring every live queue and pending-claim entry. The actual production duration is governance-tuned higher.
uint64 public constant MIN_RESERVATION_DURATION = 1 hours
CHAT_KEY_LENGTH
Required byte length for a non-empty chat key.
Mirrors @custom:contract IDotnsPopResolver InvalidChatKeyLength so the controller
can fail closed before the mint instead of bubbling the resolver's revert after partial
state has been committed.
uint256 private constant CHAT_KEY_LENGTH = 65
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
| Name | Type | Description |
|---|---|---|
params | LiteRegistration | Registration 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
| Name | Type | Description |
|---|---|---|
payload | bytes | abi.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
reservedBaseLabelis 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
| Name | Type | Description |
|---|---|---|
params | BaseReservation | Reservation 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
| Name | Type | Description |
|---|---|---|
payload | bytes | abi.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
| Name | Type | Description |
|---|---|---|
params | BaseNameReservation | Reservation 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
| Name | Type | Description |
|---|---|---|
payload | bytes | abi.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
LiteUsernamebranch validates the lite label'sNAMEXXshape (otherwise @custom:reverts InvalidLiteLabel), requires the registrant to own the lite token (otherwise @custom:reverts LiteLabelNotOwnedByUser), reads the lite node's chat key from the resolver and copies it across; if the lite node carries no chat key the inherited value is empty and the full node's chat-key write is silently skipped (theLiteToFullLinkedevent still fires). 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
| Name | Type | Description |
|---|---|---|
params | FullRegistration | Registration request; see @custom:struct FullRegistration. |
expireReservation
Permissionlessly removes expired entries from the head of a reservation queue.
Permissionless on purpose: anyone (typically a UI or a bot) can poke a stale queue
so the next live head takes over without waiting for the next gateway call. Validates
the DNS-label shape of reservedBaseLabel (otherwise @custom:reverts InvalidBaseLabel)
and 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
| Name | Type | Description |
|---|---|---|
user | address | Account 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
| Name | Type | Description |
|---|---|---|
user | address | Address 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
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | Keccak-256 of the base label whose queue is being read. |
Returns
| Name | Type | Description |
|---|---|---|
head | uint64 | Index of the live queue head. |
tail | uint64 | Index one past the last queued entry. |
reservationEntry
Returns the queue entry at index for labelhash.
Sparse storage: a zero entryOwner means the slot was relinquished, expired and
reaped, or never written. Callers pair this with @custom:function reservationMeta to walk
the live window [head, tail).
function reservationEntry(
bytes32 labelhash,
uint64 index
)
external
view
override
returns (address entryOwner, uint64 joinedAt);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | Keccak-256 of the base label whose queue is being read. |
index | uint64 | Queue index to look up. |
Returns
| Name | Type | Description |
|---|---|---|
entryOwner | address | Owner of the slot (zero if empty/relinquished). |
joinedAt | uint64 | Timestamp the entry was enqueued (only meaningful when entryOwner != address(0)). |
userReservation
Returns user's current reservation pointer.
A zero labelhash on the returned struct means the user holds no reservation;
index is meaningful only when labelhash is non-zero.
function userReservation(address user)
external
view
override
returns (UserReservation memory reservation);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Account whose reservation pointer is being read. |
Returns
| Name | Type | Description |
|---|---|---|
reservation | UserReservation | Per-user reservation pointer; see @custom:struct UserReservation. |
pendingClaims
Returns 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
| Name | Type | Description |
|---|---|---|
user | address | Account whose pending claims are being read. |
Returns
| Name | Type | Description |
|---|---|---|
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
| Name | Type | Description |
|---|---|---|
count | uint256 | Number of users currently holding a pending claim. |
pendingClaimUsers
Returns a paginated slice of users with at least one live pending claim.
Pair with @custom:function pendingClaims to read each user's stashed entries.
Ordering is not chronological; callers MUST NOT assume mintedAt is monotonic
across the slice. Returns an empty array when offset is past the live count.
function pendingClaimUsers(
uint256 offset,
uint256 limit
)
external
view
override
returns (address[] memory users);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
users | address[] | Slice of users currently holding a pending claim. |
supportsInterface
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC165Upgradeable, IERC165)
returns (bool);
version
Returns implementation version.
function version() external pure virtual returns (string memory versionString);
Returns
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
_completeGatewayRegistration
Mints a name, wires forward registry, persists PoP-flow records (chat key,
lite link) on the PoP resolver, and either writes the label into the owner's
existing LabelStore or stashes a pending claim when the owner has none yet.
The mint + forward-registry pair is delegated to
Notes:
-
function: RegistrationUtils.registerAndStore so this flow and the public commit-reveal flow share exactly one implementation of that sequence. The label is passed empty so the registrar does not deploy a
LabelStore; substrate Root cannot run theLabelStoreconstructor underpallet-revive. PoP-flow per-name records (chat key, lite link) are persisted eagerly on @custom:contract IDotnsPopResolver here, before the label is written, so the resolver carries the full identity record from mint time regardless of whether the owner already has aLabelStore. The Store stays labels-only. Warm path emits @custom:emits NameRegistered immediately; the cold path emits @custom:emits PendingClaimStashed at mint and defers -
emits: NameRegistered to @custom:function 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
| Name | Type | Description |
|---|---|---|
store | address | Owner's LabelStore proxy. |
node | bytes32 | namehash(labelhash) for the entry. |
label | string | Bare DNS label (no TLD); the TLD is appended on write. |
_stashPendingClaim
Appends a deferred binding for user and adds them to the enumeration set.
The Root gateway origin cannot deploy the user's LabelStore, so deferred names pile
up in _pendingClaimQueue until a signed-origin @custom:function 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
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
| Name | Type | Description |
|---|---|---|
id | uint256 | |
owner | address | |
label | string | The human-readable label string (e.g. "alice"). |
labelOf
Returns the human-readable label a token was registered with.
Canonical state source for the label string; any client that holds a node or tokenId can resolve the original label in one view call without scanning registration events. Returns the empty string when the token does not exist.
function labelOf(uint256 tokenId) external view override returns (string memory);
quoteTransferFee
Quotes the additional native fee required to transfer a token to to.
Returns the 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
tokenIdwith
function quoteTransferFee(
uint256 tokenId,
address to
)
external
view
override
returns (uint256 requiredFee);
transferFrom
Subject to the same fee-on-transfer gate as the safe overloads; reverts with
Note:
reverts: TransferFeeRequired when the recipient owes a non-zero 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
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
exists
Returns whether a given token id has been minted.
function exists(uint256 tokenId) external view override returns (bool tokenExists);
_exists
Checks whether a token ID exists.
function _exists(uint256 tokenId) internal view returns (bool);
_onlyController
Internal function to check for controller access.
function _onlyController() internal view;
_update
Transfers tokenId from its current owner to to, or alternatively mints (or burns) if
the current owner
(or to) is the zero address. Returns the owner of the tokenId before the update.
The auth argument is optional. If the value passed is non 0, then this function will check
that
auth is either the owner of the token, or approved to operate on the token (by the owner).
Emits a {Transfer} event.
NOTE: If overriding this function in a way that tracks balances, see also {_increaseBalance}.
function _update(
address to,
uint256 tokenId,
address auth
)
internal
override
returns (address from);
_syncRecipientStore
Mirrors the sender's label entry into the recipient's LabelStore.
function _syncRecipientStore(
IStoreFactory factory,
address to,
address from,
uint256 tokenId
)
internal;
_readLabelFor
Reads the full name (label.tld) for tokenId from holder's LabelStore using
a caller-supplied factory.
function _readLabelFor(
IStoreFactory factory,
uint256 tokenId,
address holder
)
private
view
returns (string memory fullName);
_readLabel
Reads the full name for tokenId from holder's LabelStore via fresh lookups.
Used by external view functions where caching the factory is not yet established; the hot transfer path uses @custom:function _readLabelFor with a cached factory.
function _readLabel(
uint256 tokenId,
address holder
)
private
view
returns (string memory fullName);
_escrow
Resolves the configured name escrow address from the protocol registry.
function _escrow() private view returns (address escrow);
_popRules
Resolves the configured PoP rules contract from the protocol registry.
function _popRules() private view returns (IPopRules rules);
_storeFactory
Resolves the configured store factory from the protocol registry.
function _storeFactory() private view returns (IStoreFactory factory);
_writeOwnerLabel
Writes the canonical full name into owner's LabelStore keyed by
bytes32(tokenId).
Caller (@custom:function register) is responsible for short-circuiting on empty label; the factory is a protocol-critical dependency and is assumed non-zero (a zero return from the registry would have already broken every other call site).
function _writeOwnerLabel(address owner, uint256 tokenId, string calldata label) private;
_quoteTransferFee
Quotes the friction fee required for a transfer.
Required fee is the 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
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;
maxAgemust exceedminAge(otherwise -
reverts: MaxCommitmentAgeTooLow) and must stay within
MAX_ALLOWED_COMMITMENT_AGE(otherwise @custom:reverts MaxCommitmentAgeTooHigh) before wiring the protocol registry.
function initialize(
IDotnsProtocolRegistry registry,
uint256 minAge,
uint256 maxAge
)
external
initializer;
available
Returns whether a label is available for registration.
Validates the canonical DNS-label shape (otherwise @custom:reverts InvalidLabel) and rejects labels below the minimum-length policy with
Note: reverts: LabelTooShort before checking ERC721 availability on the registrar.
function available(string calldata label) public view override returns (bool);
makeCommitment
Computes the commitment hash for a registration.
Uses abi.encode so the variable-width label is length-prefixed and the boundary
between label and the fixed-width owner, secret, 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
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
_onlyWhiteListedOrOwner
Internal check enforcing whitelist-or-owner access.
function _onlyWhiteListedOrOwner() internal view;
_isSupportedRole
function _isSupportedRole(bytes32 role) internal view override returns (bool supported);
_authorizeUpgrade
Function that should revert when msg.sender is not authorized to upgrade the contract.
Called by
{upgradeToAndCall}.
Normally, this function will use an xref:access.adoc[access control] modifier such as
{Ownable-onlyOwner}.
function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;
IDotnsController
Inherits: IERC165
Title: IDotnsController
Baseline interface implemented by every controller authorised on DotnsRegistrar.
Marker interface that types DotnsRegistrar.controllers so every authorised caller
(public commit-reveal, PoP gateway, any future privileged flow) fits the same mapping and
the same addController / removeController signatures without forcing a common call
surface. Extending @custom:contract IERC165 lets the registrar (or any observer) runtime-check
which concrete controller interface a given address implements.
Note: security-contact: admin@parity.io
IDotnsPopController
Inherits: IDotnsController
Title: IDotnsPopController
Interface for the dedicated PoP controller orchestrating lite-person and full-person username issuance on behalf of the PoP gateway pallet.
Deliberately disjoint from @custom:contract IDotnsRegistrarController. The two
controllers coexist on @custom:contract DotnsRegistrar via its multi-controller affordance
and neither imports the other. Collision handling reduces to the registrar's ERC721
availability check (first-to-mint wins). Reservation queuing for reservedBaseLabel is
an intra-PoP coordination mechanism only; it does not block public registrations.
Label formats:
Lite-person usernames (first argument to @custom:function reserveBaseName and the
liteLabel of a LinkKind.LiteUsername link) are DNS labels with exactly two
trailing digits (e.g. alice42) per @custom:function StringUtils.isLitePersonLabel.
The gateway strips any separator before calling so the on-chain label is flat.
Full-person usernames (the label of @custom:function registerBaseName and the
optional reservedBaseLabel of @custom:function reserveBaseName) follow the
DNS-label rules enforced by @custom:function StringUtils.isSingleLabel (e.g.
alice). Lite and public registrations share one namespace; first-to-mint wins at
the ERC721 layer. Cross-flow priority on the stripped base stem is arbitrated by
Notes:
-
function: IPopRules.reserveBaseNameForPop.
-
security-contact: admin@parity.io
Functions
reserveBaseName
Registers a lite-person username on behalf of the supplied user and optionally enqueues a reservation for a base name they intend to claim as a full person later.
Callable only 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
reservedBaseLabelis 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
| Name | Type | Description |
|---|---|---|
params | BaseReservation | Reservation 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.decodeignores trailing bytes past the encoded struct, so off-chain encoders MUST NOT assume strict length validation.
function reserveBaseName(bytes calldata payload) external;
Parameters
| Name | Type | Description |
|---|---|---|
payload | bytes | abi.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
| Name | Type | Description |
|---|---|---|
params | BaseNameReservation | Reservation 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
| Name | Type | Description |
|---|---|---|
params | LiteRegistration | Registration 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.decodeignores 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
| Name | Type | Description |
|---|---|---|
payload | bytes | abi.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
LiteUsernamebranch validates the lite label'sNAMEXXshape (otherwise @custom:reverts InvalidLiteLabel), requires the registrant to own the lite token (otherwise @custom:reverts LiteLabelNotOwnedByUser), reads the lite node's chat key from the resolver and copies it across; if the lite node carries no chat key the inherited value is empty and the full node's chat-key write is silently skipped (theLiteToFullLinkedevent still fires). 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
| Name | Type | Description |
|---|---|---|
params | FullRegistration | Registration 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
| Name | Type | Description |
|---|---|---|
payload | bytes | abi.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
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | Keccak-256 of the base label whose queue is being read. |
Returns
| Name | Type | Description |
|---|---|---|
head | uint64 | Index of the live queue head. |
tail | uint64 | Index one past the last queued entry. |
reservationEntry
Returns the queue entry at index for labelhash.
Sparse storage: a zero entryOwner means the slot was relinquished, expired and
reaped, or never written. Callers pair this with @custom:function reservationMeta to walk
the live window [head, tail).
function reservationEntry(
bytes32 labelhash,
uint64 index
)
external
view
returns (address entryOwner, uint64 joinedAt);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | Keccak-256 of the base label whose queue is being read. |
index | uint64 | Queue index to look up. |
Returns
| Name | Type | Description |
|---|---|---|
entryOwner | address | Owner of the slot (zero if empty/relinquished). |
joinedAt | uint64 | Timestamp the entry was enqueued (only meaningful when entryOwner != address(0)). |
userReservation
Returns user's current reservation pointer.
A zero labelhash on the returned struct means the user holds no reservation;
index is meaningful only when labelhash is non-zero.
function userReservation(address user)
external
view
returns (UserReservation memory reservation);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Account whose reservation pointer is being read. |
Returns
| Name | Type | Description |
|---|---|---|
reservation | UserReservation | Per-user reservation pointer; see @custom:struct UserReservation. |
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
| Name | Type | Description |
|---|---|---|
user | address | Account 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
| Name | Type | Description |
|---|---|---|
user | address | Address 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
| Name | Type | Description |
|---|---|---|
user | address | Account whose pending claims are being read. |
Returns
| Name | Type | Description |
|---|---|---|
claims | PendingClaim[] | 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
| Name | Type | Description |
|---|---|---|
count | uint256 | Number of users currently holding a pending claim. |
pendingClaimUsers
Returns a paginated slice of users with at least one live pending claim.
Pair with @custom:function pendingClaims to read each user's stashed entries.
Ordering is not chronological; callers MUST NOT assume mintedAt is monotonic
across the slice. Returns an empty array when offset is past the live count.
function pendingClaimUsers(
uint256 offset,
uint256 limit
)
external
view
returns (address[] memory users);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
users | address[] | Slice of users currently holding a pending claim. |
Events
LiteNameReserved
Emitted when a lite-person username is registered via the PoP gateway.
event LiteNameReserved(bytes32 indexed labelhash, address indexed user, string label);
BaseNameClaimed
Emitted when a full-person username is claimed out of an existing reservation.
event BaseNameClaimed(bytes32 indexed labelhash, address indexed user, string label);
StandaloneNameRegistered
Emitted when a standalone full-person username is registered via the PoP gateway.
event StandaloneNameRegistered(bytes32 indexed labelhash, address indexed user, string label);
ReservationQueued
Emitted when a reservation entry is added to the queue for a base name.
event ReservationQueued(
bytes32 indexed reservedLabelhash, address indexed user, uint64 position
);
Parameters
| Name | Type | Description |
|---|---|---|
reservedLabelhash | bytes32 | |
user | address | |
position | uint64 | Position in the queue at the time of joining (0 = active holder). |
ReservationExpired
Emitted when a reservation entry is removed due to expiry.
event ReservationExpired(bytes32 indexed reservedLabelhash, address indexed user);
ReservationRelinquished
Emitted when a user voluntarily relinquishes their reservation.
event ReservationRelinquished(bytes32 indexed reservedLabelhash, address indexed user);
LiteToFullLinked
Emitted when a full-person username is linked to a lite-person username.
event LiteToFullLinked(bytes32 indexed fullLabelhash, bytes32 indexed liteLabelhash);
ReservationDurationSet
Emitted when the reservation duration is updated.
event ReservationDurationSet(uint64 duration);
NameRegistered
Emitted when a name is successfully registered via the PoP controller.
event NameRegistered(
string indexed label, bytes32 indexed labelhash, address indexed owner, address store
);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | |
labelhash | bytes32 | |
owner | address | |
store | address | The Store instance used to persist the immutable registration record. |
PendingClaimStashed
Emitted when a gateway-path mint defers its LabelStore write into the
pending-claim mapping because the user has no store yet.
event PendingClaimStashed(address indexed user, bytes32 indexed labelhash, string label);
PendingClaimSettled
Emitted when a 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
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | Base-label hash whose queue head changed. |
newHead | address | Address 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
| Name | Type | Description |
|---|---|---|
caller | address | Immediate 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
| Name | Type | Description |
|---|---|---|
length | uint256 | Caller-supplied chat key length, in bytes. |
NoActiveReservation
Thrown when a user tries to claim or relinquish a reservation that they do not hold.
error NoActiveReservation(address user);
QueueFull
Thrown when a reservation queue has reached its capacity.
error QueueFull(bytes32 labelhash);
AlreadyReserved
Thrown when attempting to enqueue a user who already has an active reservation.
error AlreadyReserved(address user, bytes32 labelhash);
NotHolder
Thrown when someone tries to mint a base label in standalone mode while another user holds the live head-of-queue reservation.
error NotHolder(address user, bytes32 labelhash);
NoPendingClaim
Thrown when @custom:function claimLabelStore is called by a user with no recorded pending-claim entries.
error NoPendingClaim(address user);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | Caller 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
| Name | Type | Description |
|---|---|---|
user | address | Address 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
| Name | Type | Description |
|---|---|---|
user | address | Registrant supplied by the gateway. |
liteLabelhash | bytes32 | Lite label whose ownership did not match. |
ReservationDurationTooLow
Thrown when @custom:function setReservationDuration is called with a value below the protocol minimum.
error ReservationDurationTooLow(uint64 duration);
Parameters
| Name | Type | Description |
|---|---|---|
duration | uint64 | Caller-supplied duration, in seconds. |
Structs
Link
Tagged union selecting the chat-key source for a full-person registration.
struct Link {
LinkKind kind;
string liteLabel;
bytes chatKey;
}
Properties
| Name | Type | Description |
|---|---|---|
kind | LinkKind | |
liteLabel | string | Lite-person NAMEXX label (only read when kind == LiteUsername). |
chatKey | bytes | Chat key bytes (only read when kind == None). |
UserReservation
Per-user reservation pointer: which queue the user sits in and where.
struct UserReservation {
bytes32 labelhash;
uint64 index;
}
Properties
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | Non-zero when the user holds a live reservation; zero otherwise. |
index | uint64 | Monotonic queue index, meaningful only when labelhash is non-zero. |
ReservationEntry
Reservation queue entry: a user and the timestamp they joined the queue.
Packs into a single storage slot (20 + 8 bytes).
struct ReservationEntry {
address owner;
uint64 joinedAt;
}
ReservationQueueMeta
Metadata describing the occupied range of a reservation queue.
Uses monotonically increasing indices. Active entries occupy [head, tail);
length = tail - head. Slots past head are deleted as the head advances so
garbage never accumulates.
struct ReservationQueueMeta {
uint64 head;
uint64 tail;
}
PendingClaim
Deferred per-user binding of a freshly minted name to its LabelStore.
Recorded by the gateway path when the user has no LabelStore. The 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
| Name | Type | Description |
|---|---|---|
label | string | Bare DNS label (no TLD); the TLD is appended at settlement time. |
mintedAt | uint64 | Timestamp of the originating mint. |
LiteRegistration
Lite-person registration payload.
Single struct so the gateway can ABI-encode one tuple as the cross-chain payload
and the contract decodes it directly out of msg.data. All fields are required;
chatKey may be empty bytes to skip the resolver write.
struct LiteRegistration {
string liteLabel;
address user;
bytes chatKey;
}
Properties
| Name | Type | Description |
|---|---|---|
liteLabel | string | Lite-person NAMEXX label being minted. |
user | address | Beneficiary account on this chain. |
chatKey | bytes | Chat-key bytes persisted on the PoP resolver. Empty leaves the slot unset. |
BaseReservation
Lite-person registration combined with an optional base-name reservation.
BaseReservation is a @custom:struct LiteRegistration plus a base-label reservation
slot, expressed as composition rather than duplicated fields so internal helpers can
consume the lite leg via params.lite without unpacking. The lite leg always runs;
the reservation leg only runs when reservedBaseLabel is non-empty.
struct BaseReservation {
LiteRegistration lite;
string reservedBaseLabel;
}
Properties
| Name | Type | Description |
|---|---|---|
lite | LiteRegistration | Lite-person registration request; see LiteRegistration. |
reservedBaseLabel | string | Base label to enqueue for a later full-person claim. Empty string skips the reservation leg. |
BaseNameReservation
Base-name reservation payload for the split gateway flow.
This is the reservation-only primitive. The lite username mint is handled by
Notes:
-
function: reserveLiteName, and LabelStore settlement is handled by
-
function: claimLabelStoreFor or the user fallback @custom:function claimLabelStore.
struct BaseNameReservation {
address user;
string reservedBaseLabel;
}
Properties
| Name | Type | Description |
|---|---|---|
user | address | Beneficiary account that will hold the reservation. |
reservedBaseLabel | string | Base label to enqueue for a later full-person claim. |
FullRegistration
Full-person registration payload.
struct FullRegistration {
string label;
address user;
Link link;
}
Properties
| Name | Type | Description |
|---|---|---|
label | string | Base DNS label being minted. |
user | address | Beneficiary account on this chain. |
link | Link | Chat-key source for the new entry; see @custom:struct Link. |
Enums
LinkKind
Discriminant for the Link union supplied to registerBaseName.
Selects the chat-key source for the full-person username. Orthogonal to whether
the registration is a claim or standalone; that is derived from on-chain reservation
state. None means the caller supplies a fresh chat key in link.chatKey.
LiteUsername means the full-person username is linked to a prior lite-person
username (link.liteLabel) and inherits its chat key.
enum LinkKind {
None,
LiteUsername
}
IDotnsRegistrar
Inherits: IERC721
Title: Dotns Registrar
ERC721-backed ownership for DotNS names with controller-gated registration.
Intentionally minimal and policy-free. Provides ERC721 ownership for registered name token IDs and controller-gated registration; pricing, PoP enforcement, and flow-specific policy live in the controllers.
Note: security-contact: admin@parity.io
Functions
available
Returns whether a registration call may proceed for id.
Signals two distinct paths to the controller. Returns true when the owner slot is
empty (a fresh @custom:function register call may mint) 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
| Name | Type | Description |
|---|---|---|
id | uint256 | |
owner | address | |
label | string | The human-readable label string (e.g. "alice"). |
exists
Returns whether a given token id has been minted.
function exists(uint256 tokenId) external view returns (bool tokenExists);
addController
Adds an authorised controller.
Typed against the baseline IDotnsController (not a concrete subtype) so a single
authorisation surface accepts every controller flavour (commit-reveal, PoP gateway,
future variants) without per-flavour setters. Owner-gated (otherwise
Note: reverts: OwnableUnauthorizedAccount); emits @custom:emits ControllerAdded on success.
function addController(IDotnsController controller) external;
removeController
Removes an authorised controller.
Mirrors the @custom:function addController baseline-typed signature so any registered controller can be revoked through the same entry point. Owner-gated (otherwise
Note: reverts: OwnableUnauthorizedAccount); emits @custom:emits ControllerRemoved on success.
function removeController(IDotnsController controller) external;
controllers
Returns whether controller is currently authorised to call
Note: function: register.
function controllers(IDotnsController controller) external view returns (bool authorised);
Parameters
| Name | Type | Description |
|---|---|---|
controller | IDotnsController | Candidate controller. |
Returns
| Name | Type | Description |
|---|---|---|
authorised | bool | True when controller was added via @custom:function addController and has not been removed. |
labelOf
Returns the human-readable label a token was registered with.
Canonical state source for the label string; any client that holds a node or tokenId can resolve the original label in one view call without scanning registration events. Returns the empty string when the token does not exist.
function labelOf(uint256 tokenId) external view returns (string memory label);
quoteTransferFee
Quotes the additional native fee required to transfer a token to to.
Returns the 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
towith -
reverts: ERC721InvalidReceiver, an unminted
tokenIdwith -
reverts: ERC721NonexistentToken via the underlying
ownerOf, and requires the protocol registry to have an escrow configured (otherwise -
reverts: EscrowNotConfigured). Returns zero when the protocol registry has no
STORE_FACTORYconfigured because no label-derived price is reachable.
function quoteTransferFee(
uint256 tokenId,
address to
)
external
view
returns (uint256 requiredFee);
safeTransferFrom
The registrar's _update hook consults @custom:function PopRules.transferFloor
to compute the required 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
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 runspriceWithCheck(personhood
- reservation gate) and routes the charge to a refundable escrow deposit owned by
registration.owner. The cross-payer path skips the personhood revert inpriceWithCheckbut applies it directly via @custom:reverts OwnerStatusInsufficient when the owner's recorded tier does not meet the label's required tier, and still rejects governance-reserved labels with @custom:reverts GovernanceReserved and live cross-user stem reservations with @custom:reverts NameReserved. The total charge on the cross-payer path is the greater of the owner-side registration price and the owner-tiertransferFloorfriction (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 tomsg.senderinline and, on failure, credited to the escrow's pull-payment ledger so contract receivers cannot block registration. Emits @custom:emits OverpaymentRefunded on the inline branch, the escrow's own @custom:emits OverpaymentRefunded on the pull fallback, and @custom:emits NameRegistered on success.
function register(Registration calldata registration) external payable;
registerReserved
Registers a name after the commitment delay.
Whitelisted issuance path used to seed reserved labels at zero base cost: skips the PoP price check and the escrow deposit, but reuses the same commit-reveal pipeline so the same anti-front-running guarantees apply. Restricted to whitelisted callers and the owner (otherwise @custom:reverts NotWhiteListedOrOwner). Validates the label shape (otherwise @custom:reverts InvalidLabel) and ERC721 availability (otherwise
Notes:
-
reverts: NameNotAvailable), then consumes the prior commitment, which fails with
-
reverts: CommitmentNotFound, @custom:reverts CommitmentTooNew, or
-
reverts: CommitmentTooOld under the same conditions as @custom:function register. Emits
-
emits: NameRegistered on success.
function registerReserved(Registration calldata registration) external;
isWhiteListed
Checks if the given address is whitelisted to call registerReserved.
function isWhiteListed(address who) external view returns (bool isWhiteListed);
whiteListAddress
Adds or removes an address from the whitelist for registerReserved.
Callable by the owner or an account holding DotnsConstants.WHITELIST_OPERATOR_ROLE;
any other caller reverts with @custom:reverts IDotnsRoleManager.NotRoleOrOwner. Emits
Note: emits: WhiteListed on success.
function whiteListAddress(address who, bool whiteListStatus) external;
Events
NameCommitted
Emitted when a commitment is submitted.
event NameCommitted(bytes32 indexed commitment);
NameRegistered
Emitted when a name is successfully registered.
event NameRegistered(
string indexed label,
bytes32 indexed labelhash,
address indexed owner,
uint256 baseCost,
address store
);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | |
labelhash | bytes32 | |
owner | address | |
baseCost | uint256 | The price returned by the oracle for this registration. |
store | address | The Store instance used to persist an immutable registration record. |
WhiteListed
Emitted when an address is added to or removed from the whitelist.
event WhiteListed(address indexed who, bool indexed whiteListStatus);
OverpaymentRefunded
Emitted when overpayment is refunded to the payer at registration entry.
event OverpaymentRefunded(address indexed payer, uint256 amount);
Errors
NotWhiteListedOrOwner
Thrown when the caller is not whitelisted or the owner.
error NotWhiteListedOrOwner(address caller);
UnexpiredCommitmentExists
Thrown when an unexpired commitment already exists.
error UnexpiredCommitmentExists(bytes32 commitment);
CommitmentNotFound
Thrown when revealing a commitment that does not exist.
error CommitmentNotFound(bytes32 commitment);
CommitmentTooNew
Thrown when a commitment is revealed before the minimum age.
error CommitmentTooNew(bytes32 commitment, uint256 minTime, uint256 currentTime);
CommitmentTooOld
Thrown when a commitment has expired.
error CommitmentTooOld(bytes32 commitment, uint256 maxTime, uint256 currentTime);
NameNotAvailable
Thrown when attempting to register an unavailable name.
error NameNotAvailable(string label);
LabelTooShort
Thrown when a label is below the minimum-length policy.
Distinct from @custom:reverts NameNotAvailable so off-chain consumers can tell a too-short label apart from a name that is already minted.
error LabelTooShort(string label);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Caller-supplied label that failed the minimum-length policy. |
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
| Name | Type | Description |
|---|---|---|
label | string | Caller-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
| Name | Type | Description |
|---|---|---|
label | string | Label whose tier requirement was unmet. |
userStatus | IPopRules.PopStatus | Owner's recorded tier. |
required | IPopRules.PopStatus | Required 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
| Name | Type | Description |
|---|---|---|
label | string | Label being registered (e.g. "alice"). |
owner | address | Beneficiary the registered name is minted to. |
secret | bytes32 | Caller-chosen entropy that hides the registration intent in the commit hash; revealed verbatim at registration time. |
reserved | bool | True when the registration flows through the whitelisted reserved pipeline (registerReserved); false for the standard public flow (register). |
RootGatewayDispatcher
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
| Name | Type | Description |
|---|---|---|
target_ | address | Address 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
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
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
_authorizeUpgrade
Function that should revert when msg.sender is not authorized to upgrade the contract.
Called by
{upgradeToAndCall}.
Normally, this function will use an xref:access.adoc[access control] modifier such as
{Ownable-onlyOwner}.
function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;
DotnsRegistry
Inherits: Initializable, UUPSUpgradeable, OwnableUpgradeable, IDotnsRegistry
Title: Dotns Registry
Author: Parity
Upgradeable on-chain registry for hierarchical name ownership and resolution.
Tokenised second-level nodes store owner == address(0) as a sentinel and defer to
IDotnsRegistrar.ownerOf; subnodes carry an explicit owner address in records.
Note: security-contact: admin@parity.io
State Variables
records
Mapping of node identifiers to records.
mapping(bytes32 node => Record record) private records
protocolRegistry
Protocol-level address registry for all DotNS contracts.
IDotnsProtocolRegistry public protocolRegistry
__gap
uint256[50] private __gap
Functions
authorised
Restricts access to the current owner of node.
modifier authorised(bytes32 node) ;
onlyRegistrarController
Restricts access to the configured registrar controller.
modifier onlyRegistrarController() ;
constructor
Note: oz-upgrades-unsafe-allow: constructor
constructor() ;
initialize
Initialises the registry.
Callable exactly once via Initializable, otherwise
Notes:
-
reverts: InvalidInitialization.
registrymust be non-zero, otherwise -
reverts: NotAllowed.
function initialize(IDotnsProtocolRegistry registry) external initializer;
Parameters
| Name | Type | Description |
|---|---|---|
registry | IDotnsProtocolRegistry | Protocol-level address registry used to resolve sibling contracts. |
setSubnodeOwner
Creates or reassigns a subnode and assigns its owner.
Callable only by the current owner of record.parentNode, otherwise
Note: reverts: NotAuthorised. The new owner address must be non-zero, otherwise
function setSubnodeOwner(SubnodeRecord calldata record)
external
override
authorised(record.parentNode)
returns (bytes32 subnode);
setOwner
Creates or resets a node record for a tokenised base registration.
Restricted to the registrar's controllers, otherwise @custom:reverts NotAuthorised.
newOwner must be non-zero (otherwise @custom:reverts NotAllowed) and must match
the ERC-721 owner reported by the registrar (otherwise
Notes:
-
reverts: NotAuthorised). The function is callable both on a fresh registration and on every reclaim from escrow: each call rewrites
records[node].resolverto the protocol-registered default reverse resolver so a prior owner's resolver pointer (and the records keyed under it) cannot be inherited by the next holder. Storesowner = address(0)as a sentinel so reads delegate toIDotnsRegistrar.ownerOfand ERC-721 transfers remain authoritative. Emits -
emits: NodeTransferred on success.
function setOwner(bytes32 node, address newOwner) external override onlyRegistrarController;
setResolver
Sets or clears the resolver for a node.
Callable only by the current node owner, otherwise @custom:reverts NotAuthorised.
For tokenised nodes, authorisation falls back to ERC-721 owner / approved /
operator-for-all via the registrar. The registry does not validate
resolverAddr against any interface or code presence; off-chain consumers must
verify resolver shape before trusting reads. Emits @custom:emits NewResolver on
success.
function setResolver(bytes32 node, address newResolver) external override authorised(node);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | |
newResolver | address |
setSubnodeResolver
Sets the resolver for an existing subnode.
Callable only by the current owner of record.parentNode, otherwise
Note:
reverts: NotAuthorised. The subnode owner can still update the resolver
directly via setResolver. Both entry points emit the same NewResolver(subnode, ...) event, so the parent can silently override a subnode owner's chosen resolver:
this is the parent-sovereign hierarchy applied to resolution. Off-chain consumers
that surface trust signals to subnode owners should treat any resolver rotation as
a re-attestation prompt. record.subLabel must be a single canonical DNS label
(otherwise @custom:reverts InvalidLabel) and record.parentLabel must be a name
path whose namehash matches record.parentNode (otherwise
function setSubnodeResolver(SubnodeResolverRecord calldata record)
external
override
authorised(record.parentNode);
owner
Returns the owner of a node.
For tokenised nodes the stored owner is the zero sentinel; the implementation falls
back to IDotnsRegistrar.ownerOf(uint256(node)).
function owner(bytes32 node) external view override returns (address);
resolver
Returns the resolver of a node.
function resolver(bytes32 node) external view override returns (address);
recordExists
Returns whether a node exists.
function recordExists(bytes32 node) external view override returns (bool);
isAuthorised
Returns whether account is authorised to manage node.
For subnodes, authority is the explicit stored owner. For tokenised nodes it is the ERC-721 owner, an address approved for the token, or an operator approved for all of the owner's tokens via the registrar. This is the canonical authorisation check the registry enforces on owner-gated entry points; sibling contracts may consult it so a single registrar-level approval delegates management across the protocol. Returns false for a node that does not exist.
function isAuthorised(
bytes32 node,
address account
)
external
view
override
returns (bool authorisedFlag);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | Node identifier. |
account | address | Address whose authority is being checked. |
Returns
| Name | Type | Description |
|---|---|---|
authorisedFlag | bool | True when account may manage node. |
_writeSubnodeToStore
Writes subnode registration to the owner's LabelStore.
Keys the entry by node (full namehash) rather than labelhash so a single store
lookup yields the canonical full name without re-walking the parent chain.
function _writeSubnodeToStore(
address storeOwner,
bytes32 node,
string memory fullName
)
internal;
_parentNamehash
Computes the namehash of parentLabel rooted at the 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
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
_authorizeUpgrade
Function that should revert when msg.sender is not authorized to upgrade the contract.
Called by
{upgradeToAndCall}.
Normally, this function will use an xref:access.adoc[access control] modifier such as
{Ownable-onlyOwner}.
function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;
IDotnsProtocolRegistry
Title: IDotnsProtocolRegistry
Author: Parity
Interface for the DotNS protocol-level address registry.
Single source of truth for sibling lookups. Contracts resolve each other via well-known
bytes32 constants in DotnsConstants so an upgrade or rewire only mutates the
registry, never the consumers.
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
Title: IDotnsRegistry
Author: Parity
Minimal on-chain registry for hierarchical name ownership and resolution.
Tokenised second-level nodes are owned through the registrar (ERC-721); subnodes are
owned directly by the address stored in Record.owner.
Note: security-contact: admin@parity.io
Functions
setSubnodeOwner
Creates or reassigns a subnode and assigns its owner.
Callable only by the current owner of record.parentNode, otherwise
Notes:
-
reverts: NotAuthorised. The new owner address must be non-zero, otherwise
-
reverts: NotAllowed.
record.subLabelmust be a single canonical DNS label (otherwise @custom:reverts InvalidLabel) andrecord.parentLabelmust be a name path whose namehash matchesrecord.parentNode(otherwise -
reverts: ParentLabelMismatch). Subnodes are parent-sovereign: the current
record.parentNodeowner may reassign or rotate a subnode's resolver at any time without the prior subnode owner's consent. On reassignment the resolver pointer is reset to the protocol-registered default reverse resolver so a prior subnode owner's resolver cannot be inherited by the next holder (records on other resolver contracts are keyed by node and are not cleared by this function; downstream consumers should gate resolver reads on current ownership). Indexes the subnode under the new owner'sLabelStorekeyed by the namehashedsubnodeso off-chain consumers can enumerate names per address. Emits @custom:emits NewOwner on each successful assignment.
function setSubnodeOwner(SubnodeRecord calldata record) external returns (bytes32 subnode);
setSubnodeResolver
Sets the resolver for an existing subnode.
Callable only by the current owner of record.parentNode, otherwise
Notes:
-
reverts: NotAuthorised. The subnode owner can still update the resolver directly via
setResolver. Both entry points emit the sameNewResolver(subnode, ...)event, so the parent can silently override a subnode owner's chosen resolver: this is the parent-sovereign hierarchy applied to resolution. Off-chain consumers that surface trust signals to subnode owners should treat any resolver rotation as a re-attestation prompt.record.subLabelmust be a single canonical DNS label (otherwise @custom:reverts InvalidLabel) andrecord.parentLabelmust be a name path whose namehash matchesrecord.parentNode(otherwise -
reverts: ParentLabelMismatch). The resulting subnode must already exist, otherwise @custom:reverts NotAuthorised. Emits @custom:emits NewResolver on success.
function setSubnodeResolver(SubnodeResolverRecord calldata record) external;
setOwner
Creates or resets a node record for a tokenised base registration.
Restricted to the registrar's controllers, otherwise @custom:reverts NotAuthorised.
newOwner must be non-zero (otherwise @custom:reverts NotAllowed) and must match
the ERC-721 owner reported by the registrar (otherwise
Notes:
-
reverts: NotAuthorised). The function is callable both on a fresh registration and on every reclaim from escrow: each call rewrites
records[node].resolverto the protocol-registered default reverse resolver so a prior owner's resolver pointer (and the records keyed under it) cannot be inherited by the next holder. Storesowner = address(0)as a sentinel so reads delegate toIDotnsRegistrar.ownerOfand ERC-721 transfers remain authoritative. Emits -
emits: NodeTransferred on success.
function setOwner(bytes32 node, address newOwner) external;
setResolver
Sets or clears the resolver for a node.
Callable only by the current node owner, otherwise @custom:reverts NotAuthorised.
For tokenised nodes, authorisation falls back to ERC-721 owner / approved /
operator-for-all via the registrar. The registry does not validate
resolverAddr against any interface or code presence; off-chain consumers must
verify resolver shape before trusting reads. Emits @custom:emits NewResolver on
success.
function setResolver(bytes32 node, address resolverAddr) external;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | |
resolverAddr | address | Resolver contract address (zero clears). |
owner
Returns the owner of a node.
For tokenised nodes the stored owner is the zero sentinel; the implementation falls
back to IDotnsRegistrar.ownerOf(uint256(node)).
function owner(bytes32 node) external view returns (address);
resolver
Returns the resolver of a node.
function resolver(bytes32 node) external view returns (address);
recordExists
Returns whether a node exists.
function recordExists(bytes32 node) external view returns (bool);
isAuthorised
Returns whether account is authorised to manage node.
For subnodes, authority is the explicit stored owner. For tokenised nodes it is the ERC-721 owner, an address approved for the token, or an operator approved for all of the owner's tokens via the registrar. This is the canonical authorisation check the registry enforces on owner-gated entry points; sibling contracts may consult it so a single registrar-level approval delegates management across the protocol. Returns false for a node that does not exist.
function isAuthorised(bytes32 node, address account) external view returns (bool authorisedFlag);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | Node identifier. |
account | address | Address whose authority is being checked. |
Returns
| Name | Type | Description |
|---|---|---|
authorisedFlag | bool | True when account may manage node. |
Events
NewOwner
Emitted when a new subnode owner is set.
event NewOwner(bytes32 indexed node, bytes32 indexed label, address owner);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | Parent node. |
label | bytes32 | Labelhash of the created subnode. |
owner | address |
NodeTransferred
Emitted when ownership of a node is transferred.
event NodeTransferred(bytes32 indexed node, address owner);
NewResolver
Emitted when a resolver is set or updated.
event NewResolver(bytes32 indexed node, address resolver);
Errors
NotAllowed
Thrown when an invalid (zero) address is provided.
error NotAllowed();
NotAuthorised
Thrown when the caller is not authorised.
error NotAuthorised();
NotRegistryController
Thrown when the caller is not the registry controller.
error NotRegistryController();
NodeAlreadyOwned
Thrown when attempting to create a node that already exists.
error NodeAlreadyOwned(bytes32 node);
InvalidLabel
Thrown when a sublabel is not a canonical lowercase ASCII DNS label.
error InvalidLabel();
ParentLabelMismatch
Thrown when the supplied parent label does not match the parent node.
error ParentLabelMismatch();
Structs
SubnodeRecord
Record describing a subnode creation request.
struct SubnodeRecord {
bytes32 parentNode;
string subLabel;
string parentLabel;
address owner;
}
Properties
| Name | Type | Description |
|---|---|---|
parentNode | bytes32 | |
subLabel | string | Human readable subnode label e.g "alice". |
parentLabel | string | Canonical parent name without the .dot suffix e.g. bob or child.bob. |
owner | address | Address to assign as owner of the created subnode. |
Record
Record describing the state of a node.
struct Record {
address owner;
address resolver;
bool exists;
}
Properties
| Name | Type | Description |
|---|---|---|
owner | address | Address that owns the node, or address(0) sentinel for tokenised nodes. |
resolver | address | Address of the resolver associated with the node. |
exists | bool | Whether the node has been explicitly created. |
SubnodeResolverRecord
Record describing a subnode resolver update request.
struct SubnodeResolverRecord {
bytes32 parentNode;
string subLabel;
string parentLabel;
address resolver;
}
Properties
| Name | Type | Description |
|---|---|---|
parentNode | bytes32 | |
subLabel | string | Human-readable subnode label e.g "alice". |
parentLabel | string | Canonical parent name without .dot suffix e.g bob or child.bob. |
resolver | address | Resolver contract address (zero clears). |
Contents
- DotnsContentResolver
- DotnsPopResolver
- DotnsResolver
- DotnsReverseResolver
- IDotnsContentResolver
- IDotnsPopResolver
- IDotnsResolver
- IDotnsReverseResolver
DotnsContentResolver
Inherits: Initializable, UUPSUpgradeable, OwnableUpgradeable, ERC165Upgradeable, IDotnsContentResolver
Title: Dotns Content Resolver
Implements IDotnsContentResolver interface with content hash, text records, and
operator approvals.
Writes are gated on the registry's authorisation for the node (owner or registrar-level approval) or on a resolver-local operator the owner has approved, rather than on a privileged writer address. Content records are user-managed metadata, so write authority follows the node owner across transfers and honours the same delegates the registry recognises.
Note: security-contact: admin@parity.io
State Variables
contenthashes
Stores all content hash mappings
mapping(bytes32 node => bytes contentHash) private contenthashes
textRecords
Stores all text records
mapping(bytes32 node => mapping(string key => string value)) private textRecords
operators
Store all approval mapping
mapping(address owner => mapping(address operator => bool approved)) private operators
protocolRegistry
Protocol-level address registry for all DotNS contracts.
IDotnsProtocolRegistry public protocolRegistry
__gap
Reserved storage space to allow for layout changes in the future.
uint256[50] private __gap
Functions
constructor
Note: oz-upgrades-unsafe-allow: constructor
constructor() ;
initialize
Initialises the content resolver.
Runs once through the UUPS proxy; a repeat call reverts with
Note:
reverts: InvalidInitialization. Emits @custom:emits OwnershipTransferred when
msg.sender is recorded as the initial owner and @custom:emits Initialized once
setup completes.
function initialize(IDotnsProtocolRegistry registry) external initializer;
Parameters
| Name | Type | Description |
|---|---|---|
registry | IDotnsProtocolRegistry | Protocol-level address registry used to resolve sibling contracts. |
setContenthash
Sets the content hash for a node.
The caller must own the node in the DotNS registry or be an approved operator, otherwise @custom:reverts NotAuthorised. Content hashes are opaque bytes (e.g. an IPFS CID); the resolver stores them as-is and never interprets the payload. Emits
Note: emits: ContentHashUpdated on every successful write.
function setContenthash(bytes32 node, bytes calldata hash) external override;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node whose content hash is being set. |
hash | bytes | Opaque content hash bytes. |
contenthash
Returns the content hash associated with a node.
function contenthash(bytes32 node) external view override returns (bytes memory hash);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node to query. |
Returns
| Name | Type | Description |
|---|---|---|
hash | bytes | The stored content hash bytes, or empty if unset. |
setText
Sets a text record for a node.
The caller must own the node in the DotNS registry or be an approved operator,
otherwise @custom:reverts NotAuthorised. Text records are arbitrary key/value strings
(e.g. avatar, url, description). Emits @custom:emits TextUpdated on every
successful write.
function setText(bytes32 node, string calldata key, string calldata value) external override;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node whose text record is being set. |
key | string | Text record key (e.g., "ipfs", "avatar"). |
value | string | Text record value. |
text
Returns a text record for a node.
function text(
bytes32 node,
string calldata key
)
external
view
override
returns (string memory value);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node to query. |
key | string | Text record key. |
Returns
| Name | Type | Description |
|---|---|---|
value | string | Stored text value, or empty string if unset. |
setApprovalForAll
Enable or disable approval for a third party ("operator") to manage all of
msg.sender's nodes.
Emits @custom:emits ApprovalForAll whenever the approval flag is written, including idempotent writes that do not change the stored value.
function setApprovalForAll(address operator, bool approved) external override;
Parameters
| Name | Type | Description |
|---|---|---|
operator | address | Address to authorise or revoke. |
approved | bool | True to approve, false to revoke. |
isApprovedForAll
Query if an address is an approved operator for another address.
function isApprovedForAll(
address owner,
address operator
)
external
view
override
returns (bool);
Parameters
| Name | Type | Description |
|---|---|---|
owner | address | The owner of the nodes. |
operator | address | The address acting on behalf of the owner. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | True if operator is approved, false otherwise. |
_requireNodeOwnerOrOperator
Ensures the caller may write records for node.
Authority is granted to the node owner, to a resolver-local operator the owner has approved for all of their records, or to any address the registry deems authorised for the node. Delegating through the registry means a single registrar-level approval (ERC-721 owner / approved / operator-for-all) also confers record-write authority, while the resolver-local operator mapping remains a narrower record-only delegation that grants no power over ownership or transfers. The cheap owner and local-operator checks run before the cross-contract registry call.
function _requireNodeOwnerOrOperator(bytes32 node) internal view;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | Node identifier. |
version
Returns implementation version.
function version() external pure virtual returns (string memory versionString);
Returns
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
supportsInterface
function supportsInterface(bytes4 interfaceId) public view override returns (bool);
_authorizeUpgrade
Function that should revert when msg.sender is not authorized to upgrade the contract.
Called by
{upgradeToAndCall}.
Normally, this function will use an xref:access.adoc[access control] modifier such as
{Ownable-onlyOwner}.
function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;
DotnsPopResolver
Inherits: Initializable, UUPSUpgradeable, OwnableUpgradeable, ERC165Upgradeable, IDotnsPopResolver
Title: DotnsPopResolver
Per-node resolver holding records produced by the PoP username flow.
Writes are gated on the protocol-registered POP_CONTROLLER rather
than on node ownership. PoP records are issued by the gateway as part
of identity issuance, not curated by the holder, so authority lives
with the controller and not the user.
Note: security-contact: admin@parity.io
State Variables
protocolRegistry
Protocol-level address registry used to resolve the authorised writer.
IDotnsProtocolRegistry public protocolRegistry
_chatKeys
Stored chat-key bytes keyed by node.
mapping(bytes32 node => bytes chatKey) private _chatKeys
_liteLinks
Stored lite-person labelhash keyed by full-person node.
Forward direction (full => lite): maps a full-person node to the labelhash of the lite username it was claimed from.
mapping(bytes32 fullNode => bytes32 liteLabelhash) private _liteLinks
_fullClaims
Reverse index mapping a lite labelhash to the full-person node it was promoted to.
Written alongside _liteLinks on every claim so consumers that look
up by lite username resolve the full name without scanning events.
Zero when the lite label has never been linked to a full claim.
mapping(bytes32 liteLabelhash => bytes32 fullNode) private _fullClaims
__gap
Reserved storage space to allow for layout changes in the future.
uint256[50] private __gap
Functions
onlyPopController
Restricts writes to the address registered as POP_CONTROLLER.
modifier onlyPopController() ;
constructor
Note: oz-upgrades-unsafe-allow: constructor
constructor() ;
initialize
Initialises the PoP resolver.
Called once through the UUPS proxy; _disableInitializers on the implementation
makes direct calls revert and any repeat call on the proxy reverts with
Note:
reverts: InvalidInitialization. The registry pointer is the only storage this
setup needs because the authorised writer is resolved dynamically through
POP_CONTROLLER. Emits @custom:emits OwnershipTransferred when msg.sender is
recorded as the initial owner and @custom:emits Initialized once setup completes.
function initialize(IDotnsProtocolRegistry registry) external initializer;
Parameters
| Name | Type | Description |
|---|---|---|
registry | IDotnsProtocolRegistry | Protocol-level address registry used for writer resolution. |
setChatKey
Sets the chat key for node.
Callable only by the address registered under DotnsProtocolRegistry.POP_CONTROLLER,
otherwise @custom:reverts NotPopController. Overwrites any previous value. The payload
must be exactly 65 bytes: the uncompressed secp256k1 public key encoding (1 prefix
byte followed by the 32-byte X and 32-byte Y affine coordinates); any other length
reverts with @custom:reverts InvalidChatKeyLength. Emits @custom:emits ChatKeyUpdated
on every successful write.
function setChatKey(
bytes32 node,
bytes calldata chatKeyBytes
)
external
override
onlyPopController;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node whose chat key is being written. |
chatKeyBytes | bytes |
setLiteLink
Sets the lite-person link for a full-person node.
Callable only by the authorised PoP controller, otherwise
Notes:
-
reverts: NotPopController. Overwrites any previous link. When overwriting, the stale inverse entry is nulled so both the forward (
liteLink) and reverse (fullClaim) indices remain consistent: re-linking the samefullNodeto a newliteLabelhashclearsfullClaim(oldLite), and re-linking the sameliteLabelhashto a newfullNodeclearsliteLink(oldFull). The invariantfullClaim(liteLink(node)) == nodealways holds after the call. Emits -
emits: LiteLinkUpdated on every successful write.
function setLiteLink(
bytes32 fullNode,
bytes32 liteLabelhash
)
external
override
onlyPopController;
Parameters
| Name | Type | Description |
|---|---|---|
fullNode | bytes32 | The full-person node carrying the link. |
liteLabelhash | bytes32 | The labelhash of the linked lite-person username. |
chatKey
Returns the chat key associated with a node.
function chatKey(bytes32 node) external view override returns (bytes memory);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node to query. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bytes | chatKey The stored chat key bytes, or empty if unset. |
liteLink
Returns the lite-person labelhash linked to a full-person node.
function liteLink(bytes32 fullNode) external view override returns (bytes32);
Parameters
| Name | Type | Description |
|---|---|---|
fullNode | bytes32 | The full-person node to query. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bytes32 | liteLabelhash The linked lite-person labelhash, or zero if unset. |
fullClaim
Returns the full-person node a given lite label has claimed.
Reverse of @custom:function liteLink. Written by the same setLiteLink call so the
two directions stay in lockstep. Returns zero when the lite label
has never been linked to a full claim.
function fullClaim(bytes32 liteLabelhash) external view override returns (bytes32);
Parameters
| Name | Type | Description |
|---|---|---|
liteLabelhash | bytes32 | The labelhash of the lite-person username to query. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bytes32 | fullNode The full-person node claimed from this lite label, or zero if unset. |
version
Returns implementation version.
Bumped on every upgrade. Used by deployment scripts as a post-upgrade assertion target.
function version() external pure virtual returns (string memory versionString);
Returns
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
supportsInterface
function supportsInterface(bytes4 interfaceId) public view override returns (bool);
_onlyPopController
Internal check enforcing PoP-controller-only access.
function _onlyPopController() internal view;
_authorizeUpgrade
Function that should revert when msg.sender is not authorized to upgrade the contract.
Called by
{upgradeToAndCall}.
Normally, this function will use an xref:access.adoc[access control] modifier such as
{Ownable-onlyOwner}.
function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;
DotnsResolver
Inherits: Initializable, UUPSUpgradeable, OwnableUpgradeable, ERC165Upgradeable, IDotnsResolver
Title: Dotns Resolver
Stores forward-resolution address records for DotNS nodes
Writes are gated on node ownership in the forward registry, not on a privileged writer address. Address records describe where a name points and only the current node owner has the authority to set that target.
Note: security-contact: admin@parity.io
State Variables
protocolRegistry
Protocol-level address registry for all DotNS contracts.
IDotnsProtocolRegistry public protocolRegistry
__gap
Reserved storage space to allow for layout changes in the future.
uint256[50] private __gap
addresses
Node => resolved address.
mapping(bytes32 node => address owner) private addresses
Functions
onlyNodeOwner
Restricts access to the owner of node as recorded in the registry.
modifier onlyNodeOwner(bytes32 node) ;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | Node identifier. |
constructor
Note: oz-upgrades-unsafe-allow: constructor
constructor() ;
initialize
Initialises the resolver.
Runs once through the UUPS proxy; a repeat call reverts with
Note:
reverts: InvalidInitialization. Emits @custom:emits OwnershipTransferred when
msg.sender is recorded as the initial owner and @custom:emits Initialized once
setup completes.
function initialize(IDotnsProtocolRegistry registry) external initializer;
Parameters
| Name | Type | Description |
|---|---|---|
registry | IDotnsProtocolRegistry | Protocol-level address registry used to resolve sibling contracts. |
setAddress
Sets the resolved address for a node.
The caller must be the current owner of node in the forward registry, otherwise
Note: reverts: NotAuthorised. Emits @custom:emits AddressSet on every successful write.
function setAddress(bytes32 node, address value) external override onlyNodeOwner(node);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node identifier. |
value | address | The address to associate with the node. |
addressOf
Returns the resolved address for a node.
function addressOf(bytes32 node) external view override returns (address value);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node identifier. |
Returns
| Name | Type | Description |
|---|---|---|
value | address | The resolved address, or zero if unset. |
supportsInterface
function supportsInterface(bytes4 interfaceId) public view override returns (bool);
_onlyNodeOwner
Internal ownership check for a registry node.
Resolves the registry lazily through protocolRegistry so a registry
upgrade or rewire is picked up automatically without a resolver upgrade.
function _onlyNodeOwner(bytes32 node) internal view;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | Node identifier. |
version
Returns implementation version.
function version() external pure virtual returns (string memory versionString);
Returns
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
_authorizeUpgrade
Function that should revert when msg.sender is not authorized to upgrade the contract.
Called by
{upgradeToAndCall}.
Normally, this function will use an xref:access.adoc[access control] modifier such as
{Ownable-onlyOwner}.
function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;
DotnsReverseResolver
Inherits: Initializable, UUPSUpgradeable, OwnableUpgradeable, ERC165Upgradeable, IDotnsReverseResolver
Title: Dotns Reverse Resolver
Resolves an address to its associated .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
| Name | Type | Description |
|---|---|---|
registry | IDotnsProtocolRegistry | Protocol-level address registry used to resolve sibling contracts. |
setReverseName
Associates an address with a reverse name record.
Callable only by the configured registrar or its controller, otherwise
Note:
reverts: NotRegistrarController. Overwrites any existing reverse record for
addr and emits @custom:emits ReverseNameSet on every successful write.
function setReverseName(address addr, string calldata name) external override onlyRegistrar;
Parameters
| Name | Type | Description |
|---|---|---|
addr | address | The address for which the reverse name is being set. |
name | string | The human-readable name associated with the address. |
claimReverseRecord
Self-service claim: associates msg.sender with <label>.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
| Name | Type | Description |
|---|---|---|
label | string | The 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
| Name | Type | Description |
|---|---|---|
addr | address | The address to query. |
Returns
| Name | Type | Description |
|---|---|---|
name | string | The reverse name associated with addr, or the empty string. |
supportsInterface
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC165Upgradeable)
returns (bool supported);
_onlyRegistrar
Internal check enforcing registrar-only access.
function _onlyRegistrar() internal view;
version
Returns implementation version.
function version() external pure virtual returns (string memory versionString);
Returns
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
_authorizeUpgrade
Function that should revert when msg.sender is not authorized to upgrade the contract.
Called by
{upgradeToAndCall}.
Normally, this function will use an xref:access.adoc[access control] modifier such as
{Ownable-onlyOwner}.
function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;
IDotnsContentResolver
Title: Dotns Content Resolver
Defines storage and retrieval for content hash, text records, and operator approvals for DotNS nodes. @dev Content hash and text records point to off-chain content such as IPFS CIDs or future schemes; interpretation is handled off-chain. Operator approvals allow third parties to manage records on behalf of the owner.
Note: security-contact: admin@parity.io
Functions
setContenthash
Sets the content hash for a node.
The caller must own the node in the DotNS registry or be an approved operator, otherwise @custom:reverts NotAuthorised. Content hashes are opaque bytes (e.g. an IPFS CID); the resolver stores them as-is and never interprets the payload. Emits
Note: emits: ContentHashUpdated on every successful write.
function setContenthash(bytes32 node, bytes calldata hash) external;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node whose content hash is being set. |
hash | bytes | Opaque content hash bytes. |
contenthash
Returns the content hash associated with a node.
function contenthash(bytes32 node) external view returns (bytes memory hash);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node to query. |
Returns
| Name | Type | Description |
|---|---|---|
hash | bytes | The stored content hash bytes, or empty if unset. |
setText
Sets a text record for a node.
The caller must own the node in the DotNS registry or be an approved operator,
otherwise @custom:reverts NotAuthorised. Text records are arbitrary key/value strings
(e.g. avatar, url, description). Emits @custom:emits TextUpdated on every
successful write.
function setText(bytes32 node, string calldata key, string calldata value) external;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node whose text record is being set. |
key | string | Text record key (e.g., "ipfs", "avatar"). |
value | string | Text record value. |
text
Returns a text record for a node.
function text(bytes32 node, string calldata key) external view returns (string memory value);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node to query. |
key | string | Text record key. |
Returns
| Name | Type | Description |
|---|---|---|
value | string | Stored text value, or empty string if unset. |
setApprovalForAll
Enable or disable approval for a third party ("operator") to manage all of
msg.sender's nodes.
Emits @custom:emits ApprovalForAll whenever the approval flag is written, including idempotent writes that do not change the stored value.
function setApprovalForAll(address operator, bool approved) external;
Parameters
| Name | Type | Description |
|---|---|---|
operator | address | Address to authorise or revoke. |
approved | bool | True to approve, false to revoke. |
isApprovedForAll
Query if an address is an approved operator for another address.
function isApprovedForAll(address owner, address operator) external view returns (bool);
Parameters
| Name | Type | Description |
|---|---|---|
owner | address | The owner of the nodes. |
operator | address | The address acting on behalf of the owner. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | bool | True if operator is approved, false otherwise. |
Events
ContentHashUpdated
Emitted when a node's content hash is updated.
event ContentHashUpdated(bytes32 indexed node, bytes hash);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node whose content hash was updated. |
hash | bytes | The new content hash bytes. |
TextUpdated
Emitted when a node's text record is updated.
event TextUpdated(bytes32 indexed node, string indexed key, string value);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node whose text record was updated. |
key | string | The text record key. |
value | string | The new text record value. |
ApprovalForAll
Emitted when an operator is approved or revoked.
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
Parameters
| Name | Type | Description |
|---|---|---|
owner | address | The owner of the nodes. |
operator | address | The operator address. |
approved | bool | True if approved, false if revoked. |
Errors
NotAuthorised
Thrown when the caller is not authorised to modify a node.
error NotAuthorised(bytes32 node, address caller);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node being modified. |
caller | address | The address attempting the modification. |
IDotnsPopResolver
Title: IDotnsPopResolver
Resolver for per-name records produced by the PoP username flow.
Holds three record kinds:
- Chat key: ECDH public-key bytes used for end-to-end encrypted messaging.
- Lite link: for a full-person node, the labelhash of the lite-person username it was minted from (when the link was made).
- Full claim: reverse index mapping a lite labelhash to the full-person
node it was promoted to. Mirrors
liteLinkon every write so a caller that holds a lite labelhash can resolve the full-person node without scanning events. Lives separately from the per-userLabelStoreso that the store can remain a labels-only, protocol-write / user-read surface, and follows the project's resolver-per-record-category convention used by @custom:contract IDotnsContentResolver and
Notes:
-
contract: IDotnsReverseResolver. Write authorisation is delegated to the address registered as
DotnsProtocolRegistry.POP_CONTROLLERat call time, so rotating the PoP controller is a singleseton the protocol registry with no resolver upgrade required. -
security-contact: admin@parity.io
Functions
setChatKey
Sets the chat key for node.
Callable only by the address registered under DotnsProtocolRegistry.POP_CONTROLLER,
otherwise @custom:reverts NotPopController. Overwrites any previous value. The payload
must be exactly 65 bytes: the uncompressed secp256k1 public key encoding (1 prefix
byte followed by the 32-byte X and 32-byte Y affine coordinates); any other length
reverts with @custom:reverts InvalidChatKeyLength. Emits @custom:emits ChatKeyUpdated
on every successful write.
function setChatKey(bytes32 node, bytes calldata chatKey) external;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node whose chat key is being written. |
chatKey | bytes | ECDH public key bytes (pallet-side type is [u8; 65]). |
setLiteLink
Sets the lite-person link for a full-person node.
Callable only by the authorised PoP controller, otherwise
Notes:
-
reverts: NotPopController. Overwrites any previous link. When overwriting, the stale inverse entry is nulled so both the forward (
liteLink) and reverse (fullClaim) indices remain consistent: re-linking the samefullNodeto a newliteLabelhashclearsfullClaim(oldLite), and re-linking the sameliteLabelhashto a newfullNodeclearsliteLink(oldFull). The invariantfullClaim(liteLink(node)) == nodealways holds after the call. Emits -
emits: LiteLinkUpdated on every successful write.
function setLiteLink(bytes32 fullNode, bytes32 liteLabelhash) external;
Parameters
| Name | Type | Description |
|---|---|---|
fullNode | bytes32 | The full-person node carrying the link. |
liteLabelhash | bytes32 | The labelhash of the linked lite-person username. |
chatKey
Returns the chat key associated with a node.
function chatKey(bytes32 node) external view returns (bytes memory chatKey);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node to query. |
Returns
| Name | Type | Description |
|---|---|---|
chatKey | bytes | The stored chat key bytes, or empty if unset. |
liteLink
Returns the lite-person labelhash linked to a full-person node.
function liteLink(bytes32 fullNode) external view returns (bytes32 liteLabelhash);
Parameters
| Name | Type | Description |
|---|---|---|
fullNode | bytes32 | The full-person node to query. |
Returns
| Name | Type | Description |
|---|---|---|
liteLabelhash | bytes32 | The linked lite-person labelhash, or zero if unset. |
fullClaim
Returns the full-person node a given lite label has claimed.
Reverse of @custom:function liteLink. Written by the same setLiteLink call so the
two directions stay in lockstep. Returns zero when the lite label
has never been linked to a full claim.
function fullClaim(bytes32 liteLabelhash) external view returns (bytes32 fullNode);
Parameters
| Name | Type | Description |
|---|---|---|
liteLabelhash | bytes32 | The labelhash of the lite-person username to query. |
Returns
| Name | Type | Description |
|---|---|---|
fullNode | bytes32 | The full-person node claimed from this lite label, or zero if unset. |
Events
ChatKeyUpdated
Emitted when a node's chat key is set or updated.
event ChatKeyUpdated(bytes32 indexed node, bytes chatKey);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node whose chat key was written. |
chatKey | bytes | The new chat key bytes. |
LiteLinkUpdated
Emitted when a full-person node's lite link is set or updated.
event LiteLinkUpdated(bytes32 indexed fullNode, bytes32 indexed liteLabelhash);
Parameters
| Name | Type | Description |
|---|---|---|
fullNode | bytes32 | The full-person node carrying the link. |
liteLabelhash | bytes32 | The labelhash of the linked lite-person username. |
Errors
NotPopController
Thrown when the caller is not the authorised PoP controller.
error NotPopController(address caller);
Parameters
| Name | Type | Description |
|---|---|---|
caller | address | The address that attempted the write. |
InvalidChatKeyLength
Thrown when the provided chat key does not match the expected 65-byte length.
error InvalidChatKeyLength(uint256 length);
Parameters
| Name | Type | Description |
|---|---|---|
length | uint256 | The length of the payload that was rejected. |
IDotnsResolver
Title: Dotns Resolver
Defines forward-resolution address records for DotNS nodes.
Forward-address records describe where a name points. Authority therefore follows node ownership in the forward registry, not a privileged writer.
Note: security-contact: admin@parity.io
Functions
setAddress
Sets the resolved address for a node.
The caller must be the current owner of node in the forward registry, otherwise
Note: reverts: NotAuthorised. Emits @custom:emits AddressSet on every successful write.
function setAddress(bytes32 node, address value) external;
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node identifier. |
value | address | The address to associate with the node. |
addressOf
Returns the resolved address for a node.
function addressOf(bytes32 node) external view returns (address value);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node identifier. |
Returns
| Name | Type | Description |
|---|---|---|
value | address | The resolved address, or zero if unset. |
Events
AddressSet
Emitted when an address record is updated.
event AddressSet(bytes32 indexed node, address value);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node whose address record changed. |
value | address | The new resolved address. |
Errors
NotAuthorised
Thrown when a caller is not authorised to modify a node.
error NotAuthorised(bytes32 node, address caller);
Parameters
| Name | Type | Description |
|---|---|---|
node | bytes32 | The node being modified. |
caller | address | The address attempting the modification. |
IDotnsReverseResolver
Title: Dotns Reverse Resolver
Interface for writing and reading reverse name records for addresses.
Reverse records bind to an EOA rather than a registry node. Two write paths exist: a registrar-only setter used by the controller during reserved registration, and a self-service claim path callable by the current NFT owner. Reads are fail-closed: if the stored record no longer maps to a name owned by the address, @custom:function nameOf returns the empty string.
Note: security-contact: admin@parity.io
Functions
setReverseName
Associates an address with a reverse name record.
Callable only by the configured registrar or its controller, otherwise
Note:
reverts: NotRegistrarController. Overwrites any existing reverse record for
addr and emits @custom:emits ReverseNameSet on every successful write.
function setReverseName(address addr, string calldata name) external;
Parameters
| Name | Type | Description |
|---|---|---|
addr | address | The address for which the reverse name is being set. |
name | string | The human-readable name associated with the address. |
claimReverseRecord
Self-service claim: associates msg.sender with <label>.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
| Name | Type | Description |
|---|---|---|
label | string | The 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
| Name | Type | Description |
|---|---|---|
addr | address | The address to query. |
Returns
| Name | Type | Description |
|---|---|---|
name | string | The reverse name associated with addr, or the empty string. |
Events
ReverseNameSet
Emitted when a name is associated with an address.
event ReverseNameSet(address indexed addr, string indexed name);
Parameters
| Name | Type | Description |
|---|---|---|
addr | address | The address for which the reverse name is being set. |
name | string | The human-readable name associated with the address. |
Errors
NotRegistrarController
Thrown when a caller is not authorised to modify reverse records.
error NotRegistrarController(address caller);
Parameters
| Name | Type | Description |
|---|---|---|
caller | address | The address attempting the modification. |
NotNameOwner
Thrown when a caller attempts to claim a reverse record for a name they do not own.
error NotNameOwner(address caller, uint256 tokenId);
Parameters
| Name | Type | Description |
|---|---|---|
caller | address | The address attempting the claim. |
tokenId | uint256 | The token identifier derived from the claimed label. |
Contents
IDotnsStore
Title: IDotnsStore
Baseline interface implemented by every per-user DotNS store.
Marker interface shared by ILabelStore (protocol-written, labels-only) and
IUserStore (user-written, generic key/value). Every DotNS store type binds
to exactly one user forever, so owner() is the single shared surface.
Identity of a store is proven by its position in the StoreFactory mapping,
not by interface probing; the factory is the canonical source of truth.
Shared base so the factory and any cross-store consumer can prove which user a store
is bound to via a single uniform owner() call regardless of the concrete store type.
Note: security-contact: admin@parity.io
Functions
owner
Returns the permanent user this store is bound to.
Set once at initialize and never mutated; used as the binding-mismatch oracle
and immutable thereafter.
function owner() external view returns (address owner_);
Returns
| Name | Type | Description |
|---|---|---|
owner_ | address | The bound user address. |
ILabelStore
Inherits: IDotnsStore
Title: ILabelStore
Interface for the per-user DotNS label store.
The LabelStore is the protocol-managed half of the per-user storage pair:
write-only by addresses registered in the protocol registry, read-only by
everyone else, and permanently locked per labelhash on first write. It
holds registration records only; every other per-name category (reverse,
content, forward address, chat key, lite link) lives on a dedicated
resolver, not here.
Note: security-contact: admin@parity.io
Functions
initialize
Initialises the store, binding it permanently to user_ and protocolRegistry_.
Callable exactly once via Initializable; both parameters are immutable post-call.
user_ must be non-zero, otherwise @custom:reverts InvalidUser.
protocolRegistry_ must be non-zero, otherwise @custom:reverts
InvalidProtocolRegistry. @param user_ The user this store is bound to forever.
function initialize(address user_, address protocolRegistry_) external;
Parameters
| Name | Type | Description |
|---|---|---|
user_ | address | |
protocolRegistry_ | address | The protocol registry used to authorise writers. |
storeLabel
Records a label under labelhash and locks the slot permanently.
Gated to addresses currently registered in the protocol registry, otherwise
Notes:
-
reverts: NotAuthorised.
labelhashmust be non-zero, otherwise -
reverts: InvalidLabel. The slot must not already hold an entry, otherwise
-
reverts: LabelAlreadyExists; the write is permanent so any second call reverts. Emits @custom:emits LabelStored on the single successful write.
function storeLabel(bytes32 labelhash, string calldata label) external;
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The labelhash key. |
label | string | The label string to store. |
protocolRegistry
Returns the protocol registry this store queries for write authorisation.
function protocolRegistry() external view returns (address protocolRegistry_);
Returns
| Name | Type | Description |
|---|---|---|
protocolRegistry_ | address | The registry address. |
hasLabel
Returns true iff a label has been stored under labelhash.
function hasLabel(bytes32 labelhash) external view returns (bool exists);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The labelhash to check. |
Returns
| Name | Type | Description |
|---|---|---|
exists | bool | True iff the slot holds a label. |
isLocked
Returns true iff the slot for labelhash is permanently locked.
Always equal to hasLabel in the current design; exposed explicitly so future
implementations behind the beacon can distinguish "stored" from "locked" if needed.
function isLocked(bytes32 labelhash) external view returns (bool locked);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The labelhash to check. |
Returns
| Name | Type | Description |
|---|---|---|
locked | bool | True iff the slot is locked. |
getLabel
Returns the stored label for labelhash, or the empty string if none.
function getLabel(bytes32 labelhash) external view returns (string memory label);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The labelhash to look up. |
Returns
| Name | Type | Description |
|---|---|---|
label | string | The stored label string. |
getLabelCount
Returns the total number of labels ever stored.
function getLabelCount() external view returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Current length of the insertion-order list. |
getLabelAt
Returns the human-readable label at the given insertion-order index.
Primary read for "give me my names"; does not require the caller to know any labelhash. For the underlying labelhash key see @custom:function getLabelhashAt.
function getLabelAt(uint256 index) external view returns (string memory label);
Parameters
| Name | Type | Description |
|---|---|---|
index | uint256 | Zero-based index into the insertion-order list. |
Returns
| Name | Type | Description |
|---|---|---|
label | string | The stored label string at index. |
getLabelhashAt
Returns the labelhash at the given insertion-order index.
function getLabelhashAt(uint256 index) external view returns (bytes32 labelhash);
Parameters
| Name | Type | Description |
|---|---|---|
index | uint256 | Zero-based index into the insertion-order list. |
Returns
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The labelhash at index. |
getLabels
Paginated read returning just the stored labels, in insertion order.
Primary bulk read for "give me all my names". Callers never need to touch
labelhashes. Length is min(limit, getLabelCount() - offset);
offset >= getLabelCount() returns an empty array (not a revert).
function getLabels(uint256 offset, uint256 limit) external view returns (string[] memory labels);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
labels | string[] | Slice of label strings. |
getLabelhashes
Paginated read over the labelhash keys, in insertion order.
Advanced read for callers that need the raw labelhash keys. Symmetric with
Note: function: getLabels; same indices map to the same entries.
function getLabelhashes(
uint256 offset,
uint256 limit
)
external
view
returns (bytes32[] memory labelhashes);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
labelhashes | bytes32[] | Slice of labelhash keys. |
Events
LabelStored
Emitted when a label is stored for the first (and only) time under a given labelhash. @param owner The user this store is bound to.
event LabelStored(address indexed owner, bytes32 indexed labelhash, string label);
Parameters
| Name | Type | Description |
|---|---|---|
owner | address | |
labelhash | bytes32 | The labelhash key. |
label | string | The stored label string (typically the full name, e.g. "alice.dot"). |
Errors
NotAuthorised
Thrown when a caller that is not currently protocol-registered attempts a write.
error NotAuthorised(address caller);
Parameters
| Name | Type | Description |
|---|---|---|
caller | address | The msg.sender that failed the isRegisteredAddress check. |
InvalidUser
Thrown when initialize is called with a zero user address.
error InvalidUser(address user);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The invalid user argument. |
InvalidProtocolRegistry
Thrown when initialize is called with a zero protocol registry address.
error InvalidProtocolRegistry(address protocolRegistry);
Parameters
| Name | Type | Description |
|---|---|---|
protocolRegistry | address | The invalid registry argument. |
InvalidLabel
Thrown when storeLabel is called with a zero labelhash.
error InvalidLabel(bytes32 labelhash);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The invalid labelhash argument. |
LabelAlreadyExists
Thrown when storeLabel is called for a labelhash already present in the index.
Labels are write-once and permanently locked on first store, so any second write for the same labelhash fails with this error regardless of caller or session.
error LabelAlreadyExists(bytes32 labelhash);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The conflicting labelhash. |
IStoreFactory
Title: IStoreFactory
Interface for the DotNS per-user store factory.
Owns two UpgradeableBeacon instances; one for LabelStore (protocol-managed),
one for UserStore (user-claimed). Each user may acquire at most one of each,
forever. There is no transfer, no redeploy, no additional store type.
Note: security-contact: admin@parity.io
Functions
labelStoreBeacon
Returns the UpgradeableBeacon address backing all LabelStore proxies.
function labelStoreBeacon() external view returns (address beacon);
Returns
| Name | Type | Description |
|---|---|---|
beacon | address | Address of the beacon contract. |
userStoreBeacon
Returns the UpgradeableBeacon address backing all UserStore proxies.
function userStoreBeacon() external view returns (address beacon);
Returns
| Name | Type | Description |
|---|---|---|
beacon | address | Address of the beacon contract. |
protocolRegistry
Returns the protocol registry address used for writer authorisation.
function protocolRegistry() external view returns (address registry);
Returns
| Name | Type | Description |
|---|---|---|
registry | address | Address of the protocol registry. |
deployLabelStoreFor
Deploys a LabelStore beacon-proxy bound to user.
Callable by the factory owner or any address currently registered in the protocol
registry; any other caller @custom:reverts NotAuthorised. user must be non-zero,
otherwise @custom:reverts InvalidUser. The user must not already have a
LabelStore, otherwise @custom:reverts AlreadyDeployed. After deployment the
freshly initialised proxy must report user as its owner, otherwise
Notes:
-
reverts: ImplementationBindingMismatch. Emits
-
emits: LabelStoreDeployed on success.
function deployLabelStoreFor(address user) external returns (address store);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The user the store is bound to forever. |
Returns
| Name | Type | Description |
|---|---|---|
store | address | The deployed store address. |
getLabelStore
Returns the LabelStore address bound to user, or the zero address if none.
function getLabelStore(address user) external view returns (address store);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The user to look up. |
Returns
| Name | Type | Description |
|---|---|---|
store | address | The bound store address, or zero. |
getLabelStoreCount
Returns the total number of LabelStore proxies ever deployed.
function getLabelStoreCount() external view returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Length of the deployment list. |
getLabelStores
Paginated enumeration over every LabelStore proxy ever deployed.
Insertion order of deployLabelStoreFor calls. offset >= getLabelStoreCount()
returns an empty array; result length is min(limit, count - offset).
function getLabelStores(
uint256 offset,
uint256 limit
)
external
view
returns (address[] memory stores);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
stores | address[] | Slice of label-store addresses. |
upgradeLabelStoreImplementation
Upgrades the LabelStore implementation for every existing and future proxy.
Callable by the factory owner only, otherwise
Notes:
-
reverts: OwnableUnauthorizedAccount.
newImplementationmust be non-zero, otherwise @custom:reverts InvalidImplementation. The candidate is sentinel-probed by callingILabelStore.protocolRegistryon it before the beacon is rotated; if the address does not implement that selector the probe reverts and the upgrade does not land (deliberate fail-fast guard, no named error). Delegates toUpgradeableBeacon.upgradeToand emits -
emits: LabelStoreImplementationUpgraded on success.
function upgradeLabelStoreImplementation(address newImplementation) external;
Parameters
| Name | Type | Description |
|---|---|---|
newImplementation | address | The new implementation address. |
claimUserStore
Caller claims their UserStore beacon-proxy.
Self-claim only; _owner on the resulting store is always msg.sender,
regardless of who pays gas. One store per caller, forever: a caller who already
has a UserStore @custom:reverts AlreadyDeployed. After deployment the freshly
initialised proxy must report msg.sender as its owner, otherwise
Notes:
-
reverts: ImplementationBindingMismatch. Emits
-
emits: UserStoreClaimed on success.
function claimUserStore() external returns (address store);
Returns
| Name | Type | Description |
|---|---|---|
store | address | The deployed store address. |
getUserStore
Returns the UserStore address bound to user, or the zero address if none.
function getUserStore(address user) external view returns (address store);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The user to look up. |
Returns
| Name | Type | Description |
|---|---|---|
store | address | The bound store address, or zero. |
getUserStoreCount
Returns the total number of UserStore proxies ever claimed.
function getUserStoreCount() external view returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Length of the claim list. |
getUserStores
Paginated enumeration over every UserStore proxy ever claimed.
Insertion order of claimUserStore calls. offset >= getUserStoreCount()
returns an empty array; result length is min(limit, count - offset).
function getUserStores(
uint256 offset,
uint256 limit
)
external
view
returns (address[] memory stores);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
stores | address[] | Slice of user-store addresses. |
upgradeUserStoreImplementation
Upgrades the UserStore implementation for every existing and future proxy.
Callable by the factory owner only, otherwise
Notes:
-
reverts: OwnableUnauthorizedAccount.
newImplementationmust be non-zero, otherwise @custom:reverts InvalidImplementation. The candidate is sentinel-probed by callingIUserStore.getKeyCounton it before the beacon is rotated; if the address does not implement that selector the probe reverts and the upgrade does not land (deliberate fail-fast guard, no named error). Delegates toUpgradeableBeacon.upgradeToand emits -
emits: UserStoreImplementationUpgraded on success.
function upgradeUserStoreImplementation(address newImplementation) external;
Parameters
| Name | Type | Description |
|---|---|---|
newImplementation | address | The new implementation address. |
Events
LabelStoreDeployed
Emitted when a LabelStore beacon-proxy is deployed for user.
event LabelStoreDeployed(address indexed user, address indexed store);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The user the store is bound to. |
store | address | The deployed store address. |
UserStoreClaimed
Emitted when user claims their UserStore beacon-proxy.
event UserStoreClaimed(address indexed user, address indexed store);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The user the store is bound to. |
store | address | The deployed store address. |
LabelStoreImplementationUpgraded
Emitted when the LabelStore implementation behind the label beacon is upgraded.
event LabelStoreImplementationUpgraded(address indexed newImplementation);
Parameters
| Name | Type | Description |
|---|---|---|
newImplementation | address | The new implementation address. |
UserStoreImplementationUpgraded
Emitted when the UserStore implementation behind the user beacon is upgraded.
event UserStoreImplementationUpgraded(address indexed newImplementation);
Parameters
| Name | Type | Description |
|---|---|---|
newImplementation | address | The new implementation address. |
Errors
AlreadyDeployed
Thrown when attempting to deploy or claim a store that already exists.
error AlreadyDeployed(address user, address existingStore);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The user for whom the store exists. |
existingStore | address | The already-deployed store address. |
InvalidUser
Thrown when a zero user address is supplied.
error InvalidUser(address user);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The invalid user argument. |
InvalidProtocolRegistry
Thrown when a zero protocol registry address is supplied to the constructor.
error InvalidProtocolRegistry(address protocolRegistry);
Parameters
| Name | Type | Description |
|---|---|---|
protocolRegistry | address | The invalid registry argument. |
InvalidImplementation
Thrown when a zero implementation address is supplied to the constructor or an upgrade. @param implementation The invalid implementation argument.
error InvalidImplementation(address implementation);
NotAuthorised
Thrown when an unauthorised address attempts to deploy a label store.
error NotAuthorised(address caller);
Parameters
| Name | Type | Description |
|---|---|---|
caller | address | The unauthorised msg.sender. |
ImplementationBindingMismatch
Thrown when a freshly deployed proxy does not report the expected owner.
error ImplementationBindingMismatch();
IUserStore
Inherits: IDotnsStore
Title: IUserStore
Interface for the per-user generic key/value store with per-key history.
Each user may claim at most one UserStore from StoreFactory. The store is
bound to its claimer forever: _owner is set once at initialize and only that
address may write. Each setValue snapshots the prior non-empty value into a
per-key history list with a timestamp; current and history are both readable by
anyone, paginated.
Note: security-contact: admin@parity.io
Functions
initialize
Initialises the store, binding it permanently to user_.
Callable exactly once via Initializable. user_ must be non-zero, otherwise
Note: reverts: InvalidUser.
function initialize(address user_) external;
Parameters
| Name | Type | Description |
|---|---|---|
user_ | address | The user this store is bound to forever. |
setValue
Sets the current value for key.
Callable only by the bound owner; any other caller @custom:reverts NotOwner.
key must be non-zero, otherwise @custom:reverts InvalidKey. If a non-empty
prior value existed it is pushed into the per-key history list with
block.timestamp; empty prior values produce no history entry. Emits
Note: emits: ValueSet on every successful write.
function setValue(bytes32 key, bytes calldata value) external;
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to write. |
value | bytes | The new current value (may be empty). |
getValue
Returns the current value under key, or empty bytes if unset.
function getValue(bytes32 key) external view returns (bytes memory value);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to read. |
Returns
| Name | Type | Description |
|---|---|---|
value | bytes | The current value. |
hasValue
Returns true iff the current value under key has non-zero length.
function hasValue(bytes32 key) external view returns (bool present);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to check. |
Returns
| Name | Type | Description |
|---|---|---|
present | bool | True iff getValue(key).length != 0. |
getHistoryCount
Returns the number of prior (historical) values recorded for key.
function getHistoryCount(bytes32 key) external view returns (uint256 count);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to read. |
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Length of the history list. |
getHistoryAt
Returns the historical entry at index for key.
function getHistoryAt(bytes32 key, uint256 index) external view returns (Entry memory entry);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to read. |
index | uint256 | Zero-based index into the history list. |
Returns
| Name | Type | Description |
|---|---|---|
entry | Entry | The (value, timestamp) pair. |
getHistory
Paginated read over the per-key history list.
offset >= getHistoryCount(key) returns an empty array. Length is
min(limit, getHistoryCount(key) - offset).
function getHistory(
bytes32 key,
uint256 offset,
uint256 limit
)
external
view
returns (Entry[] memory entries);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to read. |
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
entries | Entry[] | Slice of history entries. |
getKeyCount
Returns the number of distinct keys ever written.
function getKeyCount() external view returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Length of the key-insertion list. |
getKeyAt
Returns the key at the given insertion-order index.
function getKeyAt(uint256 index) external view returns (bytes32 key);
Parameters
| Name | Type | Description |
|---|---|---|
index | uint256 | Zero-based index into the key list. |
Returns
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key at index. |
getKeys
Paginated read over the insertion-order key list.
offset >= getKeyCount() returns an empty array. Length is
min(limit, getKeyCount() - offset).
function getKeys(uint256 offset, uint256 limit) external view returns (bytes32[] memory keys);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
keys | bytes32[] | Slice of keys. |
Events
ValueSet
Emitted when the owner sets (or updates) a value under key.
event ValueSet(address indexed owner, bytes32 indexed key, bytes value);
Parameters
| Name | Type | Description |
|---|---|---|
owner | address | The user this store is bound to. |
key | bytes32 | The key being written. |
value | bytes | The new current value. |
Errors
NotOwner
Thrown when any caller other than the bound owner attempts a write.
error NotOwner(address caller);
Parameters
| Name | Type | Description |
|---|---|---|
caller | address | The unauthorised msg.sender. |
InvalidUser
Thrown when initialize is called with a zero user address.
error InvalidUser(address user);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The invalid user argument. |
InvalidKey
Thrown when setValue is called with a zero key.
error InvalidKey();
Structs
Entry
A historical (prior) value for a key and the block timestamp at which it was snapshotted. @param value The prior value that was just superseded.
struct Entry {
bytes value;
uint256 timestamp;
}
Properties
| Name | Type | Description |
|---|---|---|
value | bytes | |
timestamp | uint256 | Block timestamp at the moment of supersession. |
LabelStore
Inherits: Initializable, ILabelStore
Title: LabelStore
Permanent per-user DotNS label store.
One instance per user, deployed as a BeaconProxy by StoreFactory during registration.
Bound to its user forever: _owner and _protocolRegistry are set once at initialize
and never mutate. Writes are gated to addresses currently registered in
DotnsProtocolRegistry (isRegisteredAddress); every labelhash is single-write and
permanently locked on first use.
Labels-only by invariant: this store holds registration records only. Every other per-name category (reverse, content, forward address, chat key, lite link) lives on a dedicated resolver, never here.
Storage collision: the BeaconProxy stores the beacon address at EIP-1967 slot
keccak256("eip1967.proxy.beacon") - 1, which is non-sequential and cannot collide
with this contract's sequential storage slots.
Note: security-contact: admin@parity.io
State Variables
_owner
Permanent user this store belongs to. Set in initialize.
address private _owner
_protocolRegistry
Canonical DotNS protocol registry. Set in initialize.
address private _protocolRegistry
_labels
labelhash => stored label string.
mapping(bytes32 labelhash => string label) private _labels
_labelList
Insertion-order list of all stored labelhashes. Append-only.
bytes32[] private _labelList
_labelIndex
labelhash => 1-indexed position in _labelList (zero means "not present").
Doubles as the permanent-lock sentinel: a non-zero index proves the label was written and
the contract has no deletion path, so the index is also the locked flag.
mapping(bytes32 labelhash => uint256 indexPlusOne) private _labelIndex
__gap
Reserved storage space to allow for layout changes in future beacon upgrades.
uint256[50] private __gap
Functions
onlyAuthorisedProtocol
Restricts writes to protocol-registered addresses only.
modifier onlyAuthorisedProtocol() ;
constructor
Note: oz-upgrades-unsafe-allow: constructor
constructor() ;
initialize
Initialises the store, binding it permanently to user_ and protocolRegistry_.
Callable exactly once via Initializable; both parameters are immutable post-call.
user_ must be non-zero, otherwise @custom:reverts InvalidUser.
protocolRegistry_ must be non-zero, otherwise @custom:reverts
InvalidProtocolRegistry. @param user_ The user this store is bound to forever.
function initialize(address user_, address protocolRegistry_) external override initializer;
Parameters
| Name | Type | Description |
|---|---|---|
user_ | address | |
protocolRegistry_ | address | The protocol registry used to authorise writers. |
storeLabel
Records a label under labelhash and locks the slot permanently.
Gated to addresses currently registered in the protocol registry, otherwise
Note:
reverts: NotAuthorised. labelhash must be non-zero, otherwise
function storeLabel(
bytes32 labelhash,
string calldata label
)
external
override
onlyAuthorisedProtocol;
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The labelhash key. |
label | string | The label string to store. |
owner
Returns the permanent user this store is bound to.
Set once at initialize and never mutated; used as the binding-mismatch oracle
and immutable thereafter.
function owner() external view override returns (address owner_);
Returns
| Name | Type | Description |
|---|---|---|
owner_ | address | The bound user address. |
protocolRegistry
Returns the protocol registry this store queries for write authorisation.
function protocolRegistry() external view override returns (address protocolRegistry_);
Returns
| Name | Type | Description |
|---|---|---|
protocolRegistry_ | address | The registry address. |
hasLabel
Returns true iff a label has been stored under labelhash.
function hasLabel(bytes32 labelhash) external view override returns (bool exists);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The labelhash to check. |
Returns
| Name | Type | Description |
|---|---|---|
exists | bool | True iff the slot holds a label. |
isLocked
Returns true iff the slot for labelhash is permanently locked.
Always equal to hasLabel in the current design; exposed explicitly so future
implementations behind the beacon can distinguish "stored" from "locked" if needed.
function isLocked(bytes32 labelhash) external view override returns (bool locked);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The labelhash to check. |
Returns
| Name | Type | Description |
|---|---|---|
locked | bool | True iff the slot is locked. |
getLabel
Returns the stored label for labelhash, or the empty string if none.
function getLabel(bytes32 labelhash) external view override returns (string memory label);
Parameters
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The labelhash to look up. |
Returns
| Name | Type | Description |
|---|---|---|
label | string | The stored label string. |
getLabelCount
Returns the total number of labels ever stored.
function getLabelCount() external view override returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Current length of the insertion-order list. |
getLabelAt
Returns the human-readable label at the given insertion-order index.
Primary read for "give me my names"; does not require the caller to know any labelhash. For the underlying labelhash key see @custom:function getLabelhashAt.
function getLabelAt(uint256 index) external view override returns (string memory label);
Parameters
| Name | Type | Description |
|---|---|---|
index | uint256 | Zero-based index into the insertion-order list. |
Returns
| Name | Type | Description |
|---|---|---|
label | string | The stored label string at index. |
getLabelhashAt
Returns the labelhash at the given insertion-order index.
function getLabelhashAt(uint256 index) external view override returns (bytes32 labelhash);
Parameters
| Name | Type | Description |
|---|---|---|
index | uint256 | Zero-based index into the insertion-order list. |
Returns
| Name | Type | Description |
|---|---|---|
labelhash | bytes32 | The labelhash at index. |
getLabels
Paginated read returning just the stored labels, in insertion order.
Primary bulk read for "give me all my names". Callers never need to touch
labelhashes. Length is min(limit, getLabelCount() - offset);
offset >= getLabelCount() returns an empty array (not a revert).
function getLabels(
uint256 offset,
uint256 limit
)
external
view
override
returns (string[] memory labels);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
labels | string[] | Slice of label strings. |
getLabelhashes
Paginated read over the labelhash keys, in insertion order.
Advanced read for callers that need the raw labelhash keys. Symmetric with
Note: function: getLabels; same indices map to the same entries.
function getLabelhashes(
uint256 offset,
uint256 limit
)
external
view
override
returns (bytes32[] memory labelhashes);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
labelhashes | bytes32[] | Slice of labelhash keys. |
version
Returns implementation version.
function version() external pure virtual returns (string memory versionString);
Returns
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
_onlyAuthorisedProtocol
Internal authorisation check deferred from the onlyAuthorisedProtocol modifier.
function _onlyAuthorisedProtocol() internal view;
StoreFactory
Inherits: Ownable, IStoreFactory
Title: StoreFactory
Factory for the two per-user DotNS store types, sharing one factory contract and two beacons. @dev Each user may acquire AT MOST two stores, ever:
- a
LabelStore, deployed viadeployLabelStoreForby a protocol-registered caller during registration; and - a
UserStore, claimed viaclaimUserStoreby the user themselves. Both areBeaconProxyinstances pointing at their respectiveUpgradeableBeacon. The factory owns both beacons so the factory owner can upgrade implementations for every proxy atomically. Neither per-user mapping is ever transferred, reassigned, or overwritten after the first write; bindings are permanent.
Note: security-contact: admin@parity.io
Constants
labelStoreBeacon
Beacon backing every LabelStore proxy.
Public getter name is interface-constrained by @custom:contract IStoreFactory.
address public immutable override labelStoreBeacon
userStoreBeacon
Beacon backing every UserStore proxy.
Public getter name is interface-constrained by @custom:contract IStoreFactory.
address public immutable override userStoreBeacon
protocolRegistry
Protocol registry used to authorise deployLabelStoreFor callers.
Public getter name is interface-constrained by @custom:contract IStoreFactory.
address public immutable override protocolRegistry
State Variables
_labelStores
user => their permanent LabelStore. Set once per user, forever.
mapping(address user => address store) private _labelStores
_userStores
user => their permanent UserStore. Set once per user, forever.
mapping(address user => address store) private _userStores
_labelStoreList
Insertion-order list of every LabelStore proxy ever deployed. Append-only.
address[] private _labelStoreList
_userStoreList
Insertion-order list of every UserStore proxy ever claimed. Append-only.
address[] private _userStoreList
Functions
onlyOwnerOrProtocol
Restricts deployLabelStoreFor to the owner or any protocol-registered caller.
modifier onlyOwnerOrProtocol() ;
constructor
Deploys the factory together with both store implementations and beacons.
A single new StoreFactory(protocolRegistry, owner) call wires everything:
- Deploys a fresh
LabelStoreimplementation. - Deploys a fresh
UserStoreimplementation. - Constructs both
UpgradeableBeaconinstances, owned byaddress(this)soupgrade*Implementationcan delegate tobeacon.upgradeTo. Keeping the implementation deployments inside the constructor removes a class of operator error: there is no "did I deploy the implementation first?" step and no way to pass the wrong implementation address.protocolRegistry_must be non-zero, otherwise @custom:reverts InvalidProtocolRegistry.
Implementations and beacons are deployed inline so a single factory address fully describes the store topology, removing a class of operator error around mismatched beacons.
constructor(address protocolRegistry_, address owner_) Ownable(owner_);
Parameters
| Name | Type | Description |
|---|---|---|
protocolRegistry_ | address | The protocol registry for writer auth on label stores. |
owner_ | address | Account that owns this factory and can upgrade store implementations. |
deployLabelStoreFor
Deploys a LabelStore beacon-proxy bound to user.
Callable by the factory owner or any address currently registered in the protocol
registry; any other caller @custom:reverts NotAuthorised. user must be non-zero,
otherwise @custom:reverts InvalidUser. The user must not already have a
LabelStore, otherwise @custom:reverts AlreadyDeployed. After deployment the
freshly initialised proxy must report user as its owner, otherwise
Notes:
-
reverts: ImplementationBindingMismatch. Emits
-
emits: LabelStoreDeployed on success.
function deployLabelStoreFor(address user)
external
override
onlyOwnerOrProtocol
returns (address store);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The user the store is bound to forever. |
Returns
| Name | Type | Description |
|---|---|---|
store | address | The deployed store address. |
getLabelStore
Returns the LabelStore address bound to user, or the zero address if none.
function getLabelStore(address user) external view override returns (address store);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The user to look up. |
Returns
| Name | Type | Description |
|---|---|---|
store | address | The bound store address, or zero. |
getLabelStoreCount
Returns the total number of LabelStore proxies ever deployed.
function getLabelStoreCount() external view override returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Length of the deployment list. |
getLabelStores
Paginated enumeration over every LabelStore proxy ever deployed.
Insertion order of deployLabelStoreFor calls. offset >= getLabelStoreCount()
returns an empty array; result length is min(limit, count - offset).
function getLabelStores(
uint256 offset,
uint256 limit
)
external
view
override
returns (address[] memory stores);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
stores | address[] | Slice of label-store addresses. |
upgradeLabelStoreImplementation
Upgrades the LabelStore implementation for every existing and future proxy.
Callable by the factory owner only, otherwise
Notes:
-
reverts: OwnableUnauthorizedAccount.
newImplementationmust be non-zero, otherwise @custom:reverts InvalidImplementation. The candidate is sentinel-probed by callingILabelStore.protocolRegistryon it before the beacon is rotated; if the address does not implement that selector the probe reverts and the upgrade does not land (deliberate fail-fast guard, no named error). Delegates toUpgradeableBeacon.upgradeToand emits -
emits: LabelStoreImplementationUpgraded on success.
function upgradeLabelStoreImplementation(address newImplementation)
external
override
onlyOwner;
Parameters
| Name | Type | Description |
|---|---|---|
newImplementation | address | The new implementation address. |
claimUserStore
Caller claims their UserStore beacon-proxy.
Self-claim only; _owner on the resulting store is always msg.sender,
regardless of who pays gas. One store per caller, forever: a caller who already
has a UserStore @custom:reverts AlreadyDeployed. After deployment the freshly
initialised proxy must report msg.sender as its owner, otherwise
Notes:
-
reverts: ImplementationBindingMismatch. Emits
-
emits: UserStoreClaimed on success.
function claimUserStore() external override returns (address store);
Returns
| Name | Type | Description |
|---|---|---|
store | address | The deployed store address. |
getUserStore
Returns the UserStore address bound to user, or the zero address if none.
function getUserStore(address user) external view override returns (address store);
Parameters
| Name | Type | Description |
|---|---|---|
user | address | The user to look up. |
Returns
| Name | Type | Description |
|---|---|---|
store | address | The bound store address, or zero. |
getUserStoreCount
Returns the total number of UserStore proxies ever claimed.
function getUserStoreCount() external view override returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Length of the claim list. |
getUserStores
Paginated enumeration over every UserStore proxy ever claimed.
Insertion order of claimUserStore calls. offset >= getUserStoreCount()
returns an empty array; result length is min(limit, count - offset).
function getUserStores(
uint256 offset,
uint256 limit
)
external
view
override
returns (address[] memory stores);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
stores | address[] | Slice of user-store addresses. |
upgradeUserStoreImplementation
Upgrades the UserStore implementation for every existing and future proxy.
Callable by the factory owner only, otherwise
Notes:
-
reverts: OwnableUnauthorizedAccount.
newImplementationmust be non-zero, otherwise @custom:reverts InvalidImplementation. The candidate is sentinel-probed by callingIUserStore.getKeyCounton it before the beacon is rotated; if the address does not implement that selector the probe reverts and the upgrade does not land (deliberate fail-fast guard, no named error). Delegates toUpgradeableBeacon.upgradeToand emits -
emits: UserStoreImplementationUpgraded on success.
function upgradeUserStoreImplementation(address newImplementation) external override onlyOwner;
Parameters
| Name | Type | Description |
|---|---|---|
newImplementation | address | The new implementation address. |
version
Returns implementation version.
function version() external pure virtual returns (string memory versionString);
Returns
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
_onlyOwnerOrProtocol
Internal authorisation check deferred from the onlyOwnerOrProtocol modifier.
function _onlyOwnerOrProtocol() internal view;
_paginateAddresses
Shared pagination helper used by getLabelStores and getUserStores.
Single canonical slicer so both enumerations bound-check and copy identically.
function _paginateAddresses(
address[] storage source,
uint256 offset,
uint256 limit
)
internal
view
returns (address[] memory slice);
Parameters
| Name | Type | Description |
|---|---|---|
source | address[] | Storage array to slice. |
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
slice | address[] | Result slice; empty when offset >= source.length. |
UserStore
Inherits: Initializable, IUserStore
Title: UserStore
Permanent per-user generic key/value store with per-key history.
One instance per user, deployed as a BeaconProxy by StoreFactory.claimUserStore.
Bound to its claimer forever: _owner is set once at initialize and only that
address may write. History is append-only; every setValue that supersedes a
non-empty prior value records the supersession with block.timestamp.
Cost isolation: each user's writes bill their own contract, keeping shared resolvers from being polluted by one user's blob usage.
Storage collision: the BeaconProxy stores the beacon address at EIP-1967 slot
keccak256("eip1967.proxy.beacon") - 1, which is non-sequential and cannot collide
with this contract's sequential storage slots.
Note: security-contact: admin@parity.io
State Variables
_owner
Permanent user this store belongs to. Set in initialize.
address private _owner
_current
key => current value bytes.
mapping(bytes32 key => bytes value) private _current
_history
key => insertion-order list of prior non-empty values and their supersession timestamps.
mapping(bytes32 key => Entry[] entries) private _history
_keyList
Insertion-order list of all keys ever written. Append-only (never pruned).
bytes32[] private _keyList
_keyIndex
key => 1-indexed position in _keyList (zero means "never written").
mapping(bytes32 key => uint256 indexPlusOne) private _keyIndex
__gap
Reserved storage space to allow for layout changes in future beacon upgrades.
uint256[50] private __gap
Functions
onlyOwner
Restricts writes to the bound owner.
modifier onlyOwner() ;
constructor
Note: oz-upgrades-unsafe-allow: constructor
constructor() ;
initialize
Initialises the store, binding it permanently to user_.
Callable exactly once via Initializable. user_ must be non-zero, otherwise
Note: reverts: InvalidUser.
function initialize(address user_) external override initializer;
Parameters
| Name | Type | Description |
|---|---|---|
user_ | address | The user this store is bound to forever. |
setValue
Sets the current value for key.
Callable only by the bound owner; any other caller @custom:reverts NotOwner.
key must be non-zero, otherwise @custom:reverts InvalidKey. If a non-empty
prior value existed it is pushed into the per-key history list with
block.timestamp; empty prior values produce no history entry. Emits
Note: emits: ValueSet on every successful write.
function setValue(bytes32 key, bytes calldata value) external override onlyOwner;
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to write. |
value | bytes | The new current value (may be empty). |
owner
Returns the permanent user this store is bound to.
Set once at initialize and never mutated; used as the binding-mismatch oracle
and immutable thereafter.
function owner() external view override returns (address owner_);
Returns
| Name | Type | Description |
|---|---|---|
owner_ | address | The bound user address. |
getValue
Returns the current value under key, or empty bytes if unset.
function getValue(bytes32 key) external view override returns (bytes memory value);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to read. |
Returns
| Name | Type | Description |
|---|---|---|
value | bytes | The current value. |
hasValue
Returns true iff the current value under key has non-zero length.
function hasValue(bytes32 key) external view override returns (bool present);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to check. |
Returns
| Name | Type | Description |
|---|---|---|
present | bool | True iff getValue(key).length != 0. |
getHistoryCount
Returns the number of prior (historical) values recorded for key.
function getHistoryCount(bytes32 key) external view override returns (uint256 count);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to read. |
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Length of the history list. |
getHistoryAt
Returns the historical entry at index for key.
function getHistoryAt(
bytes32 key,
uint256 index
)
external
view
override
returns (Entry memory entry);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to read. |
index | uint256 | Zero-based index into the history list. |
Returns
| Name | Type | Description |
|---|---|---|
entry | Entry | The (value, timestamp) pair. |
getHistory
Paginated read over the per-key history list.
offset >= getHistoryCount(key) returns an empty array. Length is
min(limit, getHistoryCount(key) - offset).
function getHistory(
bytes32 key,
uint256 offset,
uint256 limit
)
external
view
override
returns (Entry[] memory entries);
Parameters
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key to read. |
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
entries | Entry[] | Slice of history entries. |
getKeyCount
Returns the number of distinct keys ever written.
function getKeyCount() external view override returns (uint256 count);
Returns
| Name | Type | Description |
|---|---|---|
count | uint256 | Length of the key-insertion list. |
getKeyAt
Returns the key at the given insertion-order index.
function getKeyAt(uint256 index) external view override returns (bytes32 key);
Parameters
| Name | Type | Description |
|---|---|---|
index | uint256 | Zero-based index into the key list. |
Returns
| Name | Type | Description |
|---|---|---|
key | bytes32 | The key at index. |
getKeys
Paginated read over the insertion-order key list.
offset >= getKeyCount() returns an empty array. Length is
min(limit, getKeyCount() - offset).
function getKeys(
uint256 offset,
uint256 limit
)
external
view
override
returns (bytes32[] memory keys);
Parameters
| Name | Type | Description |
|---|---|---|
offset | uint256 | Start index. |
limit | uint256 | Maximum entries to return. |
Returns
| Name | Type | Description |
|---|---|---|
keys | bytes32[] | Slice of keys. |
version
Returns implementation version.
function version() external pure virtual returns (string memory versionString);
Returns
| Name | Type | Description |
|---|---|---|
versionString | string | Current version string. |
_onlyOwner
Internal owner check deferred from the onlyOwner modifier.
function _onlyOwner() internal view;
Contents
DotnsConstants
Title: DotNS Constants
Protocol-level invariants shared across DotNS contracts.
Centralises the 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
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
| Name | Type | Description |
|---|---|---|
label | string | Label string. |
Returns
| Name | Type | Description |
|---|---|---|
hash | bytes32 | keccak256(bytes(label)). |
labelhashMemory
Computes keccak256(bytes(label)) for a memory string.
Overload used by call sites that hold the label in memory (e.g. the
registrar's transfer sync path reading _labels[tokenId]). Same
semantics as @custom:function labelhash, different calldata shape.
function labelhashMemory(string memory label) internal pure returns (bytes32 hash);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Label string held in memory. |
Returns
| Name | Type | Description |
|---|---|---|
hash | bytes32 | keccak256(bytes(label)). |
namehashUnder
Computes namehash(parent, labelhash) against an arbitrary parent.
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
| Name | Type | Description |
|---|---|---|
parent | bytes32 | Parent node. |
labelhash_ | bytes32 | keccak256(bytes(label)). |
Returns
| Name | Type | Description |
|---|---|---|
node | bytes32 | namehash(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
| Name | Type | Description |
|---|---|---|
labelhash_ | bytes32 | keccak256(bytes(label)). |
Returns
| Name | Type | Description |
|---|---|---|
node | bytes32 | The 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
| Name | Type | Description |
|---|---|---|
label | string | Label string. |
Returns
| Name | Type | Description |
|---|---|---|
hash | bytes32 | keccak256(bytes(label)). |
node | bytes32 | namehash(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
Title: Multicall3
Authors: Michael Elliot, Joshua Levine, Nick Johnson, Andreas Bigger, Matt Solomon
Aggregate results from multiple function calls.
Multicall and Multicall2 backwards-compatible.
Source-compatible with mds1/multicall3; pragma updated for this repository.
Aggregate methods are payable to match the standard Multicall3 ABI.
Functions
aggregate
Backwards-compatible call aggregation with Multicall.
function aggregate(Call[] calldata calls)
public
payable
returns (uint256 blockNumber, bytes[] memory returnData);
Parameters
| Name | Type | Description |
|---|---|---|
calls | Call[] | An array of Call structs. |
Returns
| Name | Type | Description |
|---|---|---|
blockNumber | uint256 | The block number where the calls were executed. |
returnData | bytes[] | An array of bytes containing the responses. |
tryAggregate
Backwards-compatible with Multicall2.
Aggregate calls without requiring success.
function tryAggregate(
bool requireSuccess,
Call[] calldata calls
)
public
payable
returns (Result[] memory returnData);
Parameters
| Name | Type | Description |
|---|---|---|
requireSuccess | bool | If true, require all calls to succeed. |
calls | Call[] | An array of Call structs. |
Returns
| Name | Type | Description |
|---|---|---|
returnData | Result[] | An array of Result structs. |
tryBlockAndAggregate
Backwards-compatible with Multicall2.
Aggregate calls and allow failures using tryAggregate.
function tryBlockAndAggregate(
bool requireSuccess,
Call[] calldata calls
)
public
payable
returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData);
Parameters
| Name | Type | Description |
|---|---|---|
requireSuccess | bool | If true, require all calls to succeed. |
calls | Call[] | An array of Call structs. |
Returns
| Name | Type | Description |
|---|---|---|
blockNumber | uint256 | The block number where the calls were executed. |
blockHash | bytes32 | The hash of the block where the calls were executed. |
returnData | Result[] | An array of Result structs. |
blockAndAggregate
Backwards-compatible with Multicall2.
Aggregate calls and allow failures using tryAggregate.
function blockAndAggregate(Call[] calldata calls)
public
payable
returns (uint256 blockNumber, bytes32 blockHash, Result[] memory returnData);
Parameters
| Name | Type | Description |
|---|---|---|
calls | Call[] | An array of Call structs. |
Returns
| Name | Type | Description |
|---|---|---|
blockNumber | uint256 | The block number where the calls were executed. |
blockHash | bytes32 | The hash of the block where the calls were executed. |
returnData | Result[] | An array of Result structs. |
aggregate3
Aggregate calls, ensuring each returns success if required.
function aggregate3(Call3[] calldata calls)
public
payable
returns (Result[] memory returnData);
Parameters
| Name | Type | Description |
|---|---|---|
calls | Call3[] | An array of Call3 structs. |
Returns
| Name | Type | Description |
|---|---|---|
returnData | Result[] | An array of Result structs. |
aggregate3Value
Aggregate calls with a msg value.
Reverts if msg.value is not equal to the sum of the call values.
function aggregate3Value(Call3Value[] calldata calls)
public
payable
returns (Result[] memory returnData);
Parameters
| Name | Type | Description |
|---|---|---|
calls | Call3Value[] | An array of Call3Value structs. |
Returns
| Name | Type | Description |
|---|---|---|
returnData | Result[] | An array of Result structs. |
getBlockHash
Returns the block hash for the given block number.
function getBlockHash(uint256 blockNumber) public view returns (bytes32 blockHash);
Parameters
| Name | Type | Description |
|---|---|---|
blockNumber | uint256 | The block number. |
getBlockNumber
Returns the block number.
function getBlockNumber() public view returns (uint256 blockNumber);
getCurrentBlockCoinbase
Returns the block coinbase.
function getCurrentBlockCoinbase() public view returns (address coinbase);
getCurrentBlockDifficulty
Returns the block prevrandao value.
function getCurrentBlockDifficulty() public view returns (uint256 difficulty);
getCurrentBlockGasLimit
Returns the block gas limit.
function getCurrentBlockGasLimit() public view returns (uint256 gaslimit);
getCurrentBlockTimestamp
Returns the block timestamp.
function getCurrentBlockTimestamp() public view returns (uint256 timestamp);
getEthBalance
Returns the native-token balance of a given address.
function getEthBalance(address addr) public view returns (uint256 balance);
getLastBlockHash
Returns the block hash of the last block.
function getLastBlockHash() public view returns (bytes32 blockHash);
getBasefee
Gets the base fee of the given block.
Can revert if the BASEFEE opcode is not implemented by the given chain.
function getBasefee() public view returns (uint256 basefee);
getChainId
Returns the chain id.
function getChainId() public view returns (uint256 chainid);
Structs
Call
struct Call {
address target;
bytes callData;
}
Call3
struct Call3 {
address target;
bool allowFailure;
bytes callData;
}
Call3Value
struct Call3Value {
address target;
bool allowFailure;
uint256 value;
bytes callData;
}
Result
struct Result {
bool success;
bytes returnData;
}
RegistrationUtils
Title: DotNS Registration Utilities Library
Single canonical implementation of the "mint + forward-registry + store-write" triad used by every DotNS registration flow.
Exists so that every controller (public commit-reveal, PoP gateway, future privileged flows) calls the same sequence. Without this library each controller re-implements the sequence, and the implementations drift.
Scope: this library is deliberately minimal. It only performs the steps that every registration flow needs regardless of policy:
- Mint the ERC721 name token on the base registrar.
- Write the forward registry entry (node => owner + default resolver).
- Resolve the per-user
LabelStore(deploying on demand) so the caller can pass it back. The registrar writes the labels-only store entry internally. Flow-specific concerns (pricing, reverse-record setting, chat-key persistence, reservation queue mutation) stay inside the calling controller.
Note: security-contact: admin@parity.io
Functions
registerAndStore
Performs the canonical mint + forward-registry + store-write sequence.
Callable by any authorised controller. Emits no events; each controller
emits its own flow-level event after this returns, so behavioural drift
between flows stays contained at the emission layer rather than at the
underlying state-transition layer. Store authorisation is handled by the
protocol registry (isRegisteredAddress) on every write, so no per-store
allowlist bookkeeping is needed here.
The registrar writes the LabelStore entry directly inside register
so this helper deliberately does not call StoreUtils.writeLabel.
Doing it twice would deploy or touch the store on every flow and
could conflict with the registrar's locked-entry semantics.
function registerAndStore(RegistrationContext memory context)
internal
returns (address labelStore);
Parameters
| Name | Type | Description |
|---|---|---|
context | RegistrationContext | Registration inputs. See @custom:struct RegistrationContext. |
Returns
| Name | Type | Description |
|---|---|---|
labelStore | address | The resolved or newly deployed LabelStore address for context.user. |
_resolveSiblings
Resolves sibling contracts via the protocol registry.
Exists so that resolution is one round-trip through a single helper and not duplicated inline at every call site. If protocol-registry key conventions change, the change lands here.
function _resolveSiblings(IDotnsProtocolRegistry protocolRegistry)
private
view
returns (Siblings memory siblings);
Structs
RegistrationContext
Inputs describing a single name registration.
Passed as a struct so callers do not have to thread a growing positional argument list, and so future additions (e.g. a subname parent node) can be made additively without breaking call sites.
struct RegistrationContext {
IDotnsProtocolRegistry protocolRegistry;
address user;
string label;
bytes32 labelhash;
bytes32 node;
}
Properties
| Name | Type | Description |
|---|---|---|
protocolRegistry | IDotnsProtocolRegistry | The protocol-level address registry for sibling lookups. |
user | address | Address receiving the name. |
label | string | Human-readable label (without the TLD). |
labelhash | bytes32 | keccak256(bytes(label)). |
node | bytes32 | namehash(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
Title: DotNS Store Utilities Library
Canonical helpers for protocol writes into per-user LabelStore instances.
One auth rule, one write path. Every DotNS consumer (controller, registrar,
registry, PoP controller) funnels label writes through writeLabel so
authorisation and deploy-on-first-use semantics are identical across flows.
Note: security-contact: admin@parity.io
Functions
ensureLabelStore
Returns the LabelStore for user, deploying one via the factory if absent.
Deploy-on-demand: a user's store is created on their first protocol write so unused accounts never pay the deployment cost. The deploy path is gated by the factory, so callers that are not the factory owner and not protocol-registered
Note: reverts: NotAuthorised when a deployment is required.
function ensureLabelStore(IStoreFactory factory, address user)
internal
returns (address store);
Parameters
| Name | Type | Description |
|---|---|---|
factory | IStoreFactory | The store factory. |
user | address | The user whose label store is being resolved. |
Returns
| Name | Type | Description |
|---|---|---|
store | address | The resolved or newly deployed store address. |
writeLabel
Writes label under labelhash for user, deploying their LabelStore if needed.
Idempotent on locked entries: once a label is locked the call is a no-op rather than a revert, so retried protocol flows (e.g. an ERC721 transfer back to a prior owner) pass through without failing on the existing lock. Inherits the factory's writer authorisation: callers that are not the factory owner and not protocol-registered @custom:reverts NotAuthorised when the user has no store yet.
function writeLabel(
IStoreFactory factory,
address user,
bytes32 labelhash,
string memory label
)
internal
returns (address store);
Parameters
| Name | Type | Description |
|---|---|---|
factory | IStoreFactory | The store factory. |
user | address | The label store owner. |
labelhash | bytes32 | The labelhash key. |
label | string | The label string (typically the full name, e.g. "alice.dot"). |
Returns
| Name | Type | Description |
|---|---|---|
store | address | The resolved or newly deployed store address. |
StringUtils
Title: String Utilities Library
Provides string manipulation utilities for DotNS contracts.
Extends OpenZeppelin's Strings library with additional UTF-8 and conversion helpers.
Note: security-contact: admin@parity.io
Constants
MIN_LITE_SUFFIX_DIGITS
Minimum number of trailing digits required in a lite-person PoP label suffix.
Matches pallet_resources::MIN_LITE_USERNAME_DIGITS to avoid drift between
what People Chain emits and what DotNS accepts.
uint256 internal constant MIN_LITE_SUFFIX_DIGITS = 2
MAX_DNS_LABEL_OCTETS
Maximum number of octets in a single DNS label.
RFC 1035 caps each label at 63 octets. Enforced inside @custom:function _isDnsLabel so every public validator (@custom:function isSingleLabel, @custom:function isNamePath,
Note: function: isSingleDotLiteLabel, @custom:function isLitePersonLabel) inherits the bound and oversized labels never reach the registrar.
uint256 internal constant MAX_DNS_LABEL_OCTETS = 63
Functions
strlen
Computes the character length of a UTF-8 encoded string.
Counts Unicode code points, not bytes. Handles multi-byte UTF-8 sequences:
- 1 byte: 0x00-0x7F (ASCII)
- 2 bytes: 0xC0-0xDF
- 3 bytes: 0xE0-0xEF
- 4 bytes: 0xF0-0xF7
- 5 bytes: 0xF8-0xFB (rare, outside Unicode standard)
- 6 bytes: 0xFC-0xFD (rare, outside Unicode standard)
function strlen(string memory value) internal pure returns (uint256 len);
Parameters
| Name | Type | Description |
|---|---|---|
value | string | The UTF-8 encoded string to measure. |
Returns
| Name | Type | Description |
|---|---|---|
len | uint256 | The number of Unicode characters in the string. |
isSingleLabel
Validates that s is a single canonical DNS label.
Lowercase ASCII letters, digits, and hyphen only; hyphen may not be the first or last
character; length must be in (0, MAX_DNS_LABEL_OCTETS]. No dots allowed; use
Note: function: isNamePath for dotted forms. Mirrors the label rules enforced at the registrar.
function isSingleLabel(string calldata value) internal pure returns (bool isValid);
Parameters
| Name | Type | Description |
|---|---|---|
value | string | Candidate label. |
Returns
| Name | Type | Description |
|---|---|---|
isValid | bool | True if value is a canonical DNS label. |
isSingleLabelMemory
Memory-location helper for @custom:function isSingleLabel, used where the candidate label is produced by an upstream string transformation (e.g. the output of
Note: function: stripDigits) so callers do not need a calldata round-trip.
function isSingleLabelMemory(string memory value) internal pure returns (bool isValid);
Parameters
| Name | Type | Description |
|---|---|---|
value | string | Candidate label held in memory. |
Returns
| Name | Type | Description |
|---|---|---|
isValid | bool | True if value is a canonical DNS label. |
stripDots
Removes dot separators from a dotted label.
Used by the PoP gateway boundary to normalise user-facing
name.path input into the flat label expected by pricing and minting.
function stripDots(string calldata value) internal pure returns (string memory stripped);
Parameters
| Name | Type | Description |
|---|---|---|
value | string | Candidate dotted label. |
Returns
| Name | Type | Description |
|---|---|---|
stripped | string | Label with all dots removed. |
isSingleDotLiteLabel
Validates the gateway-facing lite input shape: stem.suffix.
Requires exactly one dot separator. The left segment must be a canonical DNS label and the right segment must be digits-only with exactly
Note: constant: MIN_LITE_SUFFIX_DIGITS characters.
function isSingleDotLiteLabel(string calldata value) internal pure returns (bool isValid);
Parameters
| Name | Type | Description |
|---|---|---|
value | string | Candidate dotted lite label. |
Returns
| Name | Type | Description |
|---|---|---|
isValid | bool | True when value matches the gateway lite input shape. |
isLitePersonLabel
Validates the lite-person PoP label format: <stem><digits>.
A lite-person label is a single DNS label whose trailing characters are
digits, with at least @custom:constant MIN_LITE_SUFFIX_DIGITS digits at the tail. It
Mirrors @custom:pallet pallet_resources::MIN_LITE_USERNAME_DIGITS. Lite and public
registrations
share the same namespace; the gateway strips any separator before calling
so the on-chain label is a flat DNS label (e.g. alice42). First-to-mint
wins at the ERC721 layer; cross-flow priority on the stripped stem is
arbitrated by @custom:function IPopRules.reserveBaseNameForPop. Keeping a single
namespace avoids the ambiguity dotli/dweb would otherwise see between
andrew.47 (lite) and andrew owning 47 as a subname.
Note: constant: MIN_LITE_SUFFIX_DIGITS trailing digits.
function isLitePersonLabel(string calldata value) internal pure returns (bool isValid);
Parameters
| Name | Type | Description |
|---|---|---|
value | string | Candidate label. |
Returns
| Name | Type | Description |
|---|---|---|
isValid | bool | True if the label is a DNS label with at least |
isLitePersonLabelMemory
Memory-location helper for @custom:function isLitePersonLabel, used by controller-side normalisation paths.
Note: constant: MIN_LITE_SUFFIX_DIGITS trailing digits.
function isLitePersonLabelMemory(string memory value) internal pure returns (bool isValid);
Parameters
| Name | Type | Description |
|---|---|---|
value | string | Candidate label held in memory. |
Returns
| Name | Type | Description |
|---|---|---|
isValid | bool | True if the label is a DNS label with at least |
_isLitePersonLabel
function _isLitePersonLabel(bytes memory raw) private pure returns (bool isValid);
isNamePath
Validates that s is a dot-separated path of canonical DNS labels.
Each segment between dots must satisfy @custom:function isSingleLabel. Empty
segments (leading, trailing, or consecutive dots) fail. Used when
callers submit multi-label paths (e.g. alice.dot) rather than
bare labels.
function isNamePath(string calldata value) internal pure returns (bool isValid);
Parameters
| Name | Type | Description |
|---|---|---|
value | string | Candidate name path. |
Returns
| Name | Type | Description |
|---|---|---|
isValid | bool | True if every dot-separated segment is a canonical DNS label. |
_isDnsLabel
function _isDnsLabel(
bytes memory label,
uint256 start,
uint256 end
)
private
pure
returns (bool isValid);
uintToString
Converts a uint256 to its decimal string representation.
Wraps OpenZeppelin's Strings.toString().
function uintToString(uint256 value) internal pure returns (string memory);
Parameters
| Name | Type | Description |
|---|---|---|
value | uint256 | The unsigned integer to convert. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | string | The decimal string representation. |
addressToHex
Converts an address to its checksummed hexadecimal string representation.
Wraps OpenZeppelin's Strings.toHexString(). Returns lowercase hex with "0x" prefix.
function addressToHex(address account) internal pure returns (string memory);
Parameters
| Name | Type | Description |
|---|---|---|
account | address | The address to convert. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | string | The hexadecimal string representation (42 characters including "0x"). |
bytes32ToString
Converts a bytes32 value to a string, treating it as a null-terminated ASCII string.
Reads bytes until the first null byte (0x00) or end of bytes32. Useful for converting short strings stored in bytes32 back to string type.
function bytes32ToString(bytes32 _bytes32) internal pure returns (string memory);
Parameters
| Name | Type | Description |
|---|---|---|
_bytes32 | bytes32 | The bytes32 value containing a null-terminated ASCII string. |
Returns
| Name | Type | Description |
|---|---|---|
<none> | string | The extracted string (up to 32 characters). |