Skip to main content

Overview

PermissionedRegistry is a core contract that implements a tokenized, permission-based registry for ENS names. It combines ERC1155 token functionality with enhanced access control to manage name registrations, permissions, and hierarchical relationships. The contract uses a state machine model where names can be:
  • AVAILABLE: Not registered, can be registered
  • RESERVED: Registered without an owner (placeholder state)
  • REGISTERED: Fully registered with an owner and roles

Key Features

  • ERC1155-based tokenization of names
  • Role-based access control for name management
  • Expiry-based registration system
  • Hierarchical parent-child registry relationships
  • Automatic token regeneration on role changes
  • Support for subregistries and resolvers

Contract Information

Inherits: IRegistry, ERC1155Singleton, EnhancedAccessControl, IPermissionedRegistry, MetadataMixin Interface ID: 0xafff3a63

State Structure

Entry

Internal storage structure for each registered name:
eacVersionId
uint32
Version ID for Enhanced Access Control. Incremented when the name is unregistered/burned.
tokenVersionId
uint32
Version ID for the ERC1155 token. Incremented when roles change or token is regenerated.
subregistry
IRegistry
The subregistry contract address for this name, if any.
expiry
uint64
Unix timestamp when the registration expires.
resolver
address
The resolver contract address for this name.

Status

Enum representing registration status:
  • AVAILABLE (0): Name is not registered or has expired
  • RESERVED (1): Name is registered but has no owner (expiry set, owner is address(0))
  • REGISTERED (2): Name is fully registered with an owner

State

Complete state information for a name:
status
Status
Current registration status (AVAILABLE, RESERVED, or REGISTERED)
expiry
uint64
Registration expiration timestamp
latestOwner
address
Current owner address, or address(0) if AVAILABLE/RESERVED
tokenId
uint256
Current token ID (includes version)
resource
uint256
Current resource ID for access control

Constructor

constructor(
    IHCAFactoryBasic hcaFactory,
    IRegistryMetadata metadata,
    address ownerAddress,
    uint256 ownerRoles
)
hcaFactory
IHCAFactoryBasic
HCA (Hierarchical Contract Address) factory for equivalence checking
metadata
IRegistryMetadata
Metadata provider for token URIs
ownerAddress
address
Initial owner address to grant roles to
ownerRoles
uint256
Role bitmap to grant to the initial owner

Registration Functions

register

Registers a new name or converts a reserved name to registered.
function register(
    string memory label,
    address owner,
    IRegistry registry,
    address resolver,
    uint256 roleBitmap,
    uint64 expiry
) public virtual returns (uint256 tokenId)
label
string
The label to register (e.g., “alice” for alice.eth)
owner
address
The address that will own the name. Use address(0) to create a RESERVED name.
registry
IRegistry
The subregistry contract to set for this name
resolver
address
The resolver contract address
roleBitmap
uint256
Roles to grant to the owner. Must be 0 if owner is address(0).
expiry
uint64
Expiration timestamp. For RESERVED names, use 0 to keep current expiry.
tokenId
uint256
The token ID of the registered name
Requirements:
  • Label must be valid size (1-255 bytes)
  • If AVAILABLE: requires ROLE_REGISTRAR on root resource
  • If RESERVED: requires ROLE_REGISTER_RESERVED on root resource
  • Cannot overwrite REGISTERED names
  • Expiry must be in the future
  • If owner is address(0), roleBitmap must be 0
Events Emitted:
  • NameRegistered (if owner != address(0))
  • NameReserved (if owner == address(0))
  • TokenResource
  • SubregistryUpdated (if registry != address(0))
  • ResolverUpdated (if resolver != address(0))
Example:
// Register a new name
uint256 tokenId = registry.register(
    "alice",
    0x1234567890123456789012345678901234567890,
    IRegistry(address(0)),
    0xResolverAddress,
    RegistryRolesLib.ROLE_SET_RESOLVER | RegistryRolesLib.ROLE_RENEW,
    block.timestamp + 365 days
);

