v1 source realm
Package nslogic is the swappable "business rules" half of the split (NOTES.md §9t). It holds every piece of policy th...
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.
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 nsdata.SetTrustedLogic at it — 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.
71
func CountNamesOwnedBy
ActionCountNamesOwnedBy is the same walk without the strings, for a header that wants a number before the list has finished arriving.
func DefaultPriceUSD
Actionfunc DeleteDomain
crossing ActionFreezeDomain / 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.
func DomainBasePriceUSD
Actionfunc DomainOpen
ActionDomainOpen 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
Action1func ExplainPriceUSD(domainLabel, label string) (beforePremium int64, modifier string, final int64)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 FreezeDomain
crossing Actionfunc FreezeName
crossing Actionfunc GlobalLengthRule
ActionGlobalLengthRule returns the global rule for a label length, if any.
func MaxTermYears
ActionMaxTermYears is the cap that applies to names under this domain.
func NamePrice
ActionNamePrice 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.
func NamePriceUSD
ActionNamePriceUSD resolves the four inputs in precedence order and returns micro-USD. This is the authoritative price; ugnot is derived from it.
func NamesOwnedBy
ActionNamesOwnedBy 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 ActionOpenRestore 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
ActionPremiumFor returns the raw modifier for a name across all premium keys, or "" when the name is not premium anywhere.
func PriceWithReferral
ActionPriceWithReferral 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
ActionEverything 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
ActionRateUgnotPerUsd 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 ActionReassignDomain 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.
func RecentNames
ActionRecentNames 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 RefCutRules
Actionfunc RefDiscountRules
Actionfunc RefWindowDays
ActionRefWindowDays 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
ActionReferralBoard pages through every credited wallet for the leaderboard: one "address,count" row per line. The vault stores globals in a sorted tree, so the "ref:" entries are one contiguous run and the walk stops at the first key past it rather than reading the rest of the config.
func ReferralCount
ActionReferralCount is how many paid registrations a wallet has been credited with. Public, like every other global here.
func RegisterDomain
crossing ActionRegisterDomain 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 RegisterName
crossing ActionRegisterName 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 ActionRegisterNameRef 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 resolve — misspelt, lapsed, or the buyer's own name — 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 ActionRenewDomain 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 ActionRenewName — 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
ActionRenewalsLeft 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
ActionResolveAddress 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 RestoreDomain
crossing Action1func RestoreDomain(cur realm, label string, owner address, registered, expires, ttl int64, frozen bool)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 RestoreDomainField
crossing Actionfunc RestoreGlobal
crossing ActionRestoreGlobal 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 Action1func RestoreName(cur realm, label, domainLabel string, owner address, registered, expires, ttl int64, frozen, isNFT bool)RestoreName re-creates one name. See RestoreDomain on ordering and on why the dates are passed rather than computed.
isNFT is carried across rather than assumed: the free tier registers names without a token, and minting one for somebody who never had it would silently upgrade their record and desynchronise the two ownership ledgers.
func RestoreNameField
crossing ActionRestoreNameField 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 ActionRestoreNames 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 ActionRestorePrimary 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.
func RestoreStatus
ActionRestoreStatus 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 SafeTransferFrom
crossing Actionnsdata only exposes one raw transfer primitive (RawTransferNFT, backed by grc721's plain TransferFrom) — the safe-receiver-interface check grc721's SafeTransferFrom adds doesn't carry much weight for a name-service NFT, so both entry points converge on the same path here.
func SealRestore
crossing ActionSealRestore 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 SetApprovalForAll
crossing Actionfunc SetDefaultNamePrice
crossing ActionSetDefaultNamePrice sets the fallback price in micro-USD, used by any domain that has not set its own base.
func SetDomainBasePrice
crossing ActionSetDomainBasePrice 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.
func SetDomainField
crossing ActionSetDomainField writes an arbitrary key on a domain, for whatever a future feature needs without a vault change.
func SetDomainLengthRules
crossing ActionSetDomainLengthRules 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.
func SetDomainNameOverrides
crossing ActionSetDomainNameOverrides prices specific labels: "420:5000000,gm:%500". Highest precedence — this beats length rules and the base.
func SetDomainOpen
crossing ActionSetDomainOpen 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 ActionSetDomainTTL 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 SetExtraLimits
crossing Actionfunc SetGlobal
crossing ActionSetGlobal 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.
func SetGracePeriod
crossing Actionfunc SetLabelLengthLimits
crossing Actionfunc SetLabelPattern
crossing ActionSetLabelPattern 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 SetMaxTerm
crossing ActionSetMaxTerm 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 ActionSetNameField 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 ActionSetNameTTL 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.
func SetPrimaryName
crossing ActionSetPrimaryName lets a wallet explicitly choose (and later change) which of its names is used for reverse resolution.
func SetProfile
crossing ActionSetProfile writes the four conventional profile fields. Each lands in the name's extension slot, where the vault enforces size bounds.
func SetReferralRules
crossing ActionSetReferralRules replaces both tier tables at once, because setting one without the other is how a cut ends up larger than the margin. Passing two empty strings turns the programme off: no discount, no commission, and registrations stop writing a referrer at all.
func SetReferralWindow
crossing Actionfunc SetTermLength
crossing Actionfunc SetTokenURIBase
crossing ActionSetTokenURIBase re-points TokenURI at a different renderer, so the NFT artwork can be replaced without touching the vault.
func SetTreasury
crossing ActionSetTreasury moves the revenue-receiving role — independent of nsdata's TransferAdmin, same reasoning as the original build's admin/treasury separation.
func SetUsdRate
crossing ActionSetUsdRate 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.
func TenureYears
ActionTenureYears is how long a wallet counts as having been here, measured from its PRIMARY name and nothing else.
The honest alternative — the oldest name the wallet holds — means walking the whole registry on every quote, which is a read that gets slower every time somebody registers and eventually stops working. Primary is one lookup and never gets slower. It is also already the wallet's declared identity, it is set automatically at a wallet's first registration, and a holder who wants an older name counted only has to make it their primary. The site says so on the referral page.
A lapsed or missing primary is zero years, not an error: tier zero.
func TransferFrom
crossing ActionTransferFrom / SafeTransferFrom are the actual enforcement point for both (a) the anti-scam pattern — blocking transfer of an expired token closes off third-party marketplace sales without blocking the owner's own ability to reclaim by renewing (renewal never goes through these) — and (b) the domains-are-never-transferable policy: blocking it here, not just at RegisterDomain, is what actually closes the loophole. (ReassignDomain in domain.gno is the deliberate admin-only exception; see its comment.)
func UnfreezeDomain
crossing Actionfunc UnfreezeName
crossing Action8
- chain stdlib
- chain/banker stdlib
- chain/runtime/unsafe stdlib
- gno.land/p/nt/ufmt/v0 package
- gno.land/r/g1xr6tgxnpled50h74eafmvxway7z0ytr5rsmeme/nsdata/v1 realm
- strconv stdlib
- strings stdlib
- time stdlib