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

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

v1 state

Back to all declarations

collectionName

untyped string

Value

"Gnotchi gnomes"

collectionSymbol

untyped string

Value

"GNOME"

maxNameLen

untyped bigint

Value

(24 <untyped> bigint)

validHats

map[string]bool

validHats/validCoats match app/js/sprite.js's HAT\_COLOURS/COAT\_COLOURS exactly (brief Part 1: "see app/js/config.js" -- the four-and-four list itself lives in sprite.js, which config.js's own comments point to).

Open
OID
0b2dfa…04e0:4
validHats details

Inspect map

gnome

type

gnome is this realm's own per-token record. grc721's ledger already owns identity, ownership and the soulbound guarantee; this just carries the pet's display fields that grc721 has no concept of.

Value

v1.gnome

nextID

v0.ID

Value

<zero>

soulboundExtension

type

soulboundExtension is rule 6 ("every transfer fails") made structural rather than a convention a future wrapper could forget to enforce: it is grc721's own Extension hook, registered once at init. What actually happens on a refused transfer, read directly from p/nt/grc721/v0/token.gno: PrivateLedger.transfer() (308-344) first writes setBalance(from), setBalance(to) and owners.Set(tid, to) (328-330), and only \*after\* those writes does it run the registered extensions' OnTransfer (333). So this hook's panic does not roll back anything inside the transfer path -- by the time it fires, the reassignment has already been written. The guarantee this realm actually relies on is the transaction boundary, not hook ordering or crossing-call status: PrivateLedger.TransferFrom is in fact a plain method, not a crossing call -- but not for the reason an earlier pass of this comment cited. reference/grc721-v0/tellers.gno:130's "The leading int keeps this a plain method" is about fnTeller.guardHome, a different method entirely (it guards the frame-relative CallerTeller/RealmTeller wrappers, not the ledger). The actual reason is simpler: reference/grc721-v0/token.gno:235 declares PrivateLedger.TransferFrom(spender, from, to address, tid TokenID) error with no realm parameter anywhere in its signature -- and a Gno function is only ever reachable as a crossing call (via cross(...)) when it takes one. No realm parameter, no crossing call, full stop; garden\_test.gno's own TestTransferAlwaysFails already gets this right (see its comment). But the conclusion holds regardless, and unconditionally, not because of crossing: an uncaught panic anywhere inside the current transaction's call stack -- crossing call or plain method, this frame or any callee's -- aborts the \*entire enclosing transaction\*, and gno.land only ever commits a transaction's writes atomically -- if the panic is never recovered, the ledger write a few lines earlier is discarded along with everything else that transaction did, and nothing is ever observed on chain. See this file's package-level SECURITY note: the only way to break that guarantee is a recover() upstream of the panic, which is exactly why this realm has none. TransferFrom's plainness is exactly why that SECURITY warning is a live concern here rather than a moot one. Were TransferFrom (or anything it calls) instead reached across a realm boundary, a panic unwinding back out across that boundary would not be something a recover() placed in this realm's own frame could ever catch -- there would be nothing upstream of the boundary to protect, and the warning would have no real case to guard against. Because it is a plain method, the panic instead propagates up through this realm's own ordinary, unbroken Go call stack the entire way to the transaction boundary -- which is exactly the shape of panic recover() \*can\* catch, from anywhere in that stack, including a recover() several frames away that was added for an unrelated reason. That is precisely the danger the SECURITY note names, and TransferFrom being plain is why it applies here at all. OnMint is intentionally a no-op: Adopt's own Mint call is how a gnome is allowed to come into existence at all, so minting is not something this hook should ever veto.

Value

v1.soulboundExtension

Adopt

func(name string, hat string, coat string)

Adopt mints a soulbound gnome for the caller. One gnome per address: a second Adopt from the same address panics rather than minting a second token, since grc721 has no per-address cap of its own. The caller is cur.Previous().Address() -- the account that crossed into this realm -- matching examples/gno.land/r/demo/foo721's own admin-check idiom for "who actually sent this transaction", not cur.Address() (which would be this realm's own address).

Open
OID
0b2dfa…04e0:18
Adopt details

Inspect func

alreadyAdopted

func(addr .uverse.address) bool

