Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

v1 source realm

Package nslogic is the swappable "business rules" half of the split (NOTES.md §9t). It holds every piece of policy th...

Overview

Package nslogic is the swappable "business rules" half of the split (NOTES.md §9t). It holds every piece of policy that might reasonably change — pricing, character validation, payment/refund, freeze rules, transfer restrictions — and no persistent records of its own. (One throttle, not a record: when each bounded price setting last moved, timelock.gno's lastTweak. A logic swap starts it empty and loses nothing.)

Every mutation here ends by calling into nsdata's narrow Record/Raw API via cross(cur). nsdata trusts whichever address is currently wired up as trustedLogic to have done its own authorization checks correctly, so THIS realm is where "is the caller allowed to do this" actually lives. If a bug is found here, admin deploys a fixed nslogic and re-points the vault at it (ProposeTrustedLogicGrant, then ExecutePending after the timelock) — no data migration, records and config untouched.

Deliberately reads config fresh from nsdata on every call rather than caching it. The one thing that cannot be read fresh is a COMPILED regex — avl trees store the pattern string, not the compiled object — so that one value is cached, and the cache is validated against nsdata on every use.

Functions 113

func BlockChunks

Action
1func BlockChunks() int
source

BlockChunks is the number of chunks, so the panel does not hard-code a constant that this file might later change.

func BlockList

Action
1func BlockList(i int) string
source

BlockList returns one chunk, so the panel can show and edit what is actually stored rather than what somebody believes is stored.

func BlockedLabel

Action
1func BlockedLabel(label string) bool
source

BlockedLabel reports whether a label may not be registered at all.

Compares against the label as the registry stores it — lowercase, already validated — so a caller cannot slip past with different casing. Every comparison is exact: this list holds whole labels, not substrings, because "contains" matching turns a list of a dozen words into an unpredictable filter that refuses names nobody meant to refuse.

func ClaimCapSet

Action
1func ClaimCapSet() bool
source

ClaimCapSet reports whether a cap is set at all. Unset — or unreadable, which is read the same way — no credit can be spent on anything.

func ClaimMaxPriceUSD

Action
1func ClaimMaxPriceUSD() int64
source

ClaimMaxPriceUSD is the highest list price, in micro-USD, a credit can be spent on, or 0 while no cap is set — and while none is set no credit redeems at all (ClaimCapSet).

IT NEVER FOLLOWS THE DEFAULT PRICE (re-audit 2026-10-16, H-1). It used to fall back to DefaultPriceUSD while unset, which is how a registry is born. The default moves fast in its "dearer" direction — four times, every ten minutes, instantly — and premium prices do not move with it, so each dearer step on the default carried the cap past another tier of premium names: a stolen admin key took the $300 x*doge with a free credit in forty minutes, never touching SetClaimMaxPrice, whose raise is meant to be the slow step. So the cap stands on its own now, an unset or unreadable one covers nothing, and the first cap is a decision (SetClaimMaxPrice).

func ClaimWindowEnd

Action
1func ClaimWindowEnd() int64
source

ClaimWindowEnd is the unix second redemption closes, or 0 when no window is set — which means CLOSED. An entitlement nobody bounded is a liability that never ends, so the default is no window at all.

func ClearKeeper

crossing Action
1func ClearKeeper(cur realm)
source

ClearKeeper removes the keeper at once. Admin, recovery or guardian — the guardian bot's answer to a KeeperAction it did not expect. Clearing when there is no keeper does nothing and still succeeds, so a bot may call it without reading first.

func ClearPrimaryName

crossing Action
1func ClearPrimaryName(cur realm)
source

ClearPrimaryName switches reverse resolution OFF for the caller.

WHY THIS HAS TO EXIST, and it is a hole rather than a feature request.

nsdata sets the reverse pointer AUTOMATICALLY on a wallet's first registration — RegisterNameRecord does it, and nothing asked the person first. From that moment their address answers with their name, so anybody who sees that address anywhere can find the persona attached to it, and every transaction it has ever made becomes attributable. Retroactively: the chain is permanent and indexers keep copies.

And until now there was NO WAY OUT. ClearPrimaryRecord is gated to trusted logic, and no logic realm exposed it, so the one person the pointer is about could not remove it. A name service is a bridge between a persona and a wallet; publishing that bridge without consent and without an exit is the worst version of it.

This is the exit. It takes no arguments on purpose: a wallet has exactly one reverse pointer, and the only address it can clear is its own, so there is nothing to pass and nothing to get wrong.

WHAT IT DOES NOT DO, said plainly because a promise we cannot keep would be worse than the hole: clearing stops FUTURE lookups. It does not unpublish what was already visible, and it cannot — the history is on chain and other people have copies. The honest sequence is to choose before publishing, which is why the site now shows what the link exposes before it offers to make it.

func CountNamesOwnedBy

Action
1func CountNamesOwnedBy(owner address, after string, limit int) (n int, next string)
source

CountNamesOwnedBy is the same walk without the strings, for a header that wants a number before the list has finished arriving.

func CreditGrantedAt

Action
1func CreditGrantedAt(owner address) int64
source

CreditGrantedAt is when an address's credits were last granted (unix seconds; 0 for none, or for a record from before the field existed).

func CreditRefusal

Action
1func CreditRefusal(owner address, label, domainLabel string) string
source

CreditRefusal is why `owner` could not spend a credit on label*domain right now, or "" when nothing about the credit stands in the way. For the site, before anybody signs; the label rules are checked separately.

func CreditsOf

Action
1func CreditsOf(owner address) (available, granted, used, expires int64, sellable bool)
source

CreditsOf reports an address's credits: how many it may redeem right now, and the record behind that answer. `sellable` is whether the names they buy come without the free-tier hold — true unless the grant said not. Credits recovery voided are not available; the window, a pause and a quarantine are the registry's state, reported by CreditRefusal.

func DefaultReferralRules

Action
1func DefaultReferralRules() (cut, discount string)
source

DefaultReferralRules lets the site show the presets before anybody commits them.

func DeleteDomain

crossing Action
1func DeleteDomain(cur realm, label string) (removed int64, done bool)
source

FreezeDomain / UnfreezeDomain: project-only power to permanently block re-registration of a domain even after its grace period lapses. DeleteDomain destroys a domain and every name beneath it.

THE VAULT PRIMITIVE EXISTED BEFORE THIS DID, AND THAT MADE IT UNREACHABLE. nsdata.DeleteDomainRecord gates on assertIsTrustedLogic, so the only way to reach it is a function in this realm — and there was not one. The capability shipped in the vault and could not be used, which is worse than not shipping it, because it reads as present.

Bounded by the vault at maxDeletePerCall names per call. It returns how many it removed and whether it finished, so a large domain is several calls rather than a transaction that cannot fit in a block. Repeat until done is true; calling it on a domain that is already gone reports done rather than panicking, so a loop terminates cleanly.

ADMIN ONLY, and deliberately not owner-callable: domains are project-owned (SPEC.md §17b) and this destroys other people's names.

TIMELOCKED (timelock.gno). It burned every live name under a domain at once — and paid the deleter the holders' storage deposits, about 57 GNOT per hundred names. One proposal, "DeleteDomain <label>", covers every call of a multi-call deletion while it stays matured, and is spent by the call that finishes; recovery can cancel it between calls.

func DomainFree

Action
1func DomainFree(domainLabel string) bool
source

DomainFree reports whether a domain accepts free-tier registrations. Off by default, and per-domain on purpose: the free tier is a promotion aimed at a few domains, not a property of the registry.

func DomainOpen

Action
1func DomainOpen(domainLabel string) bool
source

DomainOpen reports whether names may currently be registered under a domain. An absent key means open — a domain created before this existed must not silently close.

func ExplainPriceUSD

Action
1func ExplainPriceUSD(domainLabel, label string) (beforePremium int64, modifier string, final int64)
source

ExplainPriceUSD returns each stage of the resolution, so the control panel can show the arithmetic rather than assert a total. Returns the price before any premium, the premium modifier that applied (empty if none), and the final price.

func FreeLabelAllowed

Action
1func FreeLabelAllowed(label string) bool
source

FreeLabelAllowed reports whether a label may be ISSUED on the free tier.

ISSUED, and never renewed. This is called by GrantFreeName and by nothing else, deliberately: if the minimum length rises from three to five, somebody who already holds a three-character free name keeps it and keeps renewing it free. Tightening a rule must not reach backwards and take away what was already given — that is the single fastest way to make a giveaway cost more trust than it ever bought.

A name that LAPSES and is registered again is a new issuance, and gets whatever the rules say that day. That is not retroactive; it is a different name event.

func FreeMinLength

Action
1func FreeMinLength() int
source

FreeMinLength is the shortest label the free tier will issue. Settable from the panel, because which lengths are worth money is a commercial judgement that moves.

func GlobalLengthRule

Action
1func GlobalLengthRule(n int) (string, bool)
source

GlobalLengthRule returns the global rule for a label length, if any.

func GrantCredits

crossing Action
1func GrantCredits(cur realm, to address, n, expires int64, sellable bool)
source

GrantCredits SETS how many free registrations `to` is entitled to in total, with an optional expiry (0 for none) and whether the names they buy are SELLABLE — true is the default to pass; false mints them with the free-tier hold. 0 revokes whatever is unredeemed. See the note at the top of this file on why a grant sets rather than adds.

func GrantCreditsBatch

crossing Action
1func GrantCreditsBatch(cur realm, addrs string, n, expires int64, sellable bool) int
source

GrantCreditsBatch is GrantCredits for a comma-separated list of addresses, up to 100 a call — the migration grant is one entry per testnet wallet. Returns how many were written. One bad address fails the whole batch, so a typo cannot silently leave somebody out.

func GrantFreeName

crossing Action
1func GrantFreeName(cur realm, label, domainLabel string, owner address)
source

GrantFreeName registers a free-tier record to somebody else.

ADMIN OR GRANTER, and that is the whole trust model: the eligibility rules — which Twitter account, how old, how many followers, whether the label is a dictionary word — live off chain with the issuer. Putting them here would freeze a marketing policy into a realm and cost storage to do it. What the chain enforces is the part that must not depend on our correctness: the label is structurally allowed, the domain opted in, and the window is open.

`owner` may be an address the recipient generated in their browser seconds ago and has never funded. That is the point.

func GrantFreeRenewal

crossing Action
1func GrantFreeRenewal(cur realm, label, domainLabel string)
source

GrantFreeRenewal extends a free-tier name at no charge.

THE DEAL IS THE DISPLAY, and it is checked off chain: the issuer signs somebody in on X, reads their display name, and finds the name in it. That single act proves both halves at once — that a real account still exists, and that it is still carrying the name — which is why renewal is one click a year rather than a form.

FREE-TIER RECORDS ONLY. A paid name renewed for nothing is revenue given away by accident, and the two are told apart by whether a token was ever minted — now, whether the token still carries the free-tier hold. The hold is the registry's flag, read through GetNameInfo's `paid`.

NO OWNERSHIP PROOF IS ASKED FOR, and that is deliberate rather than missing. Renewing somebody else's name is a GIFT — it takes nothing, moves nothing, and grants the caller no power over it. The only cost of allowing it is ours, and the vault's renewal cap already bounds how far any single name can be pushed. A proof requirement here would buy nothing and would cost the one-click renewal that makes the deal work.

RenewNameRecord insists the caller is the owner, so the owner is looked up and passed. That is not a bypass: this realm is the trusted logic, and what it is asserting is exactly true — this renewal is on that owner's behalf.

func GrantWindowOpen

Action
1func GrantWindowOpen() bool
source

GrantWindowOpen reports whether free grants are being issued at all.

A single global switch that closes everything at once, because the failure this guards against is not a clever attack — it is noticing on a Sunday that something is wrong and wanting one call that stops it. An unset value means open; "never" means open with no end date, which is what the admin panel already writes.

func IsFreeTier

Action
1func IsFreeTier(label, domainLabel string) bool
source

IsFreeTier reports whether a name is a record without a token — the free tier. Exported because the site and the issuer both need to ask before offering a free renewal, and neither should infer it.

func LastTweak

Action
1func LastTweak(setting string) (dearer, cheaper int64)
source

LastTweak reports when a bounded setting last moved instantly in each direction (unix seconds, 0 for never), so the panel can say when the next step each way is due: dearer after ten minutes, cheaper after an hour.

func MaxTermYears

Action
1func MaxTermYears(domainLabel string) int64
source

MaxTermYears is the cap that applies to names under this domain.

func NamePrice

Action
1func NamePrice(domainLabel, label string) int64
source

NamePrice converts the USD price to ugnot for settlement. Rounds UP, so rounding never sells a name below its listed price; a free name stays exactly free.

IT REFUSES TO OVERFLOW. usd*rate is int64 arithmetic, which wraps silently, and a wrapped product is a price of millions of GNOT or a negative one — and a negative price made takePaymentSplit refund more than was paid (audit L5). A price that cannot be computed is refused, which fails closed: no sale, never a wrong one.

func NamePriceUSD

Action
1func NamePriceUSD(domainLabel, label string) int64
source

NamePriceUSD resolves the four inputs in precedence order and returns micro-USD. This is the authoritative price; ugnot is derived from it.

func NamesOwnedBy

Action
1func NamesOwnedBy(owner address, after string, limit int) (rows, next string)
source

NamesOwnedBy returns rows of `label*domain,registered,expires,streak,frozen` separated by ";", plus the cursor to continue from. Labels are restricted to [a-z0-9-] so neither separator can appear inside a field.

`next` is empty when the scan has reached the end of the registry. It is NOT empty merely because this page found no matches: a page of two hundred names belonging to other people returns no rows and a cursor, and the caller keeps going.

func OpenRestore

crossing Action
1func OpenRestore(cur realm, days int64)
source

OpenRestore opens the window, once, for a bounded number of days.

Only ever valid on a chain where the registry is EMPTY because the previous chain was retired. It cannot be called on a vault that has been sealed, and it cannot be called twice.

func PremiumFor

Action
1func PremiumFor(label string) string
source

PremiumFor returns the raw modifier for a name across all premium keys, or "" when the name is not premium anywhere.

func PremiumNames

Action
1func PremiumNames(page int) string
source

PremiumNames reads one page back, so the admin panel can show what is stored rather than what it last sent.

func PriceWithReferral

Action
1func PriceWithReferral(domainLabel, label, refName string, buyer address) int64
source

PriceWithReferral is what a buyer actually pays given the code they arrived with. Exported so the site can display the number the chain will charge instead of computing its own and hoping they agree.

func ProfileOf

Action
1func ProfileOf(label, domainLabel, keys string) string
source

Everything the profile page draws, in ONE read.

It was making seventeen queries: three to establish that the name exists and when it expires, then fourteen more — one per extension slot — that could not start until the first three had come back. Two waves, and the node serves only a few at a time, so a profile took seconds to assemble out of values that all live in the same record.

Values come back in the ORDER THE KEYS WERE ASKED FOR rather than as key=value pairs. An extension key is allowed to contain "=" (only commas and newlines are forbidden), so a pair encoding would have an ambiguous split; positional has none.

Line 0 is the record: owner, registered, expires, streak, frozen, and the grace period, which is global but is one more query the page would otherwise have to make on its own.

An empty return means the name is not registered. That is a real answer rather than an error: asking about a name nobody owns is the normal case on a profile URL somebody typed.

func RateUgnotPerUsd

Action
1func RateUgnotPerUsd() int64
source

RateUgnotPerUsd is how many ugnot one USD buys.

This is a MANUALLY MAINTAINED rate, not a market oracle: an admin writes it and it sits there until rewritten. That is a real operational hazard — a stale rate during a sharp move sells names at the wrong price in whichever direction hurts — and the mitigation is keeping it fresh, not anything this code can do. Kept as one global so a future logic realm can swap in a real oracle without touching the vault or restating a single price.

func ReassignDomain

crossing Action
1func ReassignDomain(cur realm, label string, newOwner address)
source

ReassignDomain moves a domain record to a new owner. ADMIN-ONLY, and the deliberate exception to "domains are non-transferable".

It exists because domain ownership is snapshotted into the record at registration time, while `admin` is a rotatable role. After the planned EOA-to-multisig rotation, the incoming admin could not renew or reprice any domain the outgoing key had registered — and if the rotation happened *because* the old key was compromised, the attacker could still reprice those domains afterwards. This is the recovery lever for exactly that situation.

Not a loophole in the non-transferability policy: that policy exists to stop domains reaching third parties as tradeable assets, and this path is reachable only by the current admin, moving a project domain between project-controlled addresses.

TIMELOCKED (timelock.gno): a stolen admin key could otherwise move every project domain to itself in one block. Genesis domains run ten years, so the honest recovery this exists for loses nothing by waiting.

func RecentNames

Action
1func RecentNames(after string, limit int) (rows, next string)
source

RecentNames answers "what has just been registered" in one read.

The site used to build this by listing every key and then reading each record one at a time — the same shape NamesOwnedBy replaced for a wallet's holdings, and the same problem: fine at thirteen names, one query per name forever after. A feed that gets slower every time somebody registers is a feed that eventually stops loading on the busy day you most wanted it.

Returns rows of "label*domain,registered,expires,owner", newest first, and a cursor. Sorting happens across a PAGE, not the whole registry: the vault stores names alphabetically and there is no index by date, so a caller that wants the true newest reads every page and merges. That is one query per 200 names instead of one per name — enough of a difference to stop mattering, without pretending the vault has an index it does not have.

func RedeemCredit

crossing Action
1func RedeemCredit(cur realm, label, domainLabel string)
source

RedeemCredit registers label*domain to the caller and spends one of the caller's credits. The caller signs, so the caller pays the storage deposit and gas; the name itself costs nothing.

func RefRates

Action
1func RefRates() (cutPct, discountPct int64)
source

RefRates is the flat cut and discount in force, in percent, clamped so that together they never exceed maxRefPercent whatever is stored. The discount is kept and the cut gives way: what a buyer was quoted is the promise, the cut is the treasury's to shrink.

func RefWindowDays

Action
1func RefWindowDays() int64
source

RefWindowDays is how long a clicked link keeps counting for. It is enforced by the browser that captured the code, not by this realm — the chain never sees the click, only the registration that quotes a code — so it is stored here purely to be ONE number rather than a constant baked separately into the site, the FAQ and the rules page. Zero means the default; the site treats an absent value the same way.

func ReferralBoard

Action
1func ReferralBoard(after string, limit int) (rows, next string)
source

ReferralBoard pages through every credited wallet for the leaderboard: one "address,count" row per line.

It walks the vault's per-address store, where other fields (credits) sit beside the counters, so a page can scan more keys than it returns. `next` is an opaque cursor — pass it back as `after` — and is non-empty whenever the walk stopped before the end, including when it stopped on its scanning budget with fewer rows than asked for. A caller keeps going until `next` is empty.

func ReferralCount

Action
1func ReferralCount(who address) int64
source

ReferralCount is how many paid registrations a wallet has been credited with. Public, like everything in the vault.

func RegisterDomain

crossing Action
1func RegisterDomain(cur realm, label string)
source

RegisterDomain mints a new `*domain` — ADMIN-ONLY (2026-08-15 policy: domains are never sold or owned by third parties). No payment: the project isn't paying itself. Domains are also permanently non-transferable by users (see nft.gno's TransferFrom).

