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;