// Reserve a name (no owner)
uint256 reservedId = registry.register(
    "reserved",
    address(0),
    IRegistry(address(0)),
    address(0),
    0,
    block.timestamp + 365 days
);

unregister

Deletes a registered or reserved name, setting its expiry to the current timestamp.
function unregister(uint256 anyId) public virtual
anyId
uint256
The labelhash, token ID, or resource ID of the name to unregister
Requirements:
  • Name must not be expired
  • Caller must have ROLE_UNREGISTER permission
Events Emitted:
  • NameUnregistered
Example:
uint256 labelId = uint256(keccak256("alice"));
registry.unregister(labelId);

renew

Extends the expiration time of a registered or reserved name.
function renew(uint256 anyId, uint64 newExpiry) public override
anyId
uint256
The labelhash, token ID, or resource ID
newExpiry
uint64
The new expiration timestamp (must be greater than current expiry)
Requirements:
  • Name must not be expired
  • Caller must have ROLE_RENEW permission
  • New expiry must be greater than current expiry
Events Emitted:
  • ExpiryUpdated
Example:
uint256 labelId = uint256(keccak256("alice"));
registry.renew(labelId, block.timestamp + 730 days);

Configuration Functions

setSubregistry

Sets or updates the subregistry for a name.
function setSubregistry(uint256 anyId, IRegistry registry) public virtual
anyId
uint256
The labelhash, token ID, or resource ID
registry
IRegistry
The new subregistry contract address
Requirements:
  • Name must not be expired
  • Caller must have ROLE_SET_SUBREGISTRY permission
Events Emitted:
  • SubregistryUpdated
Example:
uint256 labelId = uint256(keccak256("alice"));
registry.setSubregistry(labelId, IRegistry(newSubregistryAddress));

setResolver

Sets or updates the resolver for a name.
function setResolver(uint256 anyId, address resolver) public virtual
anyId
uint256
The labelhash, token ID, or resource ID
resolver
address
The new resolver contract address
Requirements:
  • Name must not be expired
  • Caller must have ROLE_SET_RESOLVER permission
Events Emitted:
  • ResolverUpdated
Example:
uint256 labelId = uint256(keccak256("alice"));
registry.setResolver(labelId, newResolverAddress);

setParent

Sets the canonical parent registry and label for this registry.
function setParent(
    IRegistry parent,
    string memory label
) public virtual
parent
IRegistry
The parent registry contract
label
string
The label of this registry in the parent (e.g., “eth”)
Requirements:
  • Caller must have ROLE_SET_PARENT on root resource
Events Emitted:
  • ParentUpdated
Example:
registry.setParent(IRegistry(parentRegistryAddress), "eth");

Access Control Functions

grantRoles

Grants roles to an account for a specific name.
function grantRoles(
    uint256 anyId,
    uint256 roleBitmap,
    address account
) public override returns (bool)
anyId
uint256
The labelhash, token ID, or resource ID
roleBitmap
uint256
Bitmap of roles to grant
account
address
The address to grant roles to
success
bool
Returns true if roles were granted
Note: Admin roles can only be assigned during registration, not via grantRoles. Example:
uint256 labelId = uint256(keccak256("alice"));
registry.grantRoles(
    labelId,
    RegistryRolesLib.ROLE_SET_RESOLVER,
    delegateAddress
);

revokeRoles

Revokes roles from an account for a specific name.
function revokeRoles(
    uint256 anyId,
    uint256 roleBitmap,
    address account
) public override returns (bool)
anyId
uint256
The labelhash, token ID, or resource ID
roleBitmap
uint256
Bitmap of roles to revoke
account
address
The address to revoke roles from
success
bool
Returns true if roles were revoked
Example:
uint256 labelId = uint256(keccak256("alice"));
registry.revokeRoles(
    labelId,
    RegistryRolesLib.ROLE_SET_RESOLVER,
    delegateAddress
);