func RegisterFor

crossing Action
1func RegisterFor(cur realm, owner address, label, domainLabel string)
source

RegisterFor registers label*domain to `owner` and spends one of OWNER'S credits — the project signs and pays the storage and gas, which is the only way a holder with no GNOT can get a name at all. Admin or granter: the same machine that issues free names relays these, and it can spend only credits the admin granted, only into the holder's own wallet.

func RegisterName

crossing Action
1func RegisterName(cur realm, label, domainLabel string)
source

RegisterName mints `label*domain` under an existing domain. Anyone may call this. Minting still works while the parent domain is past its own due date, as long as its grace period hasn't fully lapsed and it isn't frozen — nsdata's RegisterNameRecord enforces that.

func RegisterNameRef

crossing Action
1func RegisterNameRef(cur realm, label, domainLabel, refName string)
source

RegisterNameRef is RegisterName with a referral code — the NAME of whoever sent the buyer here, as it appears in the link.

It is a second entry point rather than a third argument on the first one because the first one is already deployed and already being called by wallets and by the site. Changing its arity would break every one of those callers at the same instant, which is exactly the failure the voting realm produced last time; adding a function breaks nothing and lets the front end pick whichever the deployed realm actually has.

A code that does not earn — misspelt, lapsed, the buyer's own name, a free-tier name, or one not yet held for thirty days (referral.gno) — is IGNORED, not rejected. The registration goes through at list price. Refusing to sell somebody a name because they mistyped somebody else's would be an absurd way to lose a sale.

