DotnsPopController

Git Source

Inherits: Initializable, UUPSUpgradeable, OwnableUpgradeable, ERC165Upgradeable, IDotnsPopController

Title: DotnsPopController

Dedicated PoP controller orchestrating lite-person and full-person username issuance on behalf of the PoP gateway pallet.

Lives behind its own UUPS proxy with its own storage. Registered on DotnsRegistrar via addController, which is how multiple controllers coexist on the same registrar without interfering with each other. Enforcement: Personhood is attested off-chain by the gateway pallet before the call reaches this contract, so the on-chain personhood precompile is not re-queried on the gateway path. Every base-label mint path still calls @custom:function IPopRules.classifyName to reject governance-reserved labels (@custom:reverts InvalidBaseLabel on the base path,

Notes:

  • reverts: InvalidLiteLabel on the lite path). The lite leg accepts any two-digit lite label whose stem is not governance-reserved, regardless of stem length. Native-token pricing is bypassed entirely; the gateway pays no rent. Decoupling: This contract does not import or call IDotnsRegistrarController. The public commit-reveal controller is equally unaware of this one. Cross-flow collision handling relies on two distinct properties, neither of which requires the two controllers to know about each other: (1) Lite-person labels (NAMEXX) share the public namespace: they are just DNS labels with exactly two trailing digits. First-to-mint wins at the ERC721 layer, so a lite-user and a public registrant cannot hold the same flat label simultaneously. Keeping one namespace removes the ambiguity downstream tooling (dotli, dweb) would see with a separate separator form. (2) Base-name reservations are synchronised into IPopRules. The head of this controller's reservation queue is written through IPopRules.reserveBaseNameForPop on every head transition; the slot is cleared through IPopRules.releaseBaseName when the queue empties (claim, final relinquish, final expiry). The public commit-reveal controller routes through IPopRules.priceWithCheck, which rejects any registration targeting a base-name stem reserved for another user, so the public flow respects gateway reservations without ever importing this contract. PopRules is the single cross-flow authority; the queue here is the intra-PoP ordering layer on top of it. Shared primitives: labelhash / namehash via @custom:contract LabelUtils; the mint + forward-registry + store-write triad via @custom:contract RegistrationUtils; chat-key and lite-to-full link persistence via

  • contract: IDotnsPopResolver. Keeping per-name records on the resolver preserves the "Store = labels only" invariant.

  • security-contact: admin@parity.io

Constants

MAX_RESERVATION_QUEUE

Upper bound for the number of simultaneously queued reservations per label.

Keeps expireReservation gas bounded.

uint16 public constant MAX_RESERVATION_QUEUE = 64

MIN_RESERVATION_DURATION

Minimum value accepted by @custom:function setReservationDuration.

Prevents owner misconfiguration from instantly expiring every live queue and pending-claim entry. The actual production duration is governance-tuned higher.

uint64 public constant MIN_RESERVATION_DURATION = 1 hours

CHAT_KEY_LENGTH

Required byte length for a non-empty chat key.

Mirrors @custom:contract IDotnsPopResolver InvalidChatKeyLength so the controller can fail closed before the mint instead of bubbling the resolver's revert after partial state has been committed.

uint256 private constant CHAT_KEY_LENGTH = 65

SELECTOR_RESERVE_LITE

Selector for the typed @custom:function reserveLiteName overload.

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

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

SELECTOR_RESERVE_BASE

Selector for the typed @custom:function reserveBaseName overload.

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

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

SELECTOR_RESERVE_BASE_ONLY

Selector for the typed reservation-only gateway primitive.

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

SELECTOR_REGISTER_BASE

Selector for the typed @custom:function registerBaseName overload.

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

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

State Variables

protocolRegistry

Protocol-level address registry for all DotNS contracts.

IDotnsProtocolRegistry public protocolRegistry

_reservationMeta

Per-label queue metadata (head/tail pointers).

mapping(bytes32 labelhash => ReservationQueueMeta meta) internal _reservationMeta

_reservationEntries

Per-label sparse entries keyed by monotonically-increasing index.

