DotnsRegistrar

Git Source

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

NameTypeDescription
iduint256
owneraddress
labelstringThe 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 tokenId with

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

NameTypeDescription
versionStringstringCurrent 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;