func RenewDomain

crossing Action
1func RenewDomain(cur realm, label string)
source

RenewDomain extends a domain's expiry by exactly one year from its PREVIOUS due date, not from the payment date. No payment — domains are always project-owned, so this is self-service upkeep, not a sale. Deliberately does not call validateLabel — a tightened pattern only gates new registrations, never renewals of what's already registered.

func RenewName

crossing Action
1func RenewName(cur realm, label, domainLabel string)
source

RenewName — same anchoring rule as RenewDomain: extends from the previous due date, not from the payment date. Deliberately does not call validateLabel — an already-registered name must keep renewing under whatever pattern was in force when it was minted.

func RenewalsLeft

Action
1func RenewalsLeft(label, domainLabel string) int64
source

RenewalsLeft is how many more years may be added to a name right now. Zero means the renew button should be off, and the site reads this rather than doing the arithmetic itself — the cap can be per domain, and a page that guesses it will guess wrong on exactly the domains where it matters.

An unregistered name reports the full allowance: nothing is holding runway yet.

func ResolveAddress

Action
1func ResolveAddress(addr address) string
source

ResolveAddress performs reverse resolution: address -> name.

It must verify the ownership it is asserting, not merely that the pointed-at name exists and is unexpired. nsdata's `primary` tree is a record of what an address last SELECTED, not proof of what it still owns — and although nsdata now clears the pointer on transfer and reclaim, this check is the half that lives in the swappable realm and therefore stays correct even against pointers written by an older version of the logic. Without it, an address that sold or lost a name kept reverse-resolving to it, impersonating the new owner.

