IPopRules
Title: Proof of Personhood Rules for dotNS
Proof of personhood interface defining dotNS price calculation, PoP-tier requirements, and base-name reservation rules.
A label's base name is the label with any device suffix removed: the stem of a device
name, or any other label as written. joseph is the base name of both joseph and
joseph.42, and a label's base length is the length of its base name.
Classifies labels into the PoP tier required for registration and exposes reservation
metadata. Every label is measured as written, except a device name, whose separator and
allocated digits are removed first. Base length <= 5 is reserved for governance; 6-8
requires personhood; >= 9 is open to every caller as NoStatus. Devicehood applies to the
separated form alone, so digits in an ordinary label carry no meaning. Reservations are
keyed by that same base name, so joseph and joseph.42 share a slot while joseph42 is
an unrelated name.
Amounts come from the cost model registered under DotnsConstants.COST_MODEL, which owns
the curve; only the base length crosses that seam. Every caller pays the same amount for a
given length; personhood only unlocks the premium band.
Note: security-contact: admin@parity.io
Functions
classifyName
Classifies a name into a required PoP tier per dotNS naming rules.
Pure; inputs are the label bytes only. Callers use the returned tier to decide which pricing and verification branch applies. A label that is neither a single lowercase ASCII DNS label nor a device name triggers @custom:reverts PopError. Trailing digits in an ordinary label count towards its length; only a device name's separator and suffix are removed before it is classified.
function classifyName(string calldata name)
external
pure
returns (PopStatus requirement, string memory message);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | The name label being evaluated. |
Returns
| Name | Type | Description |
|---|---|---|
requirement | PopStatus | Required tier for registration. |
message | string | Explanation of the classification result. |
setShortNamesEnabled
Opens or closes the public market for names shorter than nine characters.
Restricted to a substrate Root origin; any other caller triggers @custom:reverts NotRoot. Short names are otherwise issued through the dotNS gateway pallet, so this flag is the Root-only lever that additionally admits them on the public paid path. While closed, which is the deploy default, @custom:function priceWithCheck and
Note: function: priceWithoutCheck trigger @custom:reverts PopError for a base length below nine, so no public caller buys a short name. The gateway free grant and the registrar's registerReserved path do not read this flag. Emits @custom:emits ShortNamesEnabledUpdated.
function setShortNamesEnabled(bool enabled) external;
Parameters
| Name | Type | Description |
|---|---|---|
enabled | bool | Whether names shorter than nine characters may be bought. |
popStatusOf
Returns the personhood tier recorded for an account.
Reads the account's dotns-scoped tier from the personhood precompile and maps it to a
PopStatus. This is the direct account-tier read; the same tier otherwise surfaces
only as the userStatus field of a pricing query. Never returns Reserved, so the
result is one of NoStatus, Devicehood, or Personhood.
function popStatusOf(address account) external view returns (PopStatus tier);
Parameters
| Name | Type | Description |
|---|---|---|
account | address | Address whose tier is read. |
Returns
| Name | Type | Description |
|---|---|---|
tier | PopStatus | The account's personhood tier. |
reserveBaseName
Creates or refreshes a reservation entry for a base name in the 6 to 8 band.
Authorised-controller entry point: only a controller in the registrar's controllers
set may call this, otherwise @custom:reverts NotRegistry. The gateway queue writes
through @custom:function reserveBaseNameForPop and the public commit-reveal flow
reads the slot rather than writing one, so this is the entry point for a sibling
controller. Its length window bounds the base name and does not name a tier:
Devicehood is decided by the separated label shape. The caller passes the base name; a
non-canonical label, a trailing digit, or a length outside [6, 8] triggers
Note: reverts: PopError. Cross-user collision on a live slot triggers @custom:reverts PopError so the caller cannot silently overwrite another user's reservation; same-user refresh and writes into an empty or expired slot emit @custom:emits BaseNameReserved.
function reserveBaseName(string calldata baseName, address user) external;
Parameters
| Name | Type | Description |
|---|---|---|
baseName | string | The base name, with no trailing digits. |
user | address | The address receiving reservation rights. |
reserveBaseNameForPop
Writes or refreshes a reservation for a base name.
Gateway-driven reservation path used by the PoP controller. Only a controller in the
registrar's controllers set may call this, otherwise @custom:reverts NotRegistry.
Does not apply the 6-8 base-length window that @custom:function reserveBaseName
enforces, but does require the input to be canonical with no trailing digits; a
non-canonical label or one with trailing digits triggers @custom:reverts PopError. If
the slot is already live and held by a different user, @custom:reverts PopError so the
caller's local bookkeeping and PopRules state stay in lockstep; if it is live for the
same user, expiry is refreshed to block.timestamp + MAX_RESERVATION_TIME. Emits
Note: emits: BaseNameReserved on every successful write.
function reserveBaseNameForPop(string calldata baseName, address user) external;
Parameters
| Name | Type | Description |
|---|---|---|
baseName | string | The base name, with no trailing digits. |
user | address | The address receiving reservation rights. |
releaseBaseName
Clears a reservation for a base name.
Only a controller in the registrar's controllers set may call this, otherwise
Note: reverts: NotRegistry. Non-canonical labels and labels with trailing digits trigger @custom:reverts PopError. Live reservations may only be cleared by the same controller that wrote them; another authorised controller attempting to clear a live slot triggers @custom:reverts PopError. Expired reservations may be cleared by any authorised controller as garbage collection. Used by the PoP controller when a reservation is claimed, relinquished, or a queue head promotion leaves the slot empty. Emits @custom:emits BaseNameReleased once the slot is cleared.
function releaseBaseName(string calldata baseName) external;
Parameters
| Name | Type | Description |
|---|---|---|
baseName | string | The base name whose reservation should be cleared (no trailing digits). |
releaseReservationForReclaim
Clears a reservation when the slot owner matches expectedOwner, allowing any
registrar-authorised controller (not only the stamping one) to release the slot.
Narrower than @custom:function releaseBaseName: callers must prove they know the
slot owner, so cross-controller release is gated on a positive match rather than on
caller identity. Intended for the public registrar controller's reclaim path, where
a prior occupant has handed the name back to escrow and the new registrant needs
the cross-flow guard cleared regardless of which controller originally stamped it.
Only a registrar-authorised controller may call this (@custom:reverts NotRegistry).
Non-canonical labels and labels with trailing digits trigger @custom:reverts PopError.
A live reservation
whose owner does not match expectedOwner triggers @custom:reverts PopError; expired
reservations are cleared regardless. Emits @custom:emits BaseNameReleased.
function releaseReservationForReclaim(string calldata baseName, address expectedOwner) external;
Parameters
| Name | Type | Description |
|---|---|---|
baseName | string | The base name whose reservation should be cleared (no trailing digits). |
expectedOwner | address | The address the caller expects to be the current reservation owner. |
getBaseNameReservation
Retrieves reservation information for a base name.
Raw accessor: returns the stored slot regardless of expiry. Use
Notes:
-
function: isBaseNameReserved when live-window semantics are needed. Non-canonical labels trigger
-
reverts: PopError.
function getBaseNameReservation(string calldata baseName)
external
view
returns (address owner, uint64 expires);
Parameters
| Name | Type | Description |
|---|---|---|
baseName | string | The base name, without trailing digits. |
Returns
| Name | Type | Description |
|---|---|---|
owner | address | The address assigned to the reservation. |
expires | uint64 | UNIX timestamp when the reservation expires. |
stripDigits
Returns the base name of a label, its reservation key: a device name without its allocated suffix, or any other label unchanged.
Mirrors the normalisation applied before a reservation is written, so callers can
look up or release one by passing the whole label. Only a device name is shortened,
because only the gateway allocates the digits it carries: joseph.42 yields
joseph while joseph42 is an unrelated name and yields itself. Non-canonical
labels trigger @custom:reverts PopError.
function stripDigits(string calldata name) external pure returns (string memory baseName);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Whole label, device name or otherwise. |
Returns
| Name | Type | Description |
|---|---|---|
baseName | string | The base name of name. |
isBaseNameReserved
Indicates whether a base name is currently reserved.
Applies the live-window predicate to the stored slot so an expired reservation reads as free. Non-canonical labels trigger @custom:reverts PopError.
function isBaseNameReserved(string calldata baseName)
external
view
returns (bool reservedStatus, address owner, uint64 expires);
Parameters
| Name | Type | Description |
|---|---|---|
baseName | string | The base name, without trailing digits. |
Returns
| Name | Type | Description |
|---|---|---|
reservedStatus | bool | True if a live reservation is active. |
owner | address | The reservation holder (zero when not reserved). |
expires | uint64 | UNIX timestamp when the reservation expires. |
priceWithCheck
Calculates price with PoP classification and reservation enforcement.
Reverting pricing path used by the commit-reveal controller. Price is the scarcity
curve for the label's base length and is charged to every caller, verified or not;
personhood only unlocks the premium band. Non-canonical
labels, a base name held live by another user, a governance-reserved label, or a
userAddress whose personhood tier does not meet the label's required tier each
trigger @custom:reverts PopError.
function priceWithCheck(
string calldata name,
address userAddress
)
external
view
returns (PriceWithMeta memory metadata);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label. |
userAddress | address | Registering user for the given label. |
Returns
| Name | Type | Description |
|---|---|---|
metadata | PriceWithMeta | Price with PoP requirements and classification. |
priceWithCheckAtVersion
Calculates price at a specific cost-model version with PoP classification and reservation enforcement.
The versioned counterpart of @custom:function priceWithCheck: identical classification,
tier gating, and reservation rules, but the amount comes from the model registered for
pricingVersionValue rather than the current one. The commit-reveal controller prices
a reveal at the version bound into its commitment, so a model change between commit and
reveal does not move the amount. @custom:reverts UnknownVersion when the version was
never registered.
function priceWithCheckAtVersion(
string calldata name,
address userAddress,
uint256 pricingVersionValue
)
external
view
returns (PriceWithMeta memory metadata);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label. |
userAddress | address | Registering user for the given label. |
pricingVersionValue | uint256 | Cost-model version to price against. |
Returns
| Name | Type | Description |
|---|---|---|
metadata | PriceWithMeta | Price with PoP requirements and classification. |
priceWithoutCheck
Calculates price with PoP classification and reservation metadata, without reverting on conflicts.
Non-reverting counterpart to priceWithCheck: surfaces the same fields, but reports
a Reserved status through metadata instead of reverting when the base name is
held by another user. Used by front-ends that need to present a price and eligibility
preview without forcing a transaction attempt. Governance-reserved names are not
rejected here either; the caller decides what to do. Non-canonical labels still
trigger @custom:reverts PopError because the input is malformed rather than just
contested.
function priceWithoutCheck(
string calldata name,
address userAddress
)
external
view
returns (PriceWithMeta memory metadata);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label. |
userAddress | address | Registering user for the given label. |
Returns
| Name | Type | Description |
|---|---|---|
metadata | PriceWithMeta | Price with PoP requirements and classification. |
priceWithoutCheckAtVersion
Calculates price at a specific cost-model version with PoP classification and reservation metadata, without reverting on conflicts.
The versioned counterpart of @custom:function priceWithoutCheck: same non-reverting
preview behaviour, but the amount comes from the model registered for
pricingVersionValue. @custom:reverts UnknownVersion when the version was never
registered.
function priceWithoutCheckAtVersion(
string calldata name,
address userAddress,
uint256 pricingVersionValue
)
external
view
returns (PriceWithMeta memory metadata);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label. |
userAddress | address | Registering user for the given label. |
pricingVersionValue | uint256 | Cost-model version to price against. |
Returns
| Name | Type | Description |
|---|---|---|
metadata | PriceWithMeta | Price with PoP requirements and classification. |
transferFloor
Transfer-time floor: the greater of the recipient-reach component and the sender-tier-downgrade component, each priced at the name's own length.
Re-prices the name at its own length on every move: returns the name's curve price when either (i) the recipient does not meet the label's required tier, or (ii) the recipient's personhood tier is strictly below the sender's, and zero when neither holds. Passing a name to a wallet that could never have registered it therefore costs the name's own curve price. The two components overlap on pure tier mismatches, so the function takes their maximum rather than their sum to avoid double-charging. Consumed by @custom:function DotnsRegistrar.quoteTransferFee. A label that is neither a single lowercase ASCII DNS label nor a device name triggers
Note: reverts: PopError.
function transferFloor(
string calldata name,
address from,
address to
)
external
view
returns (uint256 floor);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label being transferred. |
from | address | Current holder of the name. |
to | address | Incoming holder of the name. |
Returns
| Name | Type | Description |
|---|---|---|
floor | uint256 | Transfer-time floor in wei: the name's own curve price, or zero. |
isBaseName
Returns whether name can key a reservation: a base name with no trailing digits.
A device name always ends in two digits, so it never qualifies. Non-canonical labels trigger @custom:reverts PopError.
function isBaseName(string calldata name) external pure returns (bool isBase);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | The label to check. |
Returns
| Name | Type | Description |
|---|---|---|
isBase | bool | True when the label has no trailing digits. |
price
Calculates registration cost for a label.
Prices the label by its base length through the cost model registered under
DotnsConstants.COST_MODEL. Ignores the caller's personhood status and reservation
state. A non-canonical label triggers @custom:reverts PopError. An ordinary label is
priced as written; only a device name's allocated suffix is removed first.
function price(string calldata name) external view returns (uint256 cost);
Parameters
| Name | Type | Description |
|---|---|---|
name | string | Domain label to price. |
Returns
| Name | Type | Description |
|---|---|---|
cost | uint256 | Registration cost in wei. |
pricingVersion
Returns the current cost-model version.
The current version held by the registry under DotnsConstants.COST_MODEL. The
commit-reveal controller binds it into a commitment and prices the reveal at that
version, so a model change between commit and reveal leaves the committed amount
unchanged. @custom:reverts PopError when no registry is configured.
function pricingVersion() external view returns (uint256 modelVersion);
Returns
| Name | Type | Description |
|---|---|---|
modelVersion | uint256 | Identifier of the current cost model and its parameters. |
Events
BaseNameReserved
Emitted when a base name receives a reservation.
event BaseNameReserved(string indexed baseName, address indexed owner, uint64 expires);
Parameters
| Name | Type | Description |
|---|---|---|
baseName | string | The base name receiving the reservation. |
owner | address | Address obtaining the reservation right. |
expires | uint64 | UNIX timestamp when the reservation expires. |
ShortNamesEnabledUpdated
Emitted when the public market for names shorter than nine characters is opened or closed.
Set by the Root-gated @custom:function setShortNamesEnabled.
event ShortNamesEnabledUpdated(bool enabled);
Parameters
| Name | Type | Description |
|---|---|---|
enabled | bool | Whether names shorter than nine characters may now be bought. |
BaseNameReleased
Emitted when a base-name reservation is cleared.
event BaseNameReleased(string indexed baseName);
Parameters
| Name | Type | Description |
|---|---|---|
baseName | string | The base name whose reservation was released. |
Errors
PopError
Thrown when a name violates PoP-tier or reservation requirements.
error PopError(string reason);
Parameters
| Name | Type | Description |
|---|---|---|
reason | string | Human-readable explanation of the failure condition. |
NotRoot
Thrown when @custom:function setShortNamesEnabled is called without a substrate Root origin.
Carries no caller parameter: a Root origin has no account to report, and reading
msg.sender under one traps.
error NotRoot();
NotRegistry
Thrown when a caller is not an authorised controller on the registrar.
error NotRegistry();
NameReserved
Thrown when registering a name whose base name is held as a live reservation by another user.
error NameReserved(string label);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Caller-supplied label whose base name is reserved. |
GovernanceReserved
Thrown when registering a label that classifies as governance-reserved at the protocol level.
Distinct from @custom:reverts NameReserved so off-chain consumers can tell "wait for the holder to relinquish" apart from "this label is permanently held by governance".
error GovernanceReserved(string label);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Caller-supplied label that classifies as governance-reserved. |
OwnerStatusInsufficient
Thrown on the cross-payer path when the owner's recorded PoP tier does not meet the
label's required tier. The direct path's priceWithCheck covers this same condition via its
own revert.
error OwnerStatusInsufficient(string label, PopStatus userStatus, PopStatus required);
Parameters
| Name | Type | Description |
|---|---|---|
label | string | Label whose tier requirement was unmet. |
userStatus | PopStatus | Owner's recorded tier. |
required | PopStatus | Required tier for the label. |
Structs
PriceWithMeta
Bundle returned from metadata-aware pricing queries.
struct PriceWithMeta {
uint256 price;
PopStatus status;
PopStatus userStatus;
string message;
}
Properties
| Name | Type | Description |
|---|---|---|
price | uint256 | Registration cost from the current cost model for the label's base length. |
status | PopStatus | Required PoP tier for this name. |
userStatus | PopStatus | Current PoP status recorded for the querying user. |
message | string | Human-readable classification description. |
Reservation
Reservation metadata for a base name.
struct Reservation {
address owner;
uint64 expires;
address controller;
}
Properties
| Name | Type | Description |
|---|---|---|
owner | address | Address holding exclusive claim rights during the reservation window. |
expires | uint64 | UNIX timestamp when the reservation expires. |
controller | address | Address that wrote the reservation; the only address permitted to release it before expiry. |
Enums
PopStatus
Proof-of-Personhood eligibility tier.
NoStatus is the default for unverified users; Devicehood and Personhood are the
two proofs; Reserved covers both governance-held names and base names held by another
user through the reservation table.
enum PopStatus {
NoStatus,
Devicehood,
Personhood,
Reserved
}