alreadyAdopted consults two sources, not one, and they are not the same thing: ownerIndex is this realm's own record, written once by Adopt and never deleted (there is no burn), so a hit there is permanent and can never regress -- it is the stronger, fail-closed source. token.BalanceOf consults grc721's ledger, which today only agrees with ownerIndex because this realm's own code keeps them in lockstep (no bypass path exists, per the audit's reading of p/nt/grc721/v0 -- Mint/Burn/transfer are the only writers and TransferFrom routes through transfer); nothing in grc721 itself promises that agreement will hold under a future change. Checking only the balance, as before, meant a bug or future extension that changed grc721's balance bookkeeping without a matching change here could let a second Adopt through while ownerIndex still correctly remembered the first gnome. Checking both means either source alone is enough to refuse.

Open
OID
0b2dfa…04e0:19
alreadyAdopted details

Inspect func

validNameLength

func(name string) bool

validNameLength is this realm's length rule (1-maxNameLen characters), checked and reported on its own: Adopt gives a length failure and a charset failure two different messages (see Adopt), because they are two different mistakes for a player to have made and "characters" is the wrong word for "that character isn't on the list". utf8.RuneCountInString, not len/byte length: len(name) counts bytes, so a name full of multi-byte runes could pack several times maxNameLen's intended character budget into the stored string while still reading as "short" by byte count. This is deliberately independent of validNameCharset below -- checking length first and separately means a name that is both over-long and full of hostile runes still gets the length message, not a charset message that would obscure it, and vice versa.

Open
OID
0b2dfa…04e0:20
validNameLength details

Inspect func

validNameCharset

func(name string) bool

validNameCharset reports whether every rune in name passes validNameRune. Split out from length (above) and from hasVisibleNameContent (below) so each has its own panic message in Adopt -- see that function's comment for why conflating them once made TestAdoptRejectsNamesWithMarkdownZeroWidthAndBidiCharacters weaker than it looked.

Open
OID
0b2dfa…04e0:21
validNameCharset details

Inspect func

hasVisibleNameContent

func(name string) bool

hasVisibleNameContent reports whether name has at least one rune that is not a plain space. validNameRune's allowlist admits space so that multi-word names ("Mary Jane") and the hyphen/apostrophe punctuation pack/03-screens' name cards use are not blocked -- but that same allowance means a name of nothing but spaces passes validNameCharset while still rendering as nothing at all. There is no rename, no burn and no admin anywhere in this realm (see the package doc), so a name that gets past Adopt is on chain permanently; a blank one would be permanently blank.

Open
OID
0b2dfa…04e0:22
hasVisibleNameContent details

Inspect func

invisibleLetterRunes

map[int32]bool

invisibleLetterRunes is a denylist layered on top of the allowlist below, not a replacement for it: these are specific code points that unicode.IsLetter reports true for (Unicode general category Lo, "letter, other") despite having no visible glyph at all in any tested renderer. This list is provably the complete set, not just the four this audit happened to find by inspection. An exhaustive scan of every Unicode code point, checking "is this both default-ignorable and (a letter or a digit)", returns exactly these four and no others: U+115F, U+1160, U+3164, U+FFA0 — total: 4 The reason a scan is even conclusive here, rather than just a larger sample, is Unicode's own general-category partition: every code point belongs to exactly one general category, and the categories that carry Unicode's "default ignorable" property -- Cf (format), Mn (nonspacing mark), Zs (space separator), and the rest of the ignorable set -- are, by that same partition, definitionally disjoint from the letter categories (Lu/Ll/Lt/Lm/Lo) and the digit category (Nd) this allowlist admits. A code point cannot be in Cf and Lo at once. So the only way a default-ignorable code point can also be "a letter or a digit" by this realm's own test is to sit in the one sliver where a letter category and the ignorable property happen to overlap -- and the scan above is what that sliver actually contains: four Lo code points, all Hangul filler characters, and nothing else. That is not a sampling result that could have come out differently with more code points checked; it is the whole space. If the toolchain's Unicode/CLDR version ever moves, re-run the scan (unicode.IsLetter/IsDigit plus a default-ignorable table, iterated over every code point up to unicode.MaxRune) rather than assuming this list is still complete -- a future Unicode version could in principle assign the default-ignorable property to a new Lo or Nd code point this list does not yet know about.

Open
OID
0b2dfa…04e0:24
invisibleLetterRunes details

Inspect map

validNameRune

func(r int32) bool