func RestoreAccountField

crossing Action
1func RestoreAccountField(cur realm, owner address, key, value string)
source

RestoreAccountField puts back one field of the per-address store — credits and their redemptions, referral counts. Without it a rebuilt registry would forget who had already spent their free name, and the migration grant could be claimed twice.

func RestoreDomain

crossing Action
1func RestoreDomain(cur realm, label string, owner address, registered, expires, ttl int64, frozen bool)
source

RestoreDomain re-creates one domain with its original dates.

ORDER MATTERS: domains before their names. The vault refuses to register a name under a domain that does not exist or had lapsed at the moment the name claims to have been registered, which is correct behaviour and also exactly the order a snapshot should be replayed in.

The vault's own registration path sets `registered` to whatever instant it is handed and derives the expiry from the term length, so the registration date is restored by passing the ORIGINAL one, and the expiry is then corrected to the recorded value rather than a computed one. Anything else would quietly hand everybody a fresh term.

func RestoreGlobal

crossing Action
1func RestoreGlobal(cur realm, key, value string)
source

RestoreGlobal puts back the vault's global slot — where the default price, the referral tiers and the seasonal card effects live.

It refuses to write the restore keys themselves. Without that, a snapshot taken while a window was open would, on being replayed, cheerfully restore the open window along with everything else — and a seal that a backup file can undo is not a seal.

