DotnsRegistrar
Inherits: Initializable, UUPSUpgradeable, OwnableUpgradeable, ERC721Upgradeable, IDotnsRegistrar
Title: dotNS Registrar
ERC721-backed registrar implementing permanent name ownership.
Deliberately policy-free on pricing, reservations, and PoP gating; those live in the
controllers and @custom:contract IPopRules. The registrar owns transferability itself: publicly
registered names transfer freely, while names minted through the dotNS gateway pallet are
soulbound and revert on transfer. Transfers carry no fee. The _update hook enforces the
soulbound gate and keeps the escrow position with the holder.
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
_soulbound
Marks a token as soulbound: minted through the gateway pallet and non-transferable.
Set at mint by @custom:function register when the caller is the address registered
under DotnsConstants.POP_CONTROLLER. Write-once and never cleared: a name's soulbound
state is fixed at registration. Read by the _update transfer gate.
mapping(uint256 tokenId => bool soulbound) private _soulbound
__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(
address initialOwner,
string calldata name,
string calldata symbol,
IDotnsProtocolRegistry registry
)
external
initializer;
Parameters
| Name | Type | Description |
|---|---|---|
initialOwner | address | Address that owns the contract once initialised. |
name | string | |
symbol | string | |
registry | IDotnsProtocolRegistry |
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, gateway path,
future variants) without per-flavour setters. Owner-gated (otherwise
Note: reverts: OwnableUnauthorizedAccount); emits @custom:emits ControllerAdded on success.
function addController(IDotnsController controller) external onlyOwner;
removeController
Removes an authorised controller.
Mirrors the @custom:function addController baseline-typed signature so any registered controller can be revoked through the same entry point. Owner-gated (otherwise
Note: reverts: OwnableUnauthorizedAccount); emits @custom:emits ControllerRemoved on success.
function removeController(IDotnsController controller) external onlyOwner;
available
Returns whether a registration call may proceed for id.
Signals two distinct paths to the controller. Returns true when the owner slot is
empty (a fresh @custom:function register call may mint) OR when the current owner is
the configured escrow and the released position's redeem window has elapsed (the
controller must then route through @custom:function IDotnsNameEscrow.reclaim instead of
Note:
function: register, because register calls _mint which rejects existing
tokens). All other holders return false. The controller distinguishes the two true
cases via @custom:function exists.
Escrow custody inside the redeem window returns false: that window belongs to the
previous holder, who may still @custom:function IDotnsNameEscrow.redeem the name, and
reclaim would revert until it elapses. Clients wanting the exact moment a released name
becomes registrable should read redeemableUntil from
function available(uint256 id) public view override returns (bool isAvailable);
register
Registers a name permanently.
Permanence is by construction: there is no expire, renew, or release path on
the registrar. Custody only moves via ERC721 transfers (which the registrar polices via
the fee-on-transfer hook) or via escrow reclaim. Restricted to authorised controllers
(otherwise @custom:reverts NotController) and rejects ids that are not available
(otherwise @custom:reverts NameNotAvailable). Emits @custom:emits NameRegistered on
success.
Note: function: isSoulbound). Provenance is read from the protocol registry at mint time, so no other authorised controller can mint a soulbound name and the PoP controller cannot mint an unlocked one. Public registrations from any other controller stay transferable.
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);
version
Returns the release this network declares it runs, read live from the protocol registry so every dotNS contract reports one synchronised value.
Mirror of IDotnsProtocolRegistry.protocolVersion, kept under the historical
version() selector for ABI compatibility. It reports the network's declaration,
not this contract's build; per-contract identity is the codehash declared on the
registry.
function version() external view virtual returns (string memory versionString);
Returns
| Name | Type | Description |
|---|---|---|
versionString | string | Declared release as bare semver, empty when never declared. |
exists
Returns whether a given token id has been minted.
function exists(uint256 tokenId) external view override returns (bool tokenExists);
isSoulbound
Returns whether a token is soulbound (PoP-gateway minted and non-transferable).
Durable on-chain marker set once at mint by @custom:function register and never
cleared. A true result means every transfer overload reverts with
Note: reverts: NameSoulbound.
function isSoulbound(uint256 tokenId) external view override returns (bool soulbound);
Parameters
| Name | Type | Description |
|---|---|---|
tokenId | uint256 | The name's token id. |
Returns
| Name | Type | Description |
|---|---|---|
soulbound | bool | True when the name was minted through the gateway pallet. |
_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);
_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;
_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;