mapping(bytes32 labelhash => mapping(uint64 index => ReservationEntry entry)) internal
    _reservationEntries

_userReservations

Single per-user pointer into the reservation queues.

Keeps per-user reservation data behind one key and one struct value so callers read both fields in one call instead of two.

mapping(address user => UserReservation reservation) internal _userReservations

_reservedBaseLabel

Remembers the base-label string for each reserved labelhash so the PopRules sync path can address the reservation by its original string form (PopRules keys its reservations mapping by string).

Populated on first enqueue for a label, cleared when the queue empties. Exists only to bridge the queue's bytes32 key space to PopRules' string key space; nothing else reads it.

mapping(bytes32 labelhash => string baseLabel) internal _reservedBaseLabel

reservationDuration

Duration (in seconds) after which a reservation entry is considered expired.

Mirrors pallet_resources::UsernameReservationDuration. Configurable by governance via setReservationDuration.

uint64 public reservationDuration

_pendingClaimUsers

Enumeration set of users holding at least one pending claim.

Membership equals the set of users with a non-empty queue. Used by pendingClaimUserCount and pendingClaimUsers for paginated enumeration.

EnumerableSet.AddressSet private _pendingClaimUsers

_pendingClaimQueue

Per-user pile of deferred names awaiting a LabelStore.

The Root gateway origin cannot deploy a LabelStore (contract creation is forbidden from Root), so deferred names accumulate here until a signed-origin

Notes:

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

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

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

__gap

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

uint256[50] private __gap

Functions

onlyGateway

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

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

modifier onlyGateway() ;

constructor

Note: oz-upgrades-unsafe-allow: constructor

constructor() ;

initialize

Initialises the PoP controller.

Called once through the UUPS proxy; _disableInitializers on the implementation makes direct calls revert with @custom:reverts InvalidInitialization, and any nested call outside an active initialiser scope reverts with @custom:reverts NotInitializing. Emits @custom:emits ReservationDurationSet so indexers observe the initial value through the same event the setter uses later.

function initialize(
    IDotnsProtocolRegistry registry,
    uint64 reservationDuration_
)
    external
    initializer;

reserveLiteName

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

Callable only via the registered PoP gateway (otherwise @custom:reverts NotGateway); the gateway is responsible for asserting substrate Root authority before forwarding here. The supplied label must satisfy the dotted stem.NN shape and the flattened label must classify as PopLite (otherwise @custom:reverts InvalidLiteLabel); a supplied chat key whose length is neither zero nor CHAT_KEY_LENGTH reverts

Notes:

  • reverts: InvalidChatKey before mint and resolver writes run. On a warm-path mint emits @custom:emits LiteNameReserved and @custom:emits NameRegistered. On a cold-path mint emits @custom:emits LiteNameReserved and @custom:emits PendingClaimStashed, with

  • emits: NameRegistered deferred to @custom:function claimLabelStore when the user settles. Cross-chain callers pass the ABI-encoded lite-registration tuple as the call's payload, which Solidity decodes directly.

function reserveLiteName(LiteRegistration calldata params) external override onlyGateway;

Parameters

NameTypeDescription
paramsLiteRegistrationRegistration request; see @custom:struct LiteRegistration.

reserveLiteName

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

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

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

function reserveLiteName(bytes calldata payload) external override onlyGateway;

Parameters

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

reserveBaseName

Registers a lite-person username on behalf of the supplied user and optionally enqueues a reservation for a base name they intend to claim as a full person later.

Callable only via the registered PoP gateway (otherwise @custom:reverts NotGateway); the gateway is responsible for asserting substrate Root authority before forwarding here. The lite leg validates the dotted stem.NN shape and requires the flattened label to classify as PopLite (otherwise @custom:reverts InvalidLiteLabel), and rejects a supplied chat key whose length is neither zero nor CHAT_KEY_LENGTH (otherwise @custom:reverts InvalidChatKey). On a warm-path mint (user already has a LabelStore) it emits @custom:emits LiteNameReserved and @custom:emits NameRegistered; on a cold-path mint it emits @custom:emits LiteNameReserved and