func RestoreName

crossing Action
1func RestoreName(cur realm, label, domainLabel string, owner address, registered, expires, ttl int64, frozen, isNFT bool)
source

RestoreName re-creates one name. See RestoreDomain on ordering and on why the dates are passed rather than computed.

isNFT is carried across under its old name so snapshot blobs stay readable, and now means PAID: false restores a free-tier name with the hold on its token, so a restore can never quietly make a free name sellable.

func RestoreNameField

crossing Action
1func RestoreNameField(cur realm, label, domainLabel, key, value string)
source

RestoreNameField and RestoreDomainField put back a record's extension keys — profiles, cross-chain addresses, tier markers, and a domain's pricing rules. Separate calls because the extension slot has no fixed shape by design, which is the same reason the snapshot captures it by enumeration rather than by schema.

func RestoreNames

crossing Action
1func RestoreNames(cur realm, blob string) int
source

RestoreNames replays many names in ONE transaction.

WHY A BLOB AND NOT A LOOP OF CALLS. A restore is done in one sitting against a clock: the window is bounded and the people whose names are missing are watching. One transaction per name is one signature per name, and at a few hundred names that is an afternoon of clicking during which a single mistimed refresh loses the thread. The compact form here is unpleasant to read and it is the difference between a restore that gets finished and one that gets abandoned half way.

The format is records separated by "|", fields separated by ",":

Example
1label,domain,owner,registered,expires,ttl,frozen,isNFT

Neither a label nor a domain may contain "," or "|" — the character set is enforced at registration — and an address contains neither, so the split is unambiguous. tools/snapshot.js writes the JSON this is built from; see tools/restore-blob.js for the conversion.

It stops at the first bad record and panics, taking the whole transaction with it. That is deliberate: a partial batch that silently skipped four names would leave four people without theirs and nothing anywhere saying which four.

func RestorePrimary

crossing Action
1func RestorePrimary(cur realm, owner address, label, domainLabel string)
source

RestorePrimary puts back reverse resolution — which name an address answers to. Registration already sets this for an owner who had none, so this exists for the owners who had chosen a different one.

