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

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.

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.

Functions 71

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 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.

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 GlobalLengthRule

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

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

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.

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 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.

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 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. 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

Action
1func ReferralCount(who address) int64
source

ReferralCount is how many paid registrations a wallet has been credited with. Public, like every other global here.

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 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 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 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 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 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 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.

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 SafeTransferFrom

crossing Action
1func SafeTransferFrom(cur realm, from, to address, tid string)
source

nsdata 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 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 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.

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.

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.

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.

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.

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 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.

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 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.

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 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 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.

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.

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 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 Action
1func TransferFrom(cur realm, from, to address, tid string)
source

TransferFrom / 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.)

Imports 8

Source Files 14