assertIsAdmin
func()assertIsAdmin reads nsdata.GetAdmin() fresh every call — correct even immediately after a TransferAdmin, and correct for a freshly-deployed logic realm with no local state of its own to go stale. Note: admin authority to swap WHICH realm is trustedLogic, or to transfer admin itself, is deliberately NOT exposed here — those live only in nsdata's own admin.gno, callable solely by a direct wallet call from the current admin address, never proxied through logic. That's the one part of the system a compromised or buggy logic realm cannot touch.
- OID
- 0b19ce…a8da:3
assertIsAdmin details
SetLabelPattern
func(pattern string)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.
- OID
- 0b19ce…a8da:5
SetLabelPattern details
SetLabelLengthLimits
func(minLen int, maxLen int)- OID
- 0b19ce…a8da:6
SetLabelLengthLimits details
SetDefaultNamePrice
func(microUsd int64)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.
- OID
- 0b19ce…a8da:7
SetDefaultNamePrice details
SetUsdRate
func(ugnotPerUsd int64)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.
- OID
- 0b19ce…a8da:8
SetUsdRate details
maxLifecycleDays
untyped bigint// 100 years
Value
(36500 <untyped> bigint)
SetTermLength
func(days int64)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.
- OID
- 0b19ce…a8da:9
SetTermLength details
SetGracePeriod
func(days int64)- OID
- 0b19ce…a8da:10
SetGracePeriod details
SetTokenURIBase
func(base string)SetTokenURIBase re-points TokenURI at a different renderer, so the NFT artwork can be replaced without touching the vault.
- OID
- 0b19ce…a8da:11
SetTokenURIBase details
SetExtraLimits
func(keyLen int, valueLen int, entries int)- OID
- 0b19ce…a8da:12
SetExtraLimits details
minExtraKeyLen
untyped bigintValue
(32 <untyped> bigint)
minExtraValueLen
untyped bigintValue
(512 <untyped> bigint)
minExtraEntries
untyped bigintValue
(24 <untyped> bigint)
SetGlobalEntryLimit
func(entries int)SetGlobalEntryLimit sets how many keys the vault's globals may hold — its own cap now, apart from the per-record extras SetExtraLimits bounds.
- OID
- 0b19ce…a8da:13
SetGlobalEntryLimit details
SetAccountLimits
func(keyLen int, valueLen int, entries int)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.
- OID
- 0b19ce…a8da:14
SetAccountLimits details
SetMarketFee
func(spec string)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.
- OID
- 0b19ce…a8da:15
SetMarketFee details
keyMarketFee
untyped stringValue
"marketfee"
defaultMarketFee
untyped stringValue
"1000000"
feeCouldRise
func(old string, next string) boolfeeCouldRise reports whether moving from the stored spec \`old\` to \`next\` could raise the fee on any sale. Both are read the way nsmarket reads them: unset means the default. A move to zero never rises; a change of kind — flat to percentage or back — rises on some price, so it counts.
- OID
- 0b19ce…a8da:16
feeCouldRise details
feeParts
func(spec string) (flat int64, bps int64)feeParts splits a spec into (flat ugnot, basis points), the way nsmarket.parseFee does; unset is the default fee, and an unreadable spec is zero — which is also how nsmarket charges it.
- OID
- 0b19ce…a8da:17
feeParts details
assertValidFeeSpec
func(spec string)assertValidFeeSpec mirrors nsmarket.parseFee. Percentages are carried as basis points so a fee is expressible without floating point anywhere near money.
- OID
- 0b19ce…a8da:18
assertValidFeeSpec details
SetGlobal
func(key string, value string)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.
- OID
- 0b19ce…a8da:19
SetGlobal details
setGlobal
func(key string, value string)- OID
- 0b19ce…a8da:20
setGlobal details
SetTreasury
func(newTreasury .uverse.address)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.
- OID
- 0b19ce…a8da:21
SetTreasury details
SelfAddress
func() .uverse.address- OID
- 0b19ce…a8da:22
SelfAddress details
blockChunks
untyped bigintValue
(8 <untyped> bigint)
blockKeyStem
untyped stringValue
"block"
blockChunkMax
untyped bigintValue
(512 <untyped> bigint)
blockKey
func(i int) stringblockKey is the global name for one chunk: block0 … block7.
- OID
- 0b19ce…a8da:23
blockKey details
BlockedLabel
func(label string) boolBlockedLabel 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.
- OID
- 0b19ce…a8da:25
BlockedLabel details
BlockList
func(i int) stringBlockList returns one chunk, so the panel can show and edit what is actually stored rather than what somebody believes is stored.
- OID
- 0b19ce…a8da:26
BlockList details
BlockChunks
func() intBlockChunks is the number of chunks, so the panel does not hard-code a constant that this file might later change.
- OID
- 0b19ce…a8da:27
BlockChunks details
SetBlockList
func(i int, csv string)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.
- OID
- 0b19ce…a8da:28
SetBlockList details
assertNotBlocked
func(label string)assertNotBlocked is the gate both doors pass through — the paid registration and the free grant. One function, called twice, because a block that only covers one route is not a block.
- OID
- 0b19ce…a8da:29
assertNotBlocked details
keyCredit
untyped string// account field: the record above
Value
"cr"
keyClaimEnd
untyped string// global: unix second the claim window closes; unset means closed
Value
"claimend"
keyClaimMax
untyped string// global: highest list price, micro-USD, a credit covers
Value
"claimmax"
maxCreditsPerAddress
untyped bigintValue
(100 <untyped> bigint)
maxGrantBatch
untyped bigintValue
(100 <untyped> bigint)
maxClaimWindowDays
untyped bigint// one setting cannot promise more than two years
Value
(730 <untyped> bigint)
credit
typeValue
v1.credit
parseCredit
func(v string) v1.creditparseCredit FAILS CLOSED: anything it cannot read is no credit at all. A malformed record must never be read as a free name. Four fields is the record before the grant time existed; five carries it.
- OID
- 0b19ce…a8da:30
parseCredit details
readCredit
func(owner .uverse.address) v1.credit- OID
- 0b19ce…a8da:32
readCredit details
writeCredit
func(owner .uverse.address, c v1.credit)- OID
- 0b19ce…a8da:33
writeCredit details
CreditsOf
func(owner .uverse.address) (available int64, granted int64, used int64, expires int64, sellable bool)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.
- OID
- 0b19ce…a8da:34
CreditsOf details
CreditGrantedAt
func(owner .uverse.address) int64CreditGrantedAt is when an address's credits were last granted (unix seconds; 0 for none, or for a record from before the field existed).
- OID
- 0b19ce…a8da:35
CreditGrantedAt details
ClaimWindowEnd
func() int64ClaimWindowEnd 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.
- OID
- 0b19ce…a8da:36
ClaimWindowEnd details
ClaimWindowOpen
func() bool- OID
- 0b19ce…a8da:37
ClaimWindowOpen details
readClaimWindow
func(v string, now int64) boolreadClaimWindow fails closed on anything it cannot read, the same rule the free tier's window follows: a mistyped setting must not give names away.
- OID
- 0b19ce…a8da:38
readClaimWindow details
SetClaimWindow
func(end int64)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.
- OID
- 0b19ce…a8da:39
SetClaimWindow details
ClaimMaxPriceUSD
func() int64ClaimMaxPriceUSD 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).
- OID
- 0b19ce…a8da:40
ClaimMaxPriceUSD details
ClaimCapSet
func() boolClaimCapSet reports whether a cap is set at all. Unset — or unreadable, which is read the same way — no credit can be spent on anything.
- OID
- 0b19ce…a8da:41
ClaimCapSet details
claimCap
func() (int64, bool)- OID
- 0b19ce…a8da:42
claimCap details
SetClaimMaxPrice
func(microUsd int64)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.
- OID
- 0b19ce…a8da:43
SetClaimMaxPrice details
GrantCredits
func(to .uverse.address, n int64, expires int64, sellable bool)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.
- OID
- 0b19ce…a8da:44
GrantCredits details
GrantCreditsBatch
func(addrs string, n int64, expires int64, sellable bool) intGrantCreditsBatch 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.
- OID
- 0b19ce…a8da:45
GrantCreditsBatch details
grantCredits
func(to .uverse.address, n int64, expires int64, sellable bool, now int64)- OID
- 0b19ce…a8da:46
grantCredits details
RedeemCredit
func(label string, domainLabel string)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.
- OID
- 0b19ce…a8da:47
RedeemCredit details
RegisterFor
func(owner .uverse.address, label string, domainLabel string)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.
- OID
- 0b19ce…a8da:48
RegisterFor details
CreditRefusal
func(owner .uverse.address, label string, domainLabel string) stringCreditRefusal 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.
- OID
- 0b19ce…a8da:49
CreditRefusal details
creditRefusal
func(owner .uverse.address, label string, domainLabel string, now int64) string- OID
- 0b19ce…a8da:50
creditRefusal details
redeemCredit
func(owner .uverse.address, label string, domainLabel string)redeemCredit is both doors. Every check runs before anything is written, the registration lands before the credit is spent, and either failing reverts the other: the credit cannot be spent on a name that did not arrive, and a name cannot arrive without its credit being spent.
- OID
- 0b19ce…a8da:51
redeemCredit details
RegisterDomain
func(label string)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).
- OID
- 0b19ce…a8da:52
RegisterDomain details
RenewDomain
func(label string)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.
- OID
- 0b19ce…a8da:54
RenewDomain details
ReassignDomain
func(label string, newOwner .uverse.address)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.
- OID
- 0b19ce…a8da:55
ReassignDomain details
SetDomainOpen
func(label string, open bool)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.
- OID
- 0b19ce…a8da:56
SetDomainOpen details
SetDomainBasePrice
func(label string, microUsd int64)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.
- OID
- 0b19ce…a8da:57
SetDomainBasePrice details
SetDomainLengthRules
func(label string, rules string)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.
- OID
- 0b19ce…a8da:58
SetDomainLengthRules details
SetDomainNameOverrides
func(label string, rules string)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.
- OID
- 0b19ce…a8da:59
SetDomainNameOverrides details
SetDomainField
func(label string, key string, value string)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).
- OID
- 0b19ce…a8da:60
SetDomainField details
setDomainExtra
func(label string, key string, value string)- OID
- 0b19ce…a8da:61
setDomainExtra details
DeleteDomain
func(label string) (removed int64, done bool)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.
- OID
- 0b19ce…a8da:62
DeleteDomain details
SetDomainTTL
func(label string, seconds int64)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.
- OID
- 0b19ce…a8da:63
SetDomainTTL details
FreezeDomain
func(label string)- OID
- 0b19ce…a8da:64
FreezeDomain details
UnfreezeDomain
func(label string)- OID
- 0b19ce…a8da:65
UnfreezeDomain details
emitAdmin
func(fn string, kv []string)EVERY ADMIN ACTION EMITS AdminAction, with the signer and the call. There was no event anywhere in this realm, so a stolen admin key's treasury change, rate change or deletion was invisible until its effects showed up somewhere else (audit M-4/M-6). The vault now says what changed (nsdata/events.gno); this says who asked, through which call, with which arguments — the line a monitor alerts on. A failed call emits nothing: an aborted transaction keeps no events. One event type, "AdminAction", with "fn" naming the call, so a monitor subscribes once and a new admin call is covered without anybody updating a list. Profile values replayed by a restore are not repeated here, only their keys, for the reason given in nsdata/events.gno.
- OID
- 0b19ce…a8da:66
emitAdmin details
fmtInt
func(n int64) string- OID
- 0b19ce…a8da:68
fmtInt details
fmtI
func(n int) string- OID
- 0b19ce…a8da:69
fmtI details
fmtBool
func(b bool) string- OID
- 0b19ce…a8da:70
fmtBool details
keyFreeDomain
untyped string// per-domain: "1" means the free tier is open here
Value
"free"
keyGrantEnd
untyped string// global: unix seconds, or "never"
Value
"grantend"
keyGranter
untyped string// global: the address allowed to issue grants
Value
"granter"
Granter
func() .uverse.address- OID
- 0b19ce…a8da:71
Granter details
SetGranter
func(a .uverse.address)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.
- OID
- 0b19ce…a8da:73
SetGranter details
SetFreeLengths
func(min int, max int)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.
- OID
- 0b19ce…a8da:74
SetFreeLengths details
assertMayGrant
func()- OID
- 0b19ce…a8da:75
assertMayGrant details
DomainFree
func(domainLabel string) boolDomainFree 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.
- OID
- 0b19ce…a8da:76
DomainFree details
SetDomainFree
func(domainLabel string, on bool)SetDomainFree opens or closes the free tier for one domain.
- OID
- 0b19ce…a8da:77
SetDomainFree details
GrantWindowOpen
func() boolGrantWindowOpen 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.
- OID
- 0b19ce…a8da:78
GrantWindowOpen details
readGrantWindow
func(v string, now int64) boolreadGrantWindow is GrantWindowOpen with the clock and the stored value handed in, so the reading can be tested without a chain.
- OID
- 0b19ce…a8da:79
readGrantWindow details
GrantFreeName
func(label string, domainLabel string, owner .uverse.address)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.
- OID
- 0b19ce…a8da:80
GrantFreeName details
UpgradeFreeName
func(label string, domainLabel string, newOwner .uverse.address)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.
- OID
- 0b19ce…a8da:81
UpgradeFreeName details
GrantFreeRenewal
func(label string, domainLabel string)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.
- OID
- 0b19ce…a8da:82
GrantFreeRenewal details
IsFreeTier
func(label string, domainLabel string) boolIsFreeTier 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.
- OID
- 0b19ce…a8da:83
IsFreeTier details
maxHoldingsPage
untyped bigintAnswering "what does this address hold" in ONE read. The site used to ask the chain for every name in the REGISTRY and then throw away the ones that did not match — one HTTP round trip per name in the world to draw one person's collection. Six names took three seconds, and it gets linearly worse for everybody every time anybody else registers anything. The scan itself is unavoidable: nsdata keys names by domain\*label and has no index by owner, and nsdata is the permanent vault so no index can be added to it now. But the scan does not have to happen across the network. nslogic is swappable and can walk the same records inside a single query, which turns N round trips into one. Nothing is cached and nothing is trusted from the caller: every row is read from the vault at the moment of the query, exactly as before. Paged, because a query has a gas ceiling of its own and a registry with fifty thousand names in it must not become one enormous read that nobody can complete. \`after\` is the vault's own cursor, so paging here is the same paging ExportNames already does.
Value
(200 <untyped> bigint)
NamesOwnedBy
func(owner .uverse.address, after string, limit int) (rows string, next string)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.
- OID
- 0b19ce…a8da:84
NamesOwnedBy details
CountNamesOwnedBy
func(owner .uverse.address, after string, limit int) (n int, next string)CountNamesOwnedBy is the same walk without the strings, for a header that wants a number before the list has finished arriving.
- OID
- 0b19ce…a8da:86
CountNamesOwnedBy details
ProfileOf
func(label string, domainLabel string, keys string) stringEverything 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.
- OID
- 0b19ce…a8da:87
ProfileOf details
escapeValue
func(s string) stringExtension VALUES are free text — a bio can hold anything a person can type, newlines included — so they cannot be newline-separated raw. Escaping is done here rather than stripping, because silently eating a line break out of somebody's bio is data loss on a read path.
- OID
- 0b19ce…a8da:88
escapeValue details
RecentNames
func(after string, limit int) (rows string, next string)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.
- OID
- 0b19ce…a8da:89
RecentNames details
keyKeeper
untyped string// global: the address that may move the rate
Value
"keeper"
Keeper
func() .uverse.address- OID
- 0b19ce…a8da:90
Keeper details
SetKeeper
func(a .uverse.address)SetKeeper names the keeper. Admin only, and TIMELOCKED: propose exactly "SetKeeper \<addr>" with nsdata.ProposeAction, then call once it matures. ClearKeeper removes the role.
- OID
- 0b19ce…a8da:92
SetKeeper details
ClearKeeper
func()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.
- OID
- 0b19ce…a8da:93
ClearKeeper details
isGovernor
func(a .uverse.address) boolisGovernor is the vault's three roles, read fresh. An unset role matches nobody.
- OID
- 0b19ce…a8da:94
isGovernor details
assertIsKeeper
func(caller .uverse.address)assertIsKeeper lets the keeper through and nobody else, with the refusal a stranger has always had from an admin call.
- OID
- 0b19ce…a8da:95
assertIsKeeper details
keeperSetRate
func(ugnotPerUsd int64)keeperSetRate is SetUsdRate for the keeper: the same range check, the bound and the shared hourly slot, and no door into the timelock. Every move emits KeeperAction, with the value it replaced.
- OID
- 0b19ce…a8da:96
keeperSetRate details
keeperStep
func(setting string, old int64, next int64)keeperStep is authorizeTweak with the timelock door bricked up: an instant step inside its direction's bound, after that direction's rest, or a refusal. It writes the same clocks, so the admin's next instant move in that direction waits the same time.
- OID
- 0b19ce…a8da:97
keeperStep details
keyDisplayName
untyped stringProfile field names inside a name's extension slot. The vault stores them as opaque strings and bounds their size; what they MEAN is this realm's convention, so new profile fields need no vault change.
Value
"displayName"
keyBio
untyped stringProfile field names inside a name's extension slot. The vault stores them as opaque strings and bounds their size; what they MEAN is this realm's convention, so new profile fields need no vault change.
Value
"bio"
keyWebsite
untyped stringProfile field names inside a name's extension slot. The vault stores them as opaque strings and bounds their size; what they MEAN is this realm's convention, so new profile fields need no vault change.
Value
"website"
keyAvatar
untyped stringProfile field names inside a name's extension slot. The vault stores them as opaque strings and bounds their size; what they MEAN is this realm's convention, so new profile fields need no vault change.
Value
"avatar"
RegisterName
func(label string, domainLabel string)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.
- OID
- 0b19ce…a8da:98
RegisterName details
RegisterNameRef
func(label string, domainLabel string, refName string)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.
- OID
- 0b19ce…a8da:100
RegisterNameRef details
registerName
func(label string, domainLabel string, refName string)- OID
- 0b19ce…a8da:101
registerName details
RenewName
func(label string, domainLabel string)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.
- OID
- 0b19ce…a8da:102
RenewName details
SetProfile
func(label string, domainLabel string, displayName string, bio string, website string, avatar string)SetProfile writes the four conventional profile fields. Each lands in the name's extension slot, where the vault enforces size bounds.
- OID
- 0b19ce…a8da:103
SetProfile details
SetNameField
func(label string, domainLabel string, key string, value string)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.
- OID
- 0b19ce…a8da:104
SetNameField details
setExtra
func(label string, domainLabel string, key string, value string)- OID
- 0b19ce…a8da:105
setExtra details
assertNameOwner
func(label string, domainLabel string)- OID
- 0b19ce…a8da:106
assertNameOwner details
SetPrimaryName
func(label string, domainLabel string)SetPrimaryName lets a wallet explicitly choose (and later change) which of its names is used for reverse resolution.
- OID
- 0b19ce…a8da:107
SetPrimaryName details
ClearPrimaryName
func()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.
- OID
- 0b19ce…a8da:108
ClearPrimaryName details
maxTTL
int64maxTTL caps a cache policy, a name's or a domain's: 2^31-1 seconds, about 68 years, the ceiling DNS itself uses (RFC 2181). Anything longer reads as forever to every resolver, and an unbounded value did not survive a snapshot (restore.gno, ttlSecs).
Value
2147483647
SetNameTTL
func(label string, domainLabel string, seconds int64)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.
- OID
- 0b19ce…a8da:109
SetNameTTL details
FreezeName
func(label string, domainLabel string)- OID
- 0b19ce…a8da:110
FreezeName details
UnfreezeName
func(label string, domainLabel string)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.
- OID
- 0b19ce…a8da:111
UnfreezeName details
maxUnfreezeBatch
untyped bigintUnfreezeNames releases several names behind ONE timelocked action: propose exactly "UnfreezeNames a\*meme,b\*doge,…" (token ids, comma- separated, at most maxUnfreezeBatch). A stolen key freezes names one call each and instantly, about a thousand a block; releasing them one action each, with at most 32 actions pending, took ⌈N/32⌉ timelocks (re-audit 2026-10-16, L-3). One unknown or malformed name refuses the whole call.
Value
(64 <untyped> bigint)
UnfreezeNames
func(tids string)- OID
- 0b19ce…a8da:112
UnfreezeNames details
ResolveName
func(label string, domainLabel string) .uverse.address- OID
- 0b19ce…a8da:113
ResolveName details
ResolveAddress
func(addr .uverse.address) stringResolveAddress 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.
- OID
- 0b19ce…a8da:114
ResolveAddress details
splitTokenID
func(tid string) (label string, domainLabel string)splitTokenID splits a \`label\*domain\` token ID.
- OID
- 0b19ce…a8da:115
splitTokenID details
isDomainToken
func(tid string) bool- OID
- 0b19ce…a8da:116
isDomainToken details
isTokenValid
func(tid string) boolisTokenValid checks the underlying domain/name's expiry via nsdata's own validity reads — the anti-scam pattern (an expired name must not be sellable) enforced at the transfer boundary, not just at read time.
- OID
- 0b19ce…a8da:118
isTokenValid details
assertNotFrozen
func(tid string)assertNotFrozen is the check that made freezing mean something. FreezeName set a bit that only ever gated RE-REGISTRATION after a lapse. Transfer, approval, listing and sale all ignored it, so the one manual lever the takedown process relies on did nothing about the case it exists for: a disputed or stolen name could be approved to a fresh wallet, listed and sold while the interface showed it as frozen. The bit was already stored and already readable — GetNameInfo returns it — so this needs no change to the permanent vault, only the read that was never performed.
- OID
- 0b19ce…a8da:119
assertNotFrozen details
takePayment
func(price int64)takePayment verifies the attached send-envelope covers \`price\` ugnot, forwards the price to the treasury, and refunds any overpayment to the caller in the same transaction. Nothing is ever retained: every ugnot that arrives leaves again, as either payment or refund. It used to take a \`beneficiary\` and split the price fee/remainder, routing the remainder to treasury only while \`beneficiary == admin\`. That comparison was a proxy for "is this a project-owned domain", and it silently broke the moment \`admin\` rotated — the planned EOA-to- multisig step — because a domain's owner is frozen into its record at registration time. After a rotation, every registration's remainder would have been paid to the retired key instead of the treasury, with no error and no log line. The heuristic is gone rather than patched. Domains are admin-only to create and permanently non-transferable, so every domain is project-owned by construction and 100% of every payment belongs to the treasury. \`platformFeeBps\` stays in nsdata's config for a future logic realm that reintroduces third-party domain ownership; reinstating the split is then a logic-realm swap, not a migration.
- OID
- 0b19ce…a8da:120
takePayment details
takePaymentSplit
func(price int64, cutTo .uverse.address, cut int64)takePaymentSplit is takePayment with a referrer's cut carved out of the same price. The buyer pays exactly what they were quoted; the cut is not added on top, it is a share of what the treasury would otherwise have received. All three legs settle inside the buyer's own transaction — treasury, referrer, refund — so this realm's balance is the same after the call as before it. That is not a stylistic preference: holding a referrer's cut until they claim it would make this an account you can have a balance in, and that is a different kind of thing to operate, subject to a different set of rules, run by people with licences. The cut is paid now or it is not paid.
- OID
- 0b19ce…a8da:122
takePaymentSplit details
assertOnlyUgnot
func(sent chain.Coins)assertOnlyUgnot rejects any other denomination outright. Only the ugnot amount was ever accounted for, and neither realm has a withdrawal path, so a foreign coin sent alongside a payment would have been stranded in this realm's address permanently. Refusing the whole transaction is simpler than sweeping it back and keeps the "this realm never retains funds" invariant absolute.
- OID
- 0b19ce…a8da:123
assertOnlyUgnot details
assertNoSend
func()assertNoSend guards every entry point that has no price. Without it, coins attached to a free call (a renewal of a project domain, a profile edit, an admin setter) would land in this realm's address with no way back out. IT APPLIES TO DIRECT WALLET CALLS ONLY, and that qualification is not a loosening — it is the guard finally matching its own reason. OriginSend() reports what the TRANSACTION carried, not what this call received. Measured on sapphire-1 with a pair of probe realms: a user attaches 500000ugnot to realm A, A calls realm B, and B's OriginSend() reads 500000ugnot even though every one of those coins is sitting in A's address. B never saw them and cannot strand them. So for a realm-to-realm call this was rejecting a payment this realm never received. It is the reason a marketplace could not transfer a name inside the same transaction that pays for it — the transfer is a free call, the transaction is not, and the two are unrelated. A two-transaction sale is the alternative, and that means somebody's money or somebody's name sitting somewhere in between. Not here.
- OID
- 0b19ce…a8da:124
assertNoSend details
keyGlobalLens
untyped string// global length rules
Value
"glen"
keyPremium
untyped string// premium1, premium2, ... see below
Value
"premium"
maxPremiumKeys
untyped bigintGlobal pricing layers that sit ACROSS every domain rather than inside one — see SPEC.md §27. Two of them, and they work at different points: GLOBAL LENGTH RULES are a FLOOR beneath each domain's own length rules, consulted only when the domain has nothing to say about that length. "three-character names cost $5 everywhere" becomes one entry instead of an edit to all twenty-seven domains, and a domain that wants its own answer still overrides it. GLOBAL NAME PREMIUMS are an ADJUSTMENT applied after the price has already resolved. Making `og` premium is one entry covering `og*degen`, `og*meme` and every domain invented later. Deliberately not the per-name override, which REPLACES a price outright: "this name costs exactly X here" and "this name is worth more everywhere" are different tools and both are worth having. Full order, most specific first: 1. per-name override (domain "nm") 2. per-domain length rule (domain "len") 3. global length rule (global "glen") 4. domain base price (domain "price") 5. global default (global "defaultPrice") then the global name premium modifies the result, then any referral discount comes off what is left.
Value
(8 <untyped> bigint)
GlobalLengthRule
func(n int) (string, bool)GlobalLengthRule returns the global rule for a label length, if any.
- OID
- 0b19ce…a8da:125
GlobalLengthRule details
PremiumFor
func(label string) stringPremiumFor returns the raw modifier for a name across all premium keys, or "" when the name is not premium anywhere.
- OID
- 0b19ce…a8da:127
PremiumFor details
applyPremium
func(mod string, price int64) int64applyPremium adjusts an already-resolved price. ZERO ALWAYS STAYS ZERO. A fixed "+$2.00" would otherwise silently undo a giveaway tier set months earlier, and a free tier that quietly stops being free is worse than never having offered one. This is decided explicitly rather than inherited from the percentage case happening to work out at zero. Forms: "%5" / "+%5" five percent more · "-%10" ten percent less · "2.00" two dollars more · "-0.25" twenty-five cents less · "=5.00" exactly five dollars, ignoring everything resolved above.
- OID
- 0b19ce…a8da:128
applyPremium details
parseUSD
func(s string) (int64, bool)parseUSD reads a human dollar figure — "2", "2.5", "2.50", "0.069" — into micro-USD. Written by hand rather than with floats: a float would make "0.07" cost a different number of micro-dollars depending on the platform, and prices must be exact.
- OID
- 0b19ce…a8da:129
parseUSD details
SetGlobalLengthRules
func(rules string)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.
- OID
- 0b19ce…a8da:130
SetGlobalLengthRules details
SetPremiumNames
func(page int, csv string)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.
- OID
- 0b19ce…a8da:131
SetPremiumNames details
PremiumNames
func(page int) stringPremiumNames reads one page back, so the admin panel can show what is stored rather than what it last sent.
- OID
- 0b19ce…a8da:132
PremiumNames details
GlobalLengthRules
func() stringGlobalLengthRules returns the whole rule string, for the same reason.
- OID
- 0b19ce…a8da:133
GlobalLengthRules details
normalizePremiums
func(csv string) stringnormalizePremiums validates a premium list before storage. Same reasoning as normalizeRules: a malformed entry must never reach the vault, because every later lookup would carry it.
- OID
- 0b19ce…a8da:134
normalizePremiums details
validPremiumValue
func(v string) bool- OID
- 0b19ce…a8da:135
validPremiumValue details
keyPrice
untyped string// domain base, micro-USD
Value
"price"
keyLens
untyped string// length rules
Value
"len"
keyNames
untyped string// per-name overrides
Value
"nm"
keyOpen
untyped string// "0" closes the domain to new names
Value
"open"
keyDefaultPrice
untyped stringPricing lives here, not in the vault. The vault stores numbers as opaque strings and has no opinion about what they mean, so the whole model can be replaced by swapping this realm. PRICES ARE DENOMINATED IN USD, held as MICRO-USD (1 USD = 1\_000\_000) so fractions of a cent are expressible — a name that costs about a gumball needs that. Nothing is stored in ugnot: the chain's token is just the settlement rail, converted at payment time from a stored rate. Accepting a second token later is then only a second rate, not a second price list. PRECEDENCE — most specific wins: 1. per-name override (keyNames: "420:5000000,gm:%500") 2. per-length rule (keyLens: "3:2000000,4:%150,5:0") 3. GLOBAL length rule (global keyGlobalLens — see premium.gno) 4. domain base price (keyPrice: "500000" = $0.50) 5. global default (global keyDefaultPrice) then the GLOBAL NAME PREMIUM adjusts the result, and a referral discount comes off last. Both live in premium.gno. A rule value is either an absolute micro-USD amount or "%N", meaning N percent of that domain's base — so repricing a domain once carries every relative rule under it. "0" means free, which is how giveaway tiers are expressed: "all five-character names under \*pleb are free" is just a length rule resolving to zero. Rules are stored as CSV in ONE key each rather than one key per rule. The vault caps extension keys at 32 bytes and 24 entries per record; a key like "nm:somelongname" breaks the first and a few dozen premium names break the second. Two CSV keys sidestep both.
Value
"defaultPrice"
keyRate
untyped string// ugnot equal to 1 USD
Value
"rateUgnotPerUsd"
microUSD
int64Pricing lives here, not in the vault. The vault stores numbers as opaque strings and has no opinion about what they mean, so the whole model can be replaced by swapping this realm. PRICES ARE DENOMINATED IN USD, held as MICRO-USD (1 USD = 1\_000\_000) so fractions of a cent are expressible — a name that costs about a gumball needs that. Nothing is stored in ugnot: the chain's token is just the settlement rail, converted at payment time from a stored rate. Accepting a second token later is then only a second rate, not a second price list. PRECEDENCE — most specific wins: 1. per-name override (keyNames: "420:5000000,gm:%500") 2. per-length rule (keyLens: "3:2000000,4:%150,5:0") 3. GLOBAL length rule (global keyGlobalLens — see premium.gno) 4. domain base price (keyPrice: "500000" = $0.50) 5. global default (global keyDefaultPrice) then the GLOBAL NAME PREMIUM adjusts the result, and a referral discount comes off last. Both live in premium.gno. A rule value is either an absolute micro-USD amount or "%N", meaning N percent of that domain's base — so repricing a domain once carries every relative rule under it. "0" means free, which is how giveaway tiers are expressed: "all five-character names under \*pleb are free" is just a length rule resolving to zero. Rules are stored as CSV in ONE key each rather than one key per rule. The vault caps extension keys at 32 bytes and 24 entries per record; a key like "nm:somelongname" breaks the first and a few dozen premium names break the second. Two CSV keys sidestep both.
Value
1000000
fallbackPriceUSD
int64// $0.50
Value
500000
fallbackRate
int64Pricing lives here, not in the vault. The vault stores numbers as opaque strings and has no opinion about what they mean, so the whole model can be replaced by swapping this realm. PRICES ARE DENOMINATED IN USD, held as MICRO-USD (1 USD = 1\_000\_000) so fractions of a cent are expressible — a name that costs about a gumball needs that. Nothing is stored in ugnot: the chain's token is just the settlement rail, converted at payment time from a stored rate. Accepting a second token later is then only a second rate, not a second price list. PRECEDENCE — most specific wins: 1. per-name override (keyNames: "420:5000000,gm:%500") 2. per-length rule (keyLens: "3:2000000,4:%150,5:0") 3. GLOBAL length rule (global keyGlobalLens — see premium.gno) 4. domain base price (keyPrice: "500000" = $0.50) 5. global default (global keyDefaultPrice) then the GLOBAL NAME PREMIUM adjusts the result, and a referral discount comes off last. Both live in premium.gno. A rule value is either an absolute micro-USD amount or "%N", meaning N percent of that domain's base — so repricing a domain once carries every relative rule under it. "0" means free, which is how giveaway tiers are expressed: "all five-character names under \*pleb are free" is just a length rule resolving to zero. Rules are stored as CSV in ONE key each rather than one key per rule. The vault caps extension keys at 32 bytes and 24 entries per record; a key like "nm:somelongname" breaks the first and a few dozen premium names break the second. Two CSV keys sidestep both.
Value
14492753
maxRules
untyped bigintPricing lives here, not in the vault. The vault stores numbers as opaque strings and has no opinion about what they mean, so the whole model can be replaced by swapping this realm. PRICES ARE DENOMINATED IN USD, held as MICRO-USD (1 USD = 1\_000\_000) so fractions of a cent are expressible — a name that costs about a gumball needs that. Nothing is stored in ugnot: the chain's token is just the settlement rail, converted at payment time from a stored rate. Accepting a second token later is then only a second rate, not a second price list. PRECEDENCE — most specific wins: 1. per-name override (keyNames: "420:5000000,gm:%500") 2. per-length rule (keyLens: "3:2000000,4:%150,5:0") 3. GLOBAL length rule (global keyGlobalLens — see premium.gno) 4. domain base price (keyPrice: "500000" = $0.50) 5. global default (global keyDefaultPrice) then the GLOBAL NAME PREMIUM adjusts the result, and a referral discount comes off last. Both live in premium.gno. A rule value is either an absolute micro-USD amount or "%N", meaning N percent of that domain's base — so repricing a domain once carries every relative rule under it. "0" means free, which is how giveaway tiers are expressed: "all five-character names under \*pleb are free" is just a length rule resolving to zero. Rules are stored as CSV in ONE key each rather than one key per rule. The vault caps extension keys at 32 bytes and 24 entries per record; a key like "nm:somelongname" breaks the first and a few dozen premium names break the second. Two CSV keys sidestep both.
Value
(64 <untyped> bigint)
maxPriceUSD
int64Pricing lives here, not in the vault. The vault stores numbers as opaque strings and has no opinion about what they mean, so the whole model can be replaced by swapping this realm. PRICES ARE DENOMINATED IN USD, held as MICRO-USD (1 USD = 1\_000\_000) so fractions of a cent are expressible — a name that costs about a gumball needs that. Nothing is stored in ugnot: the chain's token is just the settlement rail, converted at payment time from a stored rate. Accepting a second token later is then only a second rate, not a second price list. PRECEDENCE — most specific wins: 1. per-name override (keyNames: "420:5000000,gm:%500") 2. per-length rule (keyLens: "3:2000000,4:%150,5:0") 3. GLOBAL length rule (global keyGlobalLens — see premium.gno) 4. domain base price (keyPrice: "500000" = $0.50) 5. global default (global keyDefaultPrice) then the GLOBAL NAME PREMIUM adjusts the result, and a referral discount comes off last. Both live in premium.gno. A rule value is either an absolute micro-USD amount or "%N", meaning N percent of that domain's base — so repricing a domain once carries every relative rule under it. "0" means free, which is how giveaway tiers are expressed: "all five-character names under \*pleb are free" is just a length rule resolving to zero. Rules are stored as CSV in ONE key each rather than one key per rule. The vault caps extension keys at 32 bytes and 24 entries per record; a key like "nm:somelongname" breaks the first and a few dozen premium names break the second. Two CSV keys sidestep both.
Value
1000000000000
maxRulePct
untyped bigintPricing lives here, not in the vault. The vault stores numbers as opaque strings and has no opinion about what they mean, so the whole model can be replaced by swapping this realm. PRICES ARE DENOMINATED IN USD, held as MICRO-USD (1 USD = 1\_000\_000) so fractions of a cent are expressible — a name that costs about a gumball needs that. Nothing is stored in ugnot: the chain's token is just the settlement rail, converted at payment time from a stored rate. Accepting a second token later is then only a second rate, not a second price list. PRECEDENCE — most specific wins: 1. per-name override (keyNames: "420:5000000,gm:%500") 2. per-length rule (keyLens: "3:2000000,4:%150,5:0") 3. GLOBAL length rule (global keyGlobalLens — see premium.gno) 4. domain base price (keyPrice: "500000" = $0.50) 5. global default (global keyDefaultPrice) then the GLOBAL NAME PREMIUM adjusts the result, and a referral discount comes off last. Both live in premium.gno. A rule value is either an absolute micro-USD amount or "%N", meaning N percent of that domain's base — so repricing a domain once carries every relative rule under it. "0" means free, which is how giveaway tiers are expressed: "all five-character names under \*pleb are free" is just a length rule resolving to zero. Rules are stored as CSV in ONE key each rather than one key per rule. The vault caps extension keys at 32 bytes and 24 entries per record; a key like "nm:somelongname" breaks the first and a few dozen premium names break the second. Two CSV keys sidestep both.
Value
(10000 <untyped> bigint)
DomainOpen
func(domainLabel string) boolDomainOpen 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.
- OID
- 0b19ce…a8da:136
DomainOpen details
DefaultPriceUSD
func() int64- OID
- 0b19ce…a8da:138
DefaultPriceUSD details
DomainBasePriceUSD
func(domainLabel string) int64- OID
- 0b19ce…a8da:139
DomainBasePriceUSD details
NamePriceUSD
func(domainLabel string, label string) int64NamePriceUSD resolves the four inputs in precedence order and returns micro-USD. This is the authoritative price; ugnot is derived from it.
- OID
- 0b19ce…a8da:140
NamePriceUSD details
resolveBeforePremium
func(domainLabel string, label string) int64resolveBeforePremium runs steps 1-5. Separated from NamePriceUSD so the admin panel can show WHY a price is what it is: the figure before the premium and the figure after are both meaningful, and a single function returning only the total makes a wrong price impossible to diagnose.
- OID
- 0b19ce…a8da:141
resolveBeforePremium details
ExplainPriceUSD
func(domainLabel string, 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.
- OID
- 0b19ce…a8da:142
ExplainPriceUSD details
RateUgnotPerUsd
func() int64RateUgnotPerUsd 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.
- OID
- 0b19ce…a8da:143
RateUgnotPerUsd details
NamePrice
func(domainLabel string, label string) int64NamePrice 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.
- OID
- 0b19ce…a8da:144
NamePrice details
applyRule
func(v string, base int64) int64applyRule reads a stored rule. An out-of-range one is treated like a malformed one — the base applies — because stored values can arrive by the restore window, unvalidated, and an unbounded percentage would wrap the multiplication into a small, wrong, price.
- OID
- 0b19ce…a8da:145
applyRule details
lookupRule
func(csv string, want string) (string, bool)- OID
- 0b19ce…a8da:146
lookupRule details
normalizeRules
func(csv string, numericKeys bool) stringnormalizeRules validates and canonicalises a rule string before it is stored, so a malformed entry can never reach storage and poison every later lookup.
- OID
- 0b19ce…a8da:147
normalizeRules details
keyRef
untyped string// on a NAME: the referrer's address
Value
"ref"
keyRefCut
untyped string// global: the referrer's percentage, stored "0:N"
Value
"refcut"
keyRefDisc
untyped string// global: the buyer's discount, stored "0:N"
Value
"refdisc"
keyRefCount
untyped string// ACCOUNT field (nsdata's per-address store): how many referred
Value
"rc"
keyRefWin
untyped string// global: days a captured code stays valid
Value
"refwin"
maxRefPercent
untyped bigint// cut plus discount, together; an admin may lower it, never exceed it
Value
(10 <untyped> bigint)
refHoldDays
untyped bigint// a code earns only once its holder has kept a paid name this long
Value
(30 <untyped> bigint)
tierPercent
func(spec string, years int64) int64tierPercent reads a stored rate. The canonical form is a one-tier table at year zero, "0:N", which every reader of the old escalating tables — the site among them — reads as N at any tenure. A legacy multi-tier table counts only at year zero (RefRates asks for nothing else), so a table that once escalated cannot escalate now. Junk entries are skipped, not fatal, so one bad character cannot stop every sale; a percentage above the cap is clamped.
- OID
- 0b19ce…a8da:148
tierPercent details
RefRates
func() (cutPct int64, discountPct int64)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.
- OID
- 0b19ce…a8da:150
RefRates details
clampRates
func(cut int64, disc int64) (int64, int64)clampRates is the 10% ceiling on its own, so it can be tested without a vault: negative inputs are zero, the discount is capped first, and the cut takes whatever room is left.
- OID
- 0b19ce…a8da:151
clampRates details
defaultRefCut
untyped stringTHE PRESET THE PROJECT LAUNCHES WITH: five percent to the referrer, five off for the buyer — the whole 10% the owner allowed, split evenly. Written here rather than typed into the admin panel at launch because a number that exists only in somebody's memory of a conversation gets set wrong once and noticed a month later. It is a DEFAULT, not a policy: SetReferralRules overwrites it, and empty rates switch the programme off.
Value
"0:5"
defaultRefDiscount
untyped stringTHE PRESET THE PROJECT LAUNCHES WITH: five percent to the referrer, five off for the buyer — the whole 10% the owner allowed, split evenly. Written here rather than typed into the admin panel at launch because a number that exists only in somebody's memory of a conversation gets set wrong once and noticed a month later. It is a DEFAULT, not a policy: SetReferralRules overwrites it, and empty rates switch the programme off.
Value
"0:5"
DefaultReferralRules
func() (cut string, discount string)DefaultReferralRules lets the site show the presets before anybody commits them.
- OID
- 0b19ce…a8da:152
DefaultReferralRules details
SeedReferralRules
func()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.
- OID
- 0b19ce…a8da:153
SeedReferralRules details
RefCutRules
func() string- OID
- 0b19ce…a8da:154
RefCutRules details
RefDiscountRules
func() string- OID
- 0b19ce…a8da:155
RefDiscountRules details
RefWindowDays
func() int64RefWindowDays 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.
- OID
- 0b19ce…a8da:156
RefWindowDays details
SetReferralWindow
func(days int64)- OID
- 0b19ce…a8da:157
SetReferralWindow details
ReferralQuote
func(refName string, buyer .uverse.address) (referrer .uverse.address, tenure int64, discountPct int64, cutPct int64)- OID
- 0b19ce…a8da:158
ReferralQuote details
SetReferralRules
func(cutSpec string, discountSpec string)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.
- OID
- 0b19ce…a8da:159
SetReferralRules details
normalizeRate
func(spec string) (stored string, pct int64)normalizeRate reads one rate as an admin types it — "5", "0:5", or "" — and returns its stored form, "0:5" or "" for none, and the percent. Anything else panics before it can reach storage.
- OID
- 0b19ce…a8da:160
normalizeRate details
TenureYears
func(who .uverse.address) int64TenureYears 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.
- OID
- 0b19ce…a8da:161
TenureYears details
PayoutFor
func(who .uverse.address) .uverse.address- OID
- 0b19ce…a8da:162
PayoutFor details
ReferrerOf
func(label string, domainLabel string) .uverse.address- OID
- 0b19ce…a8da:163
ReferrerOf details
resolveReferrer
func(refName string, buyer .uverse.address) .uverse.address- OID
- 0b19ce…a8da:164
resolveReferrer details
heldLongEnough
func(since int64, now int64) boolheldLongEnough is the thirty-day rule on its own. A zero \`since\` means the registry has not seen the current holder yet — never long enough.
- OID
- 0b19ce…a8da:165
heldLongEnough details
StartReferralClock
func(label string, domainLabel string)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.
- OID
- 0b19ce…a8da:166
StartReferralClock details
PriceWithReferral
func(domainLabel string, label string, refName string, buyer .uverse.address) int64PriceWithReferral 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.
- OID
- 0b19ce…a8da:167
PriceWithReferral details
referralCut
func(ref .uverse.address, price int64) (.uverse.address, int64)- OID
- 0b19ce…a8da:168
referralCut details
creditReferral
func(ref .uverse.address)creditReferral records one paid referral against a wallet. The COUNT and nothing else. There was a running total of micro-USD beside it, and it is gone, because a number stored in the vault is a number anyone with an RPC endpoint can read — there is no such thing as an admin-only global on a public chain, and building the site as though there were would be the kind of privacy that works right up until somebody checks. Not storing it is the only real reduction available. The payments are still in the block history and always will be, so anyone determined can add them up; what we can decline to do is publish a convenient running total ourselves. tools/referrals.py reconstructs it from the chain when we want it, which costs no storage and publishes nothing. The registrant's transaction pays for this entry: one small field, written once per referrer and overwritten after — the price of the discount they just received. IT LIVES IN THE VAULT'S PER-ADDRESS STORE, and until it did, no referred registration ever succeeded. It was the global "ref:" + address, 44 bytes against a 32-byte cap on global keys; the vault refused it, this panicked, and the whole purchase reverted after the price was taken — on every chain the programme ran on. A key that short would only have moved the failure: globals held at most 24 entries, shared with the price list, so it would have broken again at about the twenty-first referrer. An address is a key there now, and each wallet's fields are bounded on their own.
- OID
- 0b19ce…a8da:169
creditReferral details
ReferralCount
func(who .uverse.address) int64ReferralCount is how many paid registrations a wallet has been credited with. Public, like everything in the vault.
- OID
- 0b19ce…a8da:170
ReferralCount details
parseInt64
func(s string) int64- OID
- 0b19ce…a8da:171
parseInt64 details
refBoardPages
untyped bigintHow many vault pages one ReferralBoard call may scan.
Value
(5 <untyped> bigint)
ReferralBoard
func(after string, limit int) (rows string, next string)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.
- OID
- 0b19ce…a8da:172
ReferralBoard details
defaultFreeMinLength
untyped bigintValue
(5 <untyped> bigint)
defaultFreeMaxLength
untyped bigintValue
(63 <untyped> bigint)
keyFreeMin
untyped stringValue
"freemin"
keyFreeMax
untyped stringValue
"freemax"
FreeMinLength
func() intFreeMinLength 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.
- OID
- 0b19ce…a8da:173
FreeMinLength details
FreeMaxLength
func() int- OID
- 0b19ce…a8da:175
FreeMaxLength details
FreeLabelAllowed
func(label string) boolFreeLabelAllowed 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.
- OID
- 0b19ce…a8da:176
FreeLabelAllowed details
keyRestore
untyped stringThe vault key holding the state of this whole mechanism.
Value
"restore"
restoreNever
untyped string// never opened; the normal state forever
Value
""
restoreSealed
untyped string// closed permanently; cannot be reopened
Value
"sealed"
restoreOpen
untyped string// followed by the unix second it lapses
Value
"open:"
maxRestoreDays
untyped bigintThe longest a restore window may be left open. A restore is a day of work, not a season, and an unbounded window is just "open forever" spelled differently — the failure here is nobody remembering to seal it, so the deadline seals it for them.
Value
(30 <untyped> bigint)
RestoreStatus
func() (state string, deadline int64, restored int64)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.
- OID
- 0b19ce…a8da:177
RestoreStatus details
readRestoreState
func(raw string, now int64) (state string, deadline int64)readRestoreState is the state machine on its own, with the clock passed in. Split out so the one piece of logic that decides whether the door is open can be tested exhaustively without a chain under it.
- OID
- 0b19ce…a8da:179
readRestoreState details
keyRestoredCount
untyped stringValue
"restored"
restoredCount
func() int64- OID
- 0b19ce…a8da:180
restoredCount details
setRestoreKey
func(key string, value string)THE RESTORE KEYS GO THROUGH THE VAULT'S RESERVED DOOR, and every refusal panics. They used to go through SetGlobalRecord, written for the first vault, which had no reserved keys. This vault refuses "restore" and "restored" there — and the three call sites threw the error away. So SealRestore returned success and sealed nothing, OpenRestore returned success and opened nothing (every Restore\* call then said "never been opened", in the middle of a paused migration), and the counter never moved. A returned error in gno does not revert anything; ignoring one is how a control reads as live while doing nothing. SetReservedGlobalRecord is the machinery's own door, and the vault now refuses to change a seal through it as well: a sealed restore stays sealed whatever any logic realm asks, so a future logic generation cannot talk its way out of it either.
- OID
- 0b19ce…a8da:181
setRestoreKey details
bumpRestored
func(n int64)- OID
- 0b19ce…a8da:182
bumpRestored details
assertNotRestoring
func()assertNotRestoring CLOSES REGISTRATION WHILE A RESTORE WINDOW IS OPEN (re-audit 2026-10-16, M-4). The window opens on an empty registry, and the snapshots it replays are committed to the repository, so the list of who held what is public: anybody could register a holder's name before its batch arrived — at the genesis price, before the price rules were back — and the holder never got it back, while the batch holding it reverted and stopped the run. Every door that creates a name, or makes a free one sellable, passes this: RegisterName and RegisterNameRef, RedeemCredit and RegisterFor, GrantFreeName, UpgradeFreeName (and the lab's RegisterLabName). Sealed, or lapsed, the registry sells again.
- OID
- 0b19ce…a8da:183
assertNotRestoring details
assertRestoring
func()assertRestoring is the gate every Restore\* function passes through.
- OID
- 0b19ce…a8da:184
assertRestoring details
OpenRestore
func(days int64)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.
- OID
- 0b19ce…a8da:185
OpenRestore details
SealRestore
func()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.
- OID
- 0b19ce…a8da:186
SealRestore details
RestoreDomain
func(label string, owner .uverse.address, registered int64, expires int64, 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.
- OID
- 0b19ce…a8da:187
RestoreDomain details
RestoreName
func(label string, domainLabel string, owner .uverse.address, registered int64, expires int64, ttl int64, frozen bool, 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 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.
- OID
- 0b19ce…a8da:188
RestoreName details
RestoreNames
func(blob string) intRestoreNames 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 ",": label,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.
- OID
- 0b19ce…a8da:189
RestoreNames details
restoreRec
typerestoreRec is one parsed line of the blob.
Value
v1.restoreRec
parseRestoreRecord
func(rec string, i int) v1.restoreRecparseRestoreRecord reads one record and refuses anything it is not sure about. EVERY CHECK HERE IS A PANIC AND NOT A SKIP. The blob is machine-written from a snapshot, so a malformed record does not mean "this one name is odd", it means the file is not what it was believed to be — and the worst outcome available is a restore that appears to succeed while quietly dropping the records it could not read. Somebody would find out months later when one person asked where their name went.
- OID
- 0b19ce…a8da:190
parseRestoreRecord details
mustSecs
func(s string, i int, what string) int64- OID
- 0b19ce…a8da:191
mustSecs details
ttlSecs
func(s string, i int) int64ttlSecs reads a TTL and caps it at maxTTL (name.gno). A holder may have set any non-negative int64 before the cap existed, and a snapshot written by JavaScript rounded such a value above 2^53 — MaxInt64 came back as 9223372036854776000, which no int64 holds — so the whole batch of fifty reverted (re-audit 2026-10-16, M-3). A run of digits too large to parse is read as the longest a TTL may be; anything else malformed still refuses the record.
- OID
- 0b19ce…a8da:192
ttlSecs details
clampTTL
func(v int64) int64- OID
- 0b19ce…a8da:193
clampTTL details
RestoreNameField
func(label string, domainLabel string, key string, value string)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.
- OID
- 0b19ce…a8da:194
RestoreNameField details
RestoreDomainField
func(label string, key string, value string)- OID
- 0b19ce…a8da:195
RestoreDomainField details
RestoreGlobal
func(key string, value string)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.
- OID
- 0b19ce…a8da:196
RestoreGlobal details
RestoreAccountField
func(owner .uverse.address, key string, value string)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.
- OID
- 0b19ce…a8da:197
RestoreAccountField details
RestorePrimary
func(owner .uverse.address, label string, domainLabel string)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).
- OID
- 0b19ce…a8da:198
RestorePrimary details
keyMaxTerm
untyped string// domain extra AND global: years of runway
Value
"maxterm"
defaultMaxTerm
int64How far ahead a name may be paid. Renewal was unbounded. RenewNameRecord adds one term to the previous due date and asks no further questions, so a holder pressing renew seven times bought seven years of runway, and nothing anywhere said that was not intended. It is a bug in two directions at once: - Free domains have a base price of zero. Renewal is free too, so a script could take every good name under \*gnoland and hold it for a century for the cost of gas. There is no economic brake on a free name at all; the term limit IS the brake. - A paid name held fifty years ahead is fifty years of a promise we have made against a rate written by hand and a testnet that will be replaced. Selling that is easy and honouring it is not. The limit is RUNWAY, not total lifetime: at most N years may be outstanding at any moment. Nobody is stopped from holding a name forever — they renew as the runway burns down, which is the same arrangement every domain registrar has always used and the one people already understand. Per domain first, then a project-wide default, so a free domain can be capped at one year without capping anything else.
Value
5
maxMaxTermYears
int64How far ahead a name may be paid. Renewal was unbounded. RenewNameRecord adds one term to the previous due date and asks no further questions, so a holder pressing renew seven times bought seven years of runway, and nothing anywhere said that was not intended. It is a bug in two directions at once: - Free domains have a base price of zero. Renewal is free too, so a script could take every good name under \*gnoland and hold it for a century for the cost of gas. There is no economic brake on a free name at all; the term limit IS the brake. - A paid name held fifty years ahead is fifty years of a promise we have made against a rate written by hand and a testnet that will be replaced. Selling that is easy and honouring it is not. The limit is RUNWAY, not total lifetime: at most N years may be outstanding at any moment. Nobody is stopped from holding a name forever — they renew as the runway burns down, which is the same arrangement every domain registrar has always used and the one people already understand. Per domain first, then a project-wide default, so a free domain can be capped at one year without capping anything else.
Value
100
MaxTermYears
func(domainLabel string) int64MaxTermYears is the cap that applies to names under this domain.
- OID
- 0b19ce…a8da:199
MaxTermYears details
SetMaxTerm
func(domainLabel string, years int64)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.
- OID
- 0b19ce…a8da:201
SetMaxTerm details
RenewalsLeft
func(label string, domainLabel string) int64RenewalsLeft 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.
- OID
- 0b19ce…a8da:202
RenewalsLeft details
renewalsLeft
func(expires int64, now int64, term int64, maxYears int64) int64renewalsLeft is the arithmetic on its own, so it can be tested without a chain under it. Runway is measured from NOW, not from the registration date, so a name deep in its grace period counts as zero rather than as a negative that would then read as extra room. Years already bought round UP: a name with thirteen months left is holding two of its five, because the alternative lets somebody sit permanently at four-years-and-eleven- months and renew every month forever.
- OID
- 0b19ce…a8da:203
renewalsLeft details
assertRoomToRenew
func(label string, domainLabel string)assertRoomToRenew is the enforcement. It runs before payment is taken, so a rejected renewal costs gas and nothing else — taking the money first and reverting would work too, but only because a panic reverts, and depending on that for a refund is a habit that eventually meets a path where it is not true.
- OID
- 0b19ce…a8da:204
assertRoomToRenew details
actionID
func(fn string, args []string) stringactionID is the canonical name of one call: the function and its arguments, separated by single spaces, an empty argument written "". It is what the admin proposes and what the call spends, so a proposal authorizes exactly one change with exactly those arguments.
- OID
- 0b19ce…a8da:205
actionID details
assertActionReady
func(id string)assertActionReady explains, before anything is written, why a timelocked call cannot run yet. The vault's ConsumeAction is the authority; this only makes the refusal say what to do.
- OID
- 0b19ce…a8da:207
assertActionReady details
requireAction
func(id string)requireAction lets a timelocked call through only on a matured proposal for exactly this call, and spends it.
- OID
- 0b19ce…a8da:208
requireAction details
cheapMaxPct
untyped bigintValue
(25 <untyped> bigint)
cheapCooldown
untyped bigintValue
(3600 <untyped> bigint)
dearMaxFactor
untyped bigintValue
(4 <untyped> bigint)
dearCooldown
untyped bigintValue
(600 <untyped> bigint)
maxRate
int64Value
1000000000000
lastTweak
v0.TreelastTweak is the ONE piece of state this realm keeps: when each bounded setting last moved, one clock per direction. Not a record — losing it costs nothing but one early tweak — so it lives here rather than in the vault, and a logic swap simply starts it empty.
- OID
- 0b19ce…a8da:210
lastTweak details
clockKey
func(setting string, dearer bool) string- OID
- 0b19ce…a8da:211
clockKey details
authorizeTweak
func(setting string, old int64, next int64, dearer bool, id string)authorizeTweak lets a bounded setting move from \`old\` to \`next\` instantly when the move stays inside its direction's bound and that direction's clock has rested; otherwise it needs a matured proposal of \`id\`, which it spends. \`dearer\` says whether the move makes names dearer — for the rate and the prices that is a rise, for the credit cap a fall — and the caller, which knows what its setting means, decides it. A move to or from zero is never an instant step: a free domain, or a price restored from free, is a decision.
- OID
- 0b19ce…a8da:212
authorizeTweak details
authorizeTweakOn
func(settings []string, old int64, next int64, dearer bool, id string)authorizeTweakOn is authorizeTweak for a move that rides more than one clock: every clock named must have rested, and all of them are stamped. An unpriced domain's base price IS the default, so its first price of its own is measured against the default — after the default has already been cut, on a clock of its own. Rate, default and domain then each took 25% in one block: 0.75^3 of the price, 58% off, not the two levers (about 44%) this file promises (re-audit 2026-10-16, L-1). So that first own price rides the default's clocks too (domain.gno): in any hour, either the default or an unpriced domain's first own price moves, never both.
- OID
- 0b19ce…a8da:213
authorizeTweakOn details
withinStep
func(old int64, next int64, dearer bool) boolwithinStep is the bound on its own. New values are capped (maxPriceUSD, maxRate); an old value stored before the caps, or by a restore, is not, so anything too large to multiply safely is simply not an instant step.
- OID
- 0b19ce…a8da:214
withinStep details
inheritClocks
func(to string, from string)inheritClocks gives \`to\` every last-move time of \`from\` that is later than its own, in both directions: a setting that starts out as another one's value starts on that one's clocks.
- OID
- 0b19ce…a8da:215
inheritClocks details
clockRested
func(setting string, dearer bool, now int64) bool- OID
- 0b19ce…a8da:216
clockRested details
stepRefusal
func(setting string, old int64, next int64, dearer bool) string- OID
- 0b19ce…a8da:217
stepRefusal details
restRefusal
func(setting string, dearer bool) string- OID
- 0b19ce…a8da:218
restRefusal details
LastTweak
func(setting string) (dearer int64, cheaper int64)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.
- OID
- 0b19ce…a8da:219
LastTweak details
managedGlobal
func(key string) boolmanagedGlobal reports whether SetGlobal must refuse a key, because a setter in this realm owns it — bounded, timelocked or validated. The escape hatch wrote any key, so it walked straight round every one of those rules: SetGlobal("rateUgnotPerUsd", "1") was a one-call way to sell every name for dust.
- OID
- 0b19ce…a8da:220
managedGlobal details
managedDomainKey
func(key string) boolmanagedDomainKey is the same rule for SetDomainField.
- OID
- 0b19ce…a8da:221
managedDomainKey details
ugnot
untyped stringValue
"ugnot"
bpsDenom
untyped bigintValue
(10000 <untyped> bigint)
labelRule
untyped stringlabelRule is the pattern this realm implements, kept as a string so nsdata, the error messages and the site all quote the same thing.
Value
"^[a-z0-9]+(-[a-z0-9]+)*$"
matchesLabelRule
func(s string) boolmatchesLabelRule is that pattern, by hand: lowercase letters and digits, single hyphens between them, none at either end.
- OID
- 0b19ce…a8da:222
matchesLabelRule details
looksLikeAddress
func(s string) boollooksLikeAddress was \`^g1\[a-z0-9]{20,38}$\`. Anything shaped like a real gno.land address is refused as a label, so a name can never be mistaken for a raw wallet address in a UI.
- OID
- 0b19ce…a8da:224
looksLikeAddress details
validateLabel
func(label string)validateLabel — same policy as the original single-realm build (see NOTES.md for the ENS/Starname/r/sys/users research behind it). The length bounds are still read from nsdata, so they survive a swap of this realm; the character rule is now this realm's own. Call sites, and why: RegisterName/RegisterDomain only (both the first-ever mint AND reclaiming a past-grace slot — a reclaim is a genuine new mint with a new owner). RenewName/RenewDomain deliberately do NOT call this — an already-registered name must keep renewing under whatever rule was in force when it was minted.
- OID
- 0b19ce…a8da:225