Query Functions

getSubregistry

Returns the subregistry for a label.
function getSubregistry(string calldata label) public view virtual returns (IRegistry)
label
string
The label to query
subregistry
IRegistry
The subregistry contract, or address(0) if none or expired
Example:
IRegistry subregistry = registry.getSubregistry("alice");

getResolver

Returns the resolver for a label.
function getResolver(string calldata label) public view virtual returns (address)
label
string
The label to query
resolver
address
The resolver address, or address(0) if none or expired
Example:
address resolver = registry.getResolver("alice");

getParent

Returns the parent registry and label.
function getParent() public view virtual returns (IRegistry parent, string memory label)
parent
IRegistry
The parent registry contract
label
string
The label in the parent registry
Example:
(IRegistry parent, string memory label) = registry.getParent();

getExpiry

Returns the expiration timestamp for a name.
function getExpiry(uint256 anyId) public view returns (uint64)
anyId
uint256
The labelhash, token ID, or resource ID
expiry
uint64
The expiration timestamp
Example:
uint256 labelId = uint256(keccak256("alice"));
uint64 expiry = registry.getExpiry(labelId);

getResource

Converts any ID (labelhash, token ID, or resource) to the current resource ID.
function getResource(uint256 anyId) public view returns (uint256)
anyId
uint256
The labelhash, token ID, or resource ID
resource
uint256
The current resource ID for access control
Example:
uint256 labelId = uint256(keccak256("alice"));
uint256 resource = registry.getResource(labelId);

getTokenId

Converts any ID to the current token ID.
function getTokenId(uint256 anyId) public view returns (uint256)
anyId
uint256
The labelhash, token ID, or resource ID
tokenId
uint256
The current token ID
Example:
uint256 labelId = uint256(keccak256("alice"));
uint256 tokenId = registry.getTokenId(labelId);

getStatus

Returns the registration status of a name.
function getStatus(uint256 anyId) public view returns (Status)
anyId
uint256
The labelhash, token ID, or resource ID
status
Status
AVAILABLE, RESERVED, or REGISTERED
Example:
uint256 labelId = uint256(keccak256("alice"));
Status status = registry.getStatus(labelId);
if (status == Status.REGISTERED) {
    // Name is registered
}

getState

Returns complete state information for a name.
function getState(uint256 anyId) public view returns (State memory state)
anyId
uint256
The labelhash, token ID, or resource ID
state
State
Complete state including status, expiry, owner, tokenId, and resource
Example:
uint256 labelId = uint256(keccak256("alice"));
State memory state = registry.getState(labelId);
console.log("Status:", uint(state.status));
console.log("Expiry:", state.expiry);
console.log("Owner:", state.latestOwner);

latestOwnerOf

Returns the latest owner of a token, even if expired.
function latestOwnerOf(uint256 tokenId) public view virtual returns (address)
tokenId
uint256
The token ID to query
owner
address
The owner address, or address(0) if never owned
Example:
address owner = registry.latestOwnerOf(tokenId);

ownerOf

Returns the current owner of a token (respects expiry).
function ownerOf(uint256 tokenId) public view virtual override returns (address)
tokenId
uint256
The token ID to query
owner
address
The owner address, or address(0) if expired or token ID is outdated
Example:
address owner = registry.ownerOf(tokenId);

roles

Returns the role bitmap for an account.
function roles(
    uint256 anyId,
    address account
) public view override returns (uint256)
anyId
uint256
The labelhash, token ID, or resource ID
account
address
The account to query
roleBitmap
uint256
Bitmap of roles held by the account
Example:
uint256 labelId = uint256(keccak256("alice"));
uint256 userRoles = registry.roles(labelId, userAddress);
bool canSetResolver = (userRoles & RegistryRolesLib.ROLE_SET_RESOLVER) != 0;

hasRoles