validNameRune is an allowlist, not a denylist, of what a single rune in a gnome name may be. That choice matters because of what happens to a name once Adopt accepts it: there is no admin, no burn and no rename anywhere in this realm, so a name that gets past this check is on chain permanently and unremovable, and it goes straight into Render's page heading as a raw markdown "# " line with no further escaping (garden.gno, Render). The old check only rejected ASCII control characters and DEL (0x7f), which let through: markdown metacharacters (backtick, asterisk, underscore, square/round brackets, '#', '|', '>', '\\', '\<') that can break out of the heading or inject structure into any future page that lists several names together (a leaderboard, a neighbour list); zero-width characters (e.g. U+200B ZERO WIDTH SPACE) that make two visually-identical names byte-distinct; and bidi override/isolate controls (e.g. U+202E RIGHT-TO-LEFT OVERRIDE) that can make a rendered name display as something other than what it actually stores. All of those live in Unicode's "format" (Cf) general category, which this allowlist already excludes outright: nothing in Cf is a letter, a digit, space, hyphen or apostrophe. What this allowlist does guarantee: every accepted rune is an alphanumeric character (by Unicode's own classification) or one of the three punctuation runes named below, and no Cf format/control code point of any kind -- known today or standardised tomorrow -- can ever pass. What it does NOT guarantee, stated here rather than implied away: invisibleLetterRunes above is proven complete against the Unicode version this toolchain ships today (see that var's own comment for the scan and why the general-category partition makes it exhaustive, not just a sample) -- but that completeness is tied to today's Unicode version. A future Unicode update that assigned a new invisible code point to the letter or digit categories would need the scan re-run and the map updated; nothing in this realm detects that automatically. And, separately and by deliberate choice, this allowlist does nothing at all about cross-script homoglyphs -- Cyrillic "А", Greek "Ο", fullwidth "A" are ordinary letters by Unicode's own definition and this check lets every one of them through. That is accepted, not closed: gnome names are not identifiers here (nothing requires one to be unique, nothing uses a name as a lookup key), so two visually-confusable names existing side by side is a cosmetic curiosity, not a security property this realm relies on. A future feature that did start treating names as identifiers would need to revisit this.

Open
OID
0b2dfa…04e0:25
validNameRune details

Inspect func

gnomeForAddr

func(addr .uverse.address) (tid string, g *v1.gnome, found bool)

gnomeForAddr resolves addr's gnome via ownerIndex, or reports found=false.

Open
OID
0b2dfa…04e0:26
gnomeForAddr details

Inspect func

GnomeByAddress

func(addr .uverse.address) (tokenID string, name string, hat string, coat string, level int64, gnomeType string, adoptedAtUnix int64, found bool)

GnomeByAddress is the brief's "read for one address's gnome". found=false (with every other field zero) when addr has not adopted.

Open
OID
0b2dfa…04e0:27
GnomeByAddress details

Inspect func

Render

func(path string) string

Render implements the realm's gnoweb page (brief Part 1: "a public gnoweb page: name, level, type, look"). path == "" is the collection overview; any other path is read as a bech32 owner address.

Open
OID
0b2dfa…04e0:28
Render details

Inspect func
collectionName : untyped string ="Gnotchi gnomes"
collectionSymbol : untyped string ="GNOME"
maxNameLen : untyped bigint =(24 <untyped> bigint)
validHats : map[string]bool Inspect
validCoats : map[string]bool Inspect
gnome : type =v1.gnome
token : *v0.Token Inspect
ledger : *v0.PrivateLedger Inspect
nextID : v0.ID =<zero>
gnomes : *v0.Tree Inspect
ownerIndex : *v0.Tree Inspect
init.11 : func() Inspect
soulboundExtension : type =v1.soulboundExtension
Adopt : func(name string, hat string, coat string) Inspect
alreadyAdopted : func(addr .uverse.address) bool Inspect
validNameLength : func(name string) bool Inspect
validNameCharset : func(name string) bool Inspect
hasVisibleNameContent : func(name string) bool Inspect
invisibleLetterRunes : map[int32]bool Inspect
validNameRune : func(r int32) bool Inspect
gnomeForAddr : func(addr .uverse.address) (tid string, g *v1.gnome, found bool) Inspect
GnomeByAddress : func(addr .uverse.address) (tokenID string, name string, hat string, coat string, level int64, gnomeType string, adoptedAtUnix int64, found bool) Inspect
Render : func(path string) string Inspect