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;