Notes:

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

  • function: claimLabelStore when the user settles. The base-name leg only runs when reservedBaseLabel is non-empty: it validates the DNS-label shape and requires a true base label with no trailing digits (otherwise @custom:reverts InvalidBaseLabel) before any queue mutation so a bad reservation never touches the queue, advances the head past expired entries (emitting @custom:emits ReservationExpired for each one), removes the user from any prior queue position so a single user holds at most one live reservation across all labels, and enqueues a fresh entry (emitting

function reserveBaseName(BaseReservation calldata params) external override onlyGateway;

Parameters

NameTypeDescription
paramsBaseReservationReservation request; see @custom:struct BaseReservation.

reserveBaseName

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

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

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

function reserveBaseName(bytes calldata payload) external override onlyGateway;

Parameters

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

reserveBaseNameOnly

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

Callable only via the registered PoP gateway. This is the second step of the split gateway flow: @custom:function reserveLiteName mints the lite username first, then this function reserves the full/base label in a separate transaction so proof-size stays below per-call limits. Reverts with @custom:reverts InvalidBaseLabel when the label is empty, non-canonical, digit-suffixed, or governance-reserved. The caller remains agnostic about backend batching; it simply exposes a small retryable primitive.

function reserveBaseNameOnly(BaseNameReservation calldata params)
    external
    override
    onlyGateway;

Parameters

NameTypeDescription
paramsBaseNameReservationReservation request; see @custom:struct BaseNameReservation.

reserveBaseNameOnly

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

function reserveBaseNameOnly(bytes calldata payload) external override onlyGateway;

_reserveLite

Lite-only mint shared by @custom:function reserveLiteName and the lite leg of @custom:function reserveBaseName.

Gateway attestation is the authority for personhood on this path; the on-chain precompile is not consulted. The dotted-format check accepts only stem.NN, then PopRules classification must place the flattened label outside the governance-reserved tier before minting; any non-reserved two-digit lite label is accepted regardless of stem length. Takes the @custom:struct LiteRegistration struct directly so both call sites pass the same payload shape: the typed entrypoint forwards its own params, the reserveBaseName entrypoint forwards params.lite.

function _reserveLite(IPopRules rules, LiteRegistration calldata params) internal;

registerBaseName

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

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

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

function registerBaseName(bytes calldata payload) external override onlyGateway;

Parameters

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

registerBaseName

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

Callable only via the registered PoP gateway (otherwise @custom:reverts NotGateway); the gateway is responsible for asserting substrate Root authority before forwarding here. The base label must satisfy the DNS-label shape and be a true base label with no trailing digits (otherwise @custom:reverts InvalidBaseLabel), and the label must not classify as governance-reserved (otherwise @custom:reverts InvalidBaseLabel). The gateway also defers to PopRules as the single cross-flow authority: when PopRules carries a live base-name slot held by another user (stamped by the public commit-reveal flow or this controller's prior queue head), the call reverts @custom:reverts NotHolder before any queue mutation. Two orthogonal axes drive the state machine. The reservation axis treats the user as claiming if and only if they hold the live head-of-queue reservation on the base label: a claim wipes the entire queue, releases the PopRules slot, and emits @custom:emits BaseNameClaimed; a non-claim silently relinquishes any pending entry the user holds and emits @custom:emits StandaloneNameRegistered. Advancing the queue head past expired entries emits @custom:emits ReservationExpired for each one. The chat-key axis selects whether a fresh key is persisted on the resolver or the new entry inherits its key from a prior lite-person username. The fresh-key branch rejects a chat key whose length is neither zero nor CHAT_KEY_LENGTH (otherwise

Notes:

  • reverts: InvalidChatKey). The LiteUsername branch validates the lite label's NAMEXX shape (otherwise @custom:reverts InvalidLiteLabel), requires the registrant to own the lite token (otherwise @custom:reverts LiteLabelNotOwnedByUser), reads the lite node's chat key from the resolver and copies it across; if the lite node carries no chat key the inherited value is empty and the full node's chat-key write is silently skipped (the LiteToFullLinked event still fires). Emits @custom:emits LiteToFullLinked alongside the registration event. On a warm-path mint the event order is

  • emits: NameRegistered first (from the inner mint), then

function registerBaseName(FullRegistration calldata params) external override onlyGateway;

Parameters

NameTypeDescription
paramsFullRegistrationRegistration request; see @custom:struct FullRegistration.

expireReservation

Permissionlessly removes expired entries from the head of a reservation queue.

Permissionless on purpose: anyone (typically a UI or a bot) can poke a stale queue so the next live head takes over without waiting for the next gateway call. Validates the DNS-label shape of reservedBaseLabel (otherwise @custom:reverts InvalidBaseLabel) and emits @custom:emits ReservationExpired for every expired entry reaped from the head. Only base-shaped labels (no trailing digits) ever key a reservation queue, so a lite-shaped label still passes the shape check but resolves to an empty queue and the call is a no-op.

function expireReservation(string calldata reservedBaseLabel) external override;

relinquishReservation

Lets the caller voluntarily drop their own active reservation.

Reverts with @custom:reverts NoActiveReservation when the caller holds no live reservation. On success the caller's entry is removed from its queue and

Note: emits: ReservationRelinquished is emitted; if the removed entry was the queue head, head advancement may additionally emit @custom:emits ReservationExpired for any stale entries reaped behind it.

function relinquishReservation() external override;

claimLabelStore

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

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

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

function claimLabelStore() external override;

claimLabelStoreFor

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

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

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

function claimLabelStoreFor(address user) external override onlyGateway;

Parameters

NameTypeDescription
useraddressAccount whose pending claim should be settled.

_claimLabelStoreFor

function _claimLabelStoreFor(address user) internal;

_settlePendingLabel

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

Deferring the deploy to the first settled label means a pile of only-lapsed entries never leaves a fresh store behind with nothing written. Returns the (possibly newly deployed) store so the caller threads it through the remaining entries.

function _settlePendingLabel(
    IStoreFactory factory,
    address store,
    address user,
    string memory label
)
    internal
    returns (address);

expirePendingClaim

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

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

function expirePendingClaim(address user) external override;

Parameters

NameTypeDescription
useraddressAddress whose pending claims are being swept.

isReservedForClaim

Returns whether a label currently has a live reservation at the queue head.

Validates the DNS-label shape of reservedBaseLabel (otherwise

Note: reverts: InvalidBaseLabel) before inspecting the queue.

function isReservedForClaim(string calldata reservedBaseLabel)
    external
    view
    override
    returns (bool reserved, address holder);

setReservationDuration

Updates the reservation duration used to decide when queue entries expire.

Owner-gated (otherwise @custom:reverts OwnableUnauthorizedAccount); emits

Note: emits: ReservationDurationSet on success.

function setReservationDuration(uint64 duration) external override onlyOwner;

reservationMeta

Returns the queue metadata (head, tail) for labelhash.

Read-only accessor over the per-label reservation queue. head == tail means the queue is empty; active entries occupy [head, tail). Exposed on the interface because invariant tests and off-chain consumers (dotli, dweb) use it to enumerate live queue state without scanning storage.

function reservationMeta(bytes32 labelhash)
    external
    view
    override
    returns (uint64 head, uint64 tail);

Parameters

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

Returns

NameTypeDescription
headuint64Index of the live queue head.
tailuint64Index one past the last queued entry.

reservationEntry

Returns the queue entry at index for labelhash.

Sparse storage: a zero entryOwner means the slot was relinquished, expired and reaped, or never written. Callers pair this with @custom:function reservationMeta to walk the live window [head, tail).

function reservationEntry(
    bytes32 labelhash,
    uint64 index
)
    external
    view
    override
    returns (address entryOwner, uint64 joinedAt);

Parameters

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

Returns

NameTypeDescription
entryOwneraddressOwner of the slot (zero if empty/relinquished).
joinedAtuint64Timestamp the entry was enqueued (only meaningful when entryOwner != address(0)).

userReservation

Returns user's current reservation pointer.

A zero labelhash on the returned struct means the user holds no reservation; index is meaningful only when labelhash is non-zero.

function userReservation(address user)
    external
    view
    override
    returns (UserReservation memory reservation);

Parameters

NameTypeDescription
useraddressAccount whose reservation pointer is being read.

Returns

NameTypeDescription
reservationUserReservationPer-user reservation pointer; see @custom:struct UserReservation.

pendingClaims

Returns user's pending-claim entries.

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

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

Parameters

NameTypeDescription
useraddressAccount whose pending claims are being read.

Returns

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

pendingClaimUserCount

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

Exact live count, not an all-time tally: fully settled and fully expired users are removed from the enumeration set so off-chain consumers can page through every stalled user without filtering.

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

Returns

NameTypeDescription
countuint256Number of users currently holding a pending claim.

pendingClaimUsers

Returns a paginated slice of users with at least one live pending claim.

Pair with @custom:function pendingClaims to read each user's stashed entries. Ordering is not chronological; callers MUST NOT assume mintedAt is monotonic across the slice. Returns an empty array when offset is past the live count.

function pendingClaimUsers(
    uint256 offset,
    uint256 limit
)
    external
    view
    override
    returns (address[] memory users);

Parameters

NameTypeDescription
offsetuint256Start index.
limituint256Maximum entries to return.

Returns

NameTypeDescription
usersaddress[]Slice of users currently holding a pending claim.

supportsInterface

function supportsInterface(bytes4 interfaceId)
    public
    view
    override(ERC165Upgradeable, IERC165)
    returns (bool);

version

Returns implementation version.

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

Returns

NameTypeDescription
versionStringstringCurrent version string.

_completeGatewayRegistration

Mints a name, wires forward registry, persists PoP-flow records (chat key, lite link) on the PoP resolver, and either writes the label into the owner's existing LabelStore or stashes a pending claim when the owner has none yet.

The mint + forward-registry pair is delegated to

Notes:

  • function: RegistrationUtils.registerAndStore so this flow and the public commit-reveal flow share exactly one implementation of that sequence. The label is passed empty so the registrar does not deploy a LabelStore; substrate Root cannot run the LabelStore constructor under pallet-revive. PoP-flow per-name records (chat key, lite link) are persisted eagerly on @custom:contract IDotnsPopResolver here, before the label is written, so the resolver carries the full identity record from mint time regardless of whether the owner already has a LabelStore. The Store stays labels-only. Warm path emits @custom:emits NameRegistered immediately; the cold path emits @custom:emits PendingClaimStashed at mint and defers

  • emits: NameRegistered to @custom:function claimLabelStore when the user settles.

function _completeGatewayRegistration(
    address user,
    string memory label,
    bytes32 labelhash,
    bytes32 node,
    bytes memory chatKeyBytes,
    bytes32 liteLabelhash
)
    internal;

_writeRecord

Writes a name's label into store.

Single canonical persistence step shared by the warm gateway path and the user-signed @custom:function claimLabelStore. The store key is node, matching the registrar's _writeOwnerLabel convention. Idempotent on already-locked slots so a user whose store was pre-populated under the same node (e.g. by a sibling protocol flow) can still settle their pending claim without bricking on LabelAlreadyExists.

function _writeRecord(address store, bytes32 node, string memory label) internal;

Parameters

NameTypeDescription
storeaddressOwner's LabelStore proxy.
nodebytes32namehash(labelhash) for the entry.
labelstringBare DNS label (no TLD); the TLD is appended on write.

_stashPendingClaim

Appends a deferred binding for user and adds them to the enumeration set.

The Root gateway origin cannot deploy the user's LabelStore, so deferred names pile up in _pendingClaimQueue until a signed-origin @custom:function claimLabelStore settles them. Adding the user to the set is idempotent, so repeat stashes keep a single enumeration entry. Emits @custom:emits PendingClaimStashed.

function _stashPendingClaim(address user, string memory label, bytes32 labelhash) internal;

_clearPendingClaim

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

function _clearPendingClaim(address user) internal;

_isExpired

Returns whether a queue entry is expired relative to block.timestamp.

function _isExpired(uint64 joinedAt) internal view returns (bool);

_enqueueReservation

Appends a new reservation entry to the tail of the queue for labelhash.

Reverts if the queue is full or the user already holds a reservation. When the enqueued entry is the new head of an empty queue, the controller also reserves the base name on PopRules so the public commit-reveal flow sees the reservation through its existing priceWithCheck guard. Subsequent waiters only live in the local queue until they are promoted.

function _enqueueReservation(
    IPopRules rules,
    bytes32 labelhash,
    string memory baseLabel,
    address user
)
    internal;

_clearQueue

Wipes the entire reservation queue for labelhash and releases the corresponding PopRules reservation.

Used when a holder claims their reservation: every waiter is evicted and their per-user tracking state is cleared, and PopRules is told the slot is free so future public registrations are unblocked (the claim itself just minted the name, so there is nothing left to reserve).

function _clearQueue(bytes32 labelhash) internal;

_advanceExpiredHead

Advances the queue head past every expired entry at the head of the queue.

Reset semantics matter: when the queue empties (head catches tail), the meta slot is deleted AND the PopRules base-name slot is released, so the public commit-reveal flow can register the label again. When a new live head emerges, PopRules is re-synced to that head so reservations cannot be paid around by another address. Emits

Note: emits: ReservationExpired once per expired entry reaped from the head.

function _advanceExpiredHead(bytes32 labelhash) internal;

_removeUserFromQueue

Removes user from whichever reservation queue they currently occupy.

For a head removal, we delete the entry without bumping meta.head and delegate the advance to _advanceExpiredHead. Its existing zero-owner skip walks past the freshly-deleted slot, and its head != meta.head branch fires the PopRules resync in the one place head promotion is actually handled. Non-head removals leave the queue shape intact, so no advance or resync is needed.

function _removeUserFromQueue(address user) internal;

_validateLiteLabel

Validates a lite-person NAMEXX label and derives (labelhash, node).

function _validateLiteLabel(string memory liteLabel)
    internal
    pure
    returns (bytes32 labelhash, bytes32 node);

_validateBaseLabel

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

function _validateBaseLabel(string calldata baseLabel)
    internal
    pure
    returns (bytes32 labelhash, bytes32 node);

_requireValidChatKey

Reverts when a non-empty chat key is not exactly CHAT_KEY_LENGTH bytes.

Mirrors the resolver's own length gate so the gateway sees a controller-local InvalidChatKey revert before any mint state is written.

function _requireValidChatKey(bytes memory chatKey) internal pure;

_popResolver

Resolves the PoP resolver via the protocol registry.

function _popResolver() internal view returns (IDotnsPopResolver);

_popRules

Resolves the PopRules contract via the protocol registry.

function _popRules() internal view returns (IPopRules);

_storeFactory

Resolves the Store factory via the protocol registry.

function _storeFactory() internal view returns (IStoreFactory);

_registrar

Resolves the registrar via the protocol registry.

function _registrar() internal view returns (IDotnsRegistrar);

_syncPopRulesToHead

Writes the new head of the queue into PopRules so the public commit-reveal flow rejects registrations of this base name for anyone other than newHead.

Callers guarantee newHead is non-zero (the queue holds a live entry) and that _reservedBaseLabel[labelhash] is non-empty (any non-empty queue had its first head write the slot). The release-then-reserve pair satisfies PopRules' ownership gate on reserveBaseNameForPop.

function _syncPopRulesToHead(bytes32 labelhash, address newHead) internal;

_releasePopRulesSlot

Clears the PopRules slot and the local label bookkeeping when the queue empties (claim, last-relinquish, last-expire).

function _releasePopRulesSlot(bytes32 labelhash) internal;

_onlyGateway

Internal check enforcing PoP-gateway-only access.

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

function _onlyGateway() internal view;

_dispatchTyped

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

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

Note: oz-upgrades-unsafe-allow: delegatecall

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

_authorizeUpgrade

Function that should revert when msg.sender is not authorized to upgrade the contract. Called by {upgradeToAndCall}. Normally, this function will use an xref:access.adoc[access control] modifier such as {Ownable-onlyOwner}.

function _authorizeUpgrade(address) internal onlyOwner {}
function _authorizeUpgrade(address newImplementation) internal override onlyOwner;