Checks if an account has specific roles.
function hasRoles(
    uint256 anyId,
    uint256 roleBitmap,
    address account
) public view override returns (bool)
anyId
uint256
The labelhash, token ID, or resource ID
roleBitmap
uint256
Bitmap of roles to check
account
address
The account to check
hasRoles
bool
True if the account has all specified roles
Example:
uint256 labelId = uint256(keccak256("alice"));
bool canManage = registry.hasRoles(
    labelId,
    RegistryRolesLib.ROLE_SET_RESOLVER | RegistryRolesLib.ROLE_RENEW,
    userAddress
);

Events

NameRegistered

Emitted when a name is registered with an owner.
event NameRegistered(
    uint256 indexed tokenId,
    bytes32 indexed labelHash,
    string label,
    address owner,
    uint64 expiry,
    address indexed sender
)

NameReserved

Emitted when a name is reserved without an owner.
event NameReserved(
    uint256 indexed tokenId,
    bytes32 indexed labelHash,
    string label,
    uint64 expiry,
    address indexed sender
)

NameUnregistered

Emitted when a name is unregistered.
event NameUnregistered(uint256 indexed tokenId, address indexed sender)

ExpiryUpdated

Emitted when a name’s expiry is changed.
event ExpiryUpdated(uint256 indexed tokenId, uint64 newExpiry, address indexed sender)

SubregistryUpdated

Emitted when a name’s subregistry is changed.
event SubregistryUpdated(
    uint256 indexed tokenId,
    IRegistry subregistry,
    address indexed sender
)

ResolverUpdated

Emitted when a name’s resolver is changed.
event ResolverUpdated(uint256 indexed tokenId, address resolver, address indexed sender)

TokenRegenerated

Emitted when a token is regenerated with a new ID due to role changes.
event TokenRegenerated(uint256 indexed oldTokenId, uint256 indexed newTokenId)

ParentUpdated

Emitted when the parent registry is changed.
event ParentUpdated(IRegistry indexed parent, string label, address indexed sender)

TokenResource

Emitted to associate a token with an access control resource.
event TokenResource(uint256 indexed tokenId, uint256 indexed resource)

Errors

NameAlreadyRegistered

error NameAlreadyRegistered(string label)
Thrown when attempting to register a name that is already registered.

NameAlreadyReserved

error NameAlreadyReserved(string label)
Thrown when attempting to reserve or register a name that is already reserved.

NameExpired

error NameExpired(uint256 tokenId)
Thrown when attempting to modify an expired name.

CannotReduceExpiration

error CannotReduceExpiration(uint64 oldExpiration, uint64 newExpiration)
Thrown when attempting to reduce a name’s expiration time.

CannotSetPastExpiration

error CannotSetPastExpiration(uint64 expiry)
Thrown when attempting to set an expiration in the past.

TransferDisallowed

error TransferDisallowed(uint256 tokenId, address from)
Thrown when attempting to transfer a token without the required transfer admin role.

State Machine

The registry follows this state diagram:
                     register()
                  +ROLE_REGISTRAR
      +------------------->----------------------+
      |                                          |
      |               renew()                    |    renew()
      |             +ROLE_RENEW                  |  +ROLE_RENEW
      |              +------+                    |   +------+
      |              |      |                    |   |      |
      v              v      v                    v   v      |
  AVAILABLE -------> RESERVED -------------> REGISTERED >---+
      ^   register()    v      register()         v
      |   w/owner=0     |  +ROLE_REGISTER_        |
      | +ROLE_REGISTRAR |      _RESERVED           |
      |                 |                          |
      +--------<--------+------------<-------------+
                    unregister()
                 +ROLE_UNREGISTER

Notes

  • Token IDs include version numbers and are regenerated when roles change
  • Resource IDs for access control are separate from token IDs
  • The “anyId” parameter in most functions accepts labelhash, token ID, or resource ID
  • Admin roles can only be granted during registration, not afterward
  • Transfers automatically transfer all roles to the new owner
  • Transfers require ROLE_CAN_TRANSFER_ADMIN

Build docs developers (and LLMs) love