SKIPPED, NOT REFUSED, when the vault would refuse it — the name is not there, or the ledger says somebody else holds it (a sale leaves the seller's pointer behind): pointers travel fifty to a transaction, and one stale pointer must not revert everybody else's (re-audit 2026-10-16, M-3).

func RestoreStatus

Action
1func RestoreStatus() (state string, deadline int64, restored int64)
source

RestoreStatus reports the state of the restore mechanism in a form a person can read, and is deliberately PUBLIC AND UNAUTHENTICATED. Anybody at all can check whether this registry currently has a door in it, without asking us and without trusting the answer we would give.

Returns the state ("never", "open", "expired" or "sealed"), the unix second an open window lapses (0 otherwise), and how many records have been restored in the lifetime of this vault.

func SealRestore

crossing Action
1func SealRestore(cur realm)
source

SealRestore closes the door permanently. Valid from any state, including "never" — which is how it should be called on a chain that has nothing to restore, so that the capability is provably dead rather than merely unused.

There is no matching unseal, and adding one later would defeat every word written at the top of this file.

func SeedReferralRules

crossing Action
1func SeedReferralRules(cur realm)
source

SeedReferralRules writes the presets, and ONLY into empty rates. Not an init(): at init a fresh logic realm is not yet trusted by the vault. Refusing to overwrite makes it safe to call twice and safe to call by mistake.

func SetAccountLimits

crossing Action
1func SetAccountLimits(cur realm, keyLen, valueLen, entries int)
source

SetAccountLimits bounds the vault's per-address store: field name, value, and fields per address. Small numbers, because whoever's transaction writes a field pays for it.

func SetBlockList

crossing Action
1func SetBlockList(cur realm, i int, csv string)
source

SetBlockList replaces one chunk. Admin only.

Replaces rather than appends: an append-only list has no way to correct a mistake, and a mistake here refuses somebody a name for reasons nobody can see. The panel reads the chunk, edits it, and writes it back whole.

func SetClaimMaxPrice

crossing Action
1func SetClaimMaxPrice(cur realm, microUsd int64)
source

SetClaimMaxPrice is BOUNDED like a price (timelock.gno), with the direction turned round: RAISING the cap is what makes names cheaper — a credit then covers pricier names, premium ones in the end — so a raise is the slow step (25%, once an hour) and a cut the fast one (down to a quarter, every ten minutes). Anything else is a timelocked proposal.

THE FIRST CAP IS NOT A STEP. From no cap there is nothing to step from, so setting one is always the timelocked proposal: instant at a fresh vault's delay of zero, which is when the deploy sets it, and recovery's to veto once the delay is raised.

func SetClaimWindow

crossing Action
1func SetClaimWindow(cur realm, end int64)
source

SetClaimWindow opens redemption until `end` (unix seconds), at most two years ahead, or closes it with 0. Closing is the one call to reach for on a Sunday when something about the grants looks wrong.

func SetDefaultNamePrice

crossing Action
1func SetDefaultNamePrice(cur realm, microUsd int64)
source

SetDefaultNamePrice sets the fallback price in micro-USD, used by any domain that has not set its own base. BOUNDED (timelock.gno): a raise up to four times every ten minutes, a cut up to 25% once an hour, or a timelocked proposal for anything else.

func SetDomainBasePrice

crossing Action
1func SetDomainBasePrice(cur realm, label string, microUsd int64)
source

SetDomainBasePrice sets the domain's base price in micro-USD. Every "%N" rule under the domain is relative to this, so one call reprices the whole domain. BOUNDED (timelock.gno): a raise up to four times every ten minutes, a cut up to 25% once an hour; a move to or from free, or anything larger, is a timelocked proposal.

func SetDomainField

crossing Action
1func SetDomainField(cur realm, label, key, value string)
source

SetDomainField writes an arbitrary key on a domain, for whatever a future feature needs without a vault change — the domain's art among them. It refuses the keys this realm's own setters own, which it used to write straight past ("price" to zero, with no bound).

func SetDomainFree

crossing Action
1func SetDomainFree(cur realm, domainLabel string, on bool)
source

SetDomainFree opens or closes the free tier for one domain.

func SetDomainLengthRules

crossing Action
1func SetDomainLengthRules(cur realm, label, rules string)
source

SetDomainLengthRules prices by label length: "3:2000000,4:%150,5:0" means 3-char names cost $2.00, 4-char names cost 150% of the domain base, and 5-char names are free.

TIMELOCKED, with the price rules below: one rule can make any length free ("3:0"), so a rule set is not a tweak whatever it says. Propose the NORMALISED form, which the refusal prints.

func SetDomainNameOverrides

crossing Action
1func SetDomainNameOverrides(cur realm, label, rules string)
source

SetDomainNameOverrides prices specific labels: "420:5000000,gm:%500". Highest precedence — this beats length rules and the base. TIMELOCKED, like the length rules: "premiumname:0" was a one-call way to take one.

func SetDomainOpen

crossing Action
1func SetDomainOpen(cur realm, label string, open bool)
source

SetDomainOpen enables or disables registration of new names under a domain. Existing names are untouched: closing a domain stops new mints, it does not seize anything already held.

func SetDomainTTL

crossing Action
1func SetDomainTTL(cur realm, label string, seconds int64)
source

SetDomainTTL sets how long a resolver may cache answers about this domain, in seconds, where 0 means never cache — the ENS rule. Admin only, since domains are project-owned.

func SetFreeLengths

crossing Action
1func SetFreeLengths(cur realm, min, max int)
source

SetFreeLengths tunes which lengths the free tier will issue.

In the panel rather than in a redeploy: nslogic is about 22 GNOT to deploy, and "should four-character names be free this month" is a marketing question that should cost a signature, not a deployment.

IT DOES NOT REACH BACKWARDS. Raising the minimum stops NEW issuance of short names; every one already granted stays granted and keeps renewing free. FreeLabelAllowed is called by GrantFreeName and by nothing else, which is what makes that true — see the note on it in reserved.gno.

func SetGlobal

crossing Action
1func SetGlobal(cur realm, key, value string)
source

SetGlobal writes an arbitrary project-wide key into the vault's package-level extension slot — the escape hatch for global state that does not exist yet, and for keys other realms read (cardfx). It refuses every key a setter here owns: the escape hatch used to walk round every bound and timelock on them. See managedGlobal.

func SetGlobalEntryLimit

crossing Action
1func SetGlobalEntryLimit(cur realm, entries int)
source

SetGlobalEntryLimit sets how many keys the vault's globals may hold — its own cap now, apart from the per-record extras SetExtraLimits bounds.

func SetGlobalLengthRules

crossing Action
1func SetGlobalLengthRules(cur realm, rules string)
source

SetGlobalLengthRules writes the project-wide length rules: "1:300000000,2:150000000,3:60000000" prices one, two and three character names everywhere a domain has not said otherwise.

IT HAD NO SETTER UNTIL NOW, which meant the only way to write it was the generic SetGlobal escape hatch — straight past normalizeRules, so a typo stored fine and then vanished at lookup time, because lookupRule skips a malformed pair in silence. A pricing layer whose only writer cannot validate is a pricing layer that fails open.

TIMELOCKED (timelock.gno), like every price rule set: "3:0" here makes short names free on every domain that has no rule of its own.

func SetGranter

crossing Action
1func SetGranter(cur realm, a address)
source

SetGranter delegates grant issuance, or withdraws it. Passing the empty address is the revocation, and it is the first thing to reach for if the issuing machine is ever in doubt — instant, always.

NAMING a granter is TIMELOCKED (timelock.gno): a stolen admin key would otherwise hand itself a second, quieter key that gives names away and spends people's credits, and keep it after the admin key is rotated out.

func SetKeeper

crossing Action
1func SetKeeper(cur realm, a address)
source

SetKeeper names the keeper. Admin only, and TIMELOCKED: propose exactly "SetKeeper <addr>" with nsdata.ProposeAction, then call once it matures. ClearKeeper removes the role.

func SetLabelPattern

crossing Action
1func SetLabelPattern(cur realm, pattern string)
source

SetLabelPattern no longer changes anything, and refuses anything but the rule this realm actually implements.

It used to compile an arbitrary regex and store it, with validateLabel reading it back. That flexibility cost 15 GNOT of permanent storage per deploy — see the measurements in validate.gno — for a rule that has never changed and should not change casually.

It is kept rather than deleted so that an admin who reaches for it gets told what happened instead of a "function not found". And it refuses a different pattern rather than accepting one it will not enforce: a stored string describing rules the code does not apply is the exact silent divergence the caching version was written to avoid.

func SetMarketFee

crossing Action
1func SetMarketFee(cur realm, spec string)
source

SetMarketFee is nsmarket's fee control, living here because this is the realm the vault trusts.

nsmarket.SetFee could never work: it wrote through nsdata.SetGlobalRecord from a realm deliberately outside the trusted set, so it passed its own admin check and then panicked inside the vault for every caller. Rather than grant nsmarket vault privileges — which would surrender the property that realm is built around — the setter moved to the realm that already has the grant, and the validation moved with it.

The spec grammar is nsmarket's, restated here rather than imported so this realm does not take a dependency on the marketplace to set a number: an amount in ugnot ("1000000"), or a percentage ("%2.5"); "0" for none, because "" removes the key and nsmarket then charges its default of one GNOT. A malformed spec would otherwise be read on every sale and resolve silently to zero.

A CHANGE THAT COULD RAISE THE FEE IS TIMELOCKED. The fee is read at sale time, so a raise lands on listings sellers made under the old one — up to half of their sale, moved to the treasury, with no notice. A timelocked raise is the notice: sellers see ActionProposed and may unlist. Lowering it, or switching it off, stays instant.

func SetMaxTerm

crossing Action
1func SetMaxTerm(cur realm, domainLabel string, years int64)
source

SetMaxTerm caps a single domain, or the whole project when the domain label is empty. A domain's own cap always wins, so tightening one free domain does not disturb the rest.

func SetNameField

crossing Action
1func SetNameField(cur realm, label, domainLabel, key, value string)
source

SetNameField writes a single arbitrary key — cross-chain addresses and anything else a future feature needs, with no vault change required. Keys used by this realm's own machinery are reserved.

func SetNameTTL

crossing Action
1func SetNameTTL(cur realm, label, domainLabel string, seconds int64)
source

SetNameTTL is the owner's cache policy for their own name, in seconds, where 0 MEANS NEVER CACHE — copied from the ENS registry so integrators need not read anything of ours to handle it.

The owner sets it rather than the registry because only they know the risk: a name being used to receive payments wants 0, since a cached answer after a sale pays the previous owner; a name used as a display handle can safely be cached for a long time. At most maxTTL.

func SetPremiumNames

crossing Action
1func SetPremiumNames(cur realm, page int, csv string)
source

SetPremiumNames writes one page of the premium keyword list, 1-based. The list is paged because the vault caps an extension value at 512 bytes; see maxPremiumKeys.

Same gap as above: normalizePremiums existed and nothing exported called it.

func SetPrimaryName

crossing Action
1func SetPrimaryName(cur realm, label, domainLabel string)
source

SetPrimaryName lets a wallet explicitly choose (and later change) which of its names is used for reverse resolution.

func SetProfile

crossing Action
1func SetProfile(cur realm, label, domainLabel, displayName, bio, website, avatar string)
source

SetProfile writes the four conventional profile fields. Each lands in the name's extension slot, where the vault enforces size bounds.

func SetReferralRules

crossing Action
1func SetReferralRules(cur realm, cutSpec, discountSpec string)
source

SetReferralRules sets both rates at once, because setting one without the other is how a cut ends up larger than the margin. Each is "N" or "0:N" (percent), or empty for none; two empties switch the programme off. Together they may not exceed maxRefPercent. A multi-tier table is refused: the programme has one tier.

func SetTermLength

crossing Action
1func SetTermLength(cur realm, days int64)
source

BOTH ARE TIMELOCKED (timelock.gno). A grace period of zero made every name in grace reclaimable by anybody on the spot — owners who could still have renewed lost their names to whoever registered first — and a term change reprices every renewal and moves every holder's tenure.

func SetTokenURIBase

crossing Action
1func SetTokenURIBase(cur realm, base string)
source

SetTokenURIBase re-points TokenURI at a different renderer, so the NFT artwork can be replaced without touching the vault.

func SetTreasury

crossing Action
1func SetTreasury(cur realm, newTreasury address)
source

SetTreasury moves the revenue-receiving role — independent of nsdata's TransferAdmin, same reasoning as the original build's admin/treasury separation.

TIMELOCKED. It took effect at once and said nothing, so a stolen admin key redirected every later sale — names, renewals, skins, market fees — in one call (audit M4). Now it is announced and waits, and recovery can cancel it.

func SetUsdRate

crossing Action
1func SetUsdRate(cur realm, ugnotPerUsd int64)
source

SetUsdRate records how many ugnot one USD buys — the bridge between the USD price list and settlement on this chain.

It is a MANUAL rate, not a market feed: it sits where it was last written. A stale rate during a sharp move sells names at the wrong price, and no amount of contract logic fixes that — keeping it current is an operational duty. Isolated as one global so a future logic realm can swap in a real oracle without touching the vault or restating a single price.

BOUNDED (timelock.gno): a rate of 1 ugnot to the dollar used to be one call away from selling every name for dust. ASYMMETRIC (Ely, 2026-10-16): a higher rate — GNOT fell, names cost more GNOT — moves up to four times every ten minutes, so a 75% crash is followed in one update; a lower rate — the direction that sells names cheap — moves at most 25%, once an hour. Anything larger is a timelocked proposal.

THE KEEPER may call this too (keeper.gno), and nothing else: the same bound and the same hourly slot, never the timelock, and KeeperAction on every move. Anybody who is neither gets the admin refusal, as before.

func StartReferralClock

crossing Action
1func StartReferralClock(cur realm, label, domainLabel string)
source

StartReferralClock starts the holding clock on a name the caller BOUGHT, so that thirty days later it can be a referral code.

A sale that settles on the token ledger never calls the registry, so the registry first sees a buyer at their first write to the name. A buyer who never edits a profile would otherwise never qualify. This is that first write, with nothing in it: it clears the seller's stale data, which every read already hides, and stamps the clock. Names you registered yourself start their clock at registration and need nothing.

func TenureYears

Action
1func TenureYears(who address) int64
source

TenureYears is how long a wallet counts as having been here, measured from its PRIMARY name. It used to pick the referral tier; it moves no money now, and stays because the referral page shows it.

A lapsed or missing primary is zero years, not an error.

func UnfreezeName

crossing Action
1func UnfreezeName(cur realm, label, domainLabel string)
source

UnfreezeName is TIMELOCKED (timelock.gno); freezing stays instant. A freeze is the takedown and dispute lever — a disputed or stolen name is held while it is sorted out — so releasing one instantly would let a stolen admin key free a name and move it in the same transaction.

func UpgradeFreeName

crossing Action
1func UpgradeFreeName(cur realm, label, domainLabel string, newOwner address)
source

UpgradeFreeName mints the token for a free-tier record, at full price.

This is the only way a free name becomes sellable, and the price is the ordinary list price with no referral discount — a discount is for bringing somebody new, and this person is already here.

`newOwner` exists because the common case is somebody who has decided to sell and, in the same moment, wants the name in a wallet they actually control rather than the browser key they were given. The vault handles that move inside UpgradeFreeRecord.

Callable by the record's current owner only. Anyone may PAY for somebody else's upgrade in the sense that they can hand over the money, but they cannot perform it — a stranger promoting your name into a token and choosing where it lands is a theft with a receipt.

Dependencies

Source Files 21