TokenURI
func(tid v0.TokenID) (string, .uverse.error)TokenURI is the skin's image, as a data URI, for a token that exists.
- OID
- 04a889…e630:3
TokenURI details
TokenMetadata
func(tid v0.TokenID) (string, .uverse.error)TokenMetadata is the skin's metadata as a camelCase JSON string: name, description, externalUrl and attributes of {traitType, value}. No image in it; that is TokenURI.
- OID
- 04a889…e630:5
TokenMetadata details
skinOfTID
func(tid string) (*v2.skin, bool)- OID
- 04a889…e630:6
skinOfTID details
externalURL
func(tid string) stringexternalURL is the skin's own page: tokenURIBase plus the token id, on the chain's gnoweb when the base is a path. "#" would start a URL fragment, so it is escaped.
- OID
- 04a889…e630:7
externalURL details
spriteSVG
func(art string) (string, bool)spriteSVG draws the sprite format DefineSkin validates ("A#rrggbb,..|row|row") as one rect per horizontal run, on a dark matte. It re-checks every rule assertValidArt applies rather than trusting that it ran, and refuses the whole sprite on any fault — a picture with holes in it is worse than an honest placeholder. Colours are emitted only after they are checked to be exactly # and six hex digits, so nothing from the definition reaches the SVG unvalidated.
- OID
- 04a889…e630:8
spriteSVG details
isHex6
func(s string) bool- OID
- 04a889…e630:9
isHex6 details
placeholderImage
func(msg string) string- OID
- 04a889…e630:10
placeholderImage details
dataURI
func(svg string) string- OID
- 04a889…e630:11
dataURI details
escapeXML
func(s string) string- OID
- 04a889…e630:12
escapeXML details
escapeJSON
func(s string) string- OID
- 04a889…e630:13
escapeJSON details
nameKey
func(label string, domain string) string- OID
- 04a889…e630:14
nameKey details
bindingIsLive
func(tid string) boolbindingIsLive reports whether a token's binding still points at the registration it was made against. Same test ArtForName and Claim already apply; named once so the four copies cannot drift.
- OID
- 04a889…e630:16
bindingIsLive details
grcID
func(tid string) v0.TokenIDgrcID saves every call site from importing grc721 just to cast.
- OID
- 04a889…e630:17
grcID details
Attach
func(tid string, label string, domain string)Attach binds a skin to a name and puts the token into this realm's custody. See the long note in types.gno for why custody rather than a lock in place: it is what makes a marketplace's escrow of the NAME irrelevant to the skin. ONE SKIN, ONE NAME, both ways. A token may be attached to one name, and a name may wear one skin. Letting one copy dress every name a person owns would make the second copy pointless, and the object stops meaning anything.
- OID
- 04a889…e630:18
Attach details
wear
func(tid string, label string, domain string, caller .uverse.address)wear is Attach's body, shared with Buy so that buying and wearing settle in one transaction. Splitting them would let a buyer stop halfway and end up holding a skin they paid for and did not put on.
- OID
- 04a889…e630:19
wear details
Refresh
func(tid string)Refresh points a binding's \`binder\` at the name's CURRENT owner. WHY THIS EXISTS, and it is the fix for a real hole. \`binder\` decides who gets the skin back once the name record is gone. Set once at Attach and never updated, it names whoever ATTACHED the skin — so: Alice attaches, sells the name to Bob, the skin travels with it as intended, Bob lets the name lapse, and the skin goes back to ALICE. Bob paid for it. That is not a limitation, it is taking something from the person who bought it. nsdata cannot tell us a name changed hands — it is permanent, it has no hook, and it never will. So \`binder\` cannot be maintained; it can only be CAUGHT UP. This is that, and it is deliberately open to anyone: it only ever writes the name's real current owner, so calling it can never move the skin anywhere it should not go, and there is nothing to gain by calling it on somebody else's binding which means the site can call it on a purchase, a keeper can call it on a schedule, the buyer can call it themselves, or nobody calls it at all and the next Attach or Claim does it — all with the same result and none of them needing to be trusted Nothing on chain is automatic here. gno has no scheduler and nsdata has no callback, so every version of "the skin follows the sale" is one extra call by someone. Making that call safe for ANYONE to make is as close to automatic as this can get, and it is close enough.
- OID
- 04a889…e630:20
Refresh details
refresh
func(b *v2.binding)refresh is Refresh without the realm ceremony, for the callers below.
- OID
- 04a889…e630:21
refresh details
claimEntitled
func(b *v2.binding, caller .uverse.address, exists bool, owner .uverse.address, registered int64, frozen bool) (bool, string)claimEntitled is Claim's rule on its own, with the record handed in (exists false and zeros when the record is gone), so it can be tested without a registry under it. A live, FROZEN registration is refused outright: the admin freezes a stolen or disputed name so nothing moves while it is sorted out, and its skin used to be claimable and sellable all the same (re-audit 2026-10-16, L-5). The record's frozen bit, not the ledger's holds, so a free-tier name's skin still travels.
- OID
- 04a889…e630:22
claimEntitled details
Claim
func(tid string)Claim takes a skin out of custody and into the caller's wallet. WHO MAY, and this is the whole travels-with-the-name rule: the CURRENT OWNER of the name, while the name record still exists. Buy the name, claim the skin — including a name bought from someone else, which is the point. the LAST KNOWN NAME OWNER, once the record is gone entirely. That is the "it comes back to your wallet when the name expires" case — and "last known" rather than "whoever attached it", because after a sale those are different people and only one of them paid for it. See Refresh above for how that is kept current without a hook nsdata does not have. The binding is pinned to one REGISTRATION. If a name lapsed and somebody else registered it, its timestamp has changed, so the new registrant is not the owner of anything here and cannot claim.
- OID
- 04a889…e630:23
Claim details
SkinOnName
func(label string, domain string) stringSkinOnName returns the token currently worn by a name, or "".
- OID
- 04a889…e630:24
SkinOnName details
BindingOf
func(tid string) (label string, domain string, registered int64, binder .uverse.address)- OID
- 04a889…e630:25
BindingOf details
ugnot
untyped stringValue
"ugnot"
Buy
func(id string, label string, domain string) stringBUYING ONE, AND WEARING IT, IN A SINGLE TRANSACTION. Splitting these would mean a buyer signs twice and can stop between — leaving a skin they paid for sitting unworn in a wallet, which is the state nobody wants and the one a two-step flow produces most often. So the name is an argument: pass it and the skin is minted and attached together; pass "" and it is minted loose, which is what somebody buying ahead of registering a name needs. NOTHING IS EVER HELD. Payment, treasury and refund all settle inside the buyer's own transaction, exactly as nslogic does it — and for the same reason, which is worth restating because it is a legal position rather than a style: a realm you can have a BALANCE in is a different kind of thing to operate, subject to different rules, run by people with licences. Every ugnot that arrives leaves again in the same call. THE 70/30 SPLIT IS NOT HERE, and that is deliberate rather than forgotten. Every skin in this collection is ours, so the artist and the treasury are the same address and a split would move money from a pocket to itself while costing gas to do it. The artist is recorded on every definition so the day it stops being us, this is where the split goes — see SKINS.md.
- OID
- 04a889…e630:26
Buy details
SetPrice
func(p int64)SetPrice sets the BASE price, in ugnot — what a design costs when the rule has not made it free and no admin has priced it by hand. ugnot because that is what the chain moves: what it is worth in anything else is a question for the page that quotes it, and pinning a dollar figure into a contract that cannot see an exchange rate would be a lie with a number in it.
- OID
- 04a889…e630:28
SetPrice details
GetPrice
func() int64- OID
- 04a889…e630:29
GetPrice details
assertNoSend
func()Free calls must not be able to strand coins here. This realm has no withdrawal path, so anything attached to one would sit in its address permanently.
- OID
- 04a889…e630:30
assertNoSend details
init.26
func()- OID
- 04a889…e630:31
init.26 details
freeShare
untyped bigintThe threshold, out of a hundred, under which a hashed id is free. THIRTY-FIVE, NOT THIRTY, AND THE FOUR-POINT GAP IS THE HONEST PART. The rule is a statement about ids, not a quota, so any particular collection lands near the threshold rather than on it — sixty ids at a threshold of thirty came out at thirteen, which is twenty-one per cent and not the third that was asked for. Thirty-five puts the launch batch at eighteen, which is thirty per cent exactly. So the constant was chosen to fit the collection that exists. That is worth saying plainly rather than dressing up: what it buys is that every FUTURE id still gets an answer nobody can lobby for, which is the property that mattered, while the batch on sale today is the share that was promised. A constant rather than a stored value, because changing it would silently reprice every skin nobody has overridden — including ones people have already been shown a price for. That should cost a deploy.
Value
(35 <untyped> bigint)
priceOverride
*v0.Tree// definition id -> int64 ugnot
- OID
- 04a889…e630:34
priceOverride details
freeByRule
func(id string) boolfreeByRule is FNV-1a over the id, taken modulo one hundred. freeShare of every hundred ids fall under the threshold, so the share lands NEAR it rather than on it for any particular collection — see the note on the constant. The launch batch of sixty comes out at eighteen, and ListFreeSkins says so rather than asking anyone to trust the arithmetic. The same hash is in nsview.faceForArt, deliberately not shared: one runs over a sprite and one over an id, they answer different questions, and a helper spanning both realms would be a dependency between them for four lines of arithmetic.
- OID
- 04a889…e630:36
freeByRule details
IsFreeByRule
func(id string) boolIsFreeByRule reports what the rule alone says, ignoring any override. Exposed so an admin panel can show what it would revert to.
- OID
- 04a889…e630:38
IsFreeByRule details
HasPriceOverride
func(id string) boolHasPriceOverride reports whether this id has been priced by hand. A panel needs it to tell "set to zero" from "free by the rule", which look identical in the price alone and are different decisions.
- OID
- 04a889…e630:39
HasPriceOverride details
PriceOf
func(id string) int64PriceOf is what this design costs right now, in ugnot. It does NOT check that the id exists: a price for a definition that is not there is meaningless rather than wrong, and Buy is the thing that must refuse an unknown id — which it already does, by name, before it ever asks what the price is.
- OID
- 04a889…e630:40
PriceOf details
SetSkinPrice
func(id string, p int64)SetSkinPrice prices one design by hand, overriding the rule. Zero is a legitimate value and is NOT the same as removing the override: it says "this one is free because I said so", which survives a change to the base price and to the rule. ClearSkinPrice is how you go back to being unopinionated.
- OID
- 04a889…e630:41
SetSkinPrice details
setSkinPrice
func(id string, p int64)setSkinPrice is SetSkinPrice without the realm ceremony. Split for the same reason attach.gno splits refresh from Refresh: the rules are worth testing on their own, and the gate above them is the same assertIsAdmin every other write in this realm goes through.
- OID
- 04a889…e630:42
setSkinPrice details
ClearSkinPrice
func(id string)ClearSkinPrice drops an override, putting the design back under the rule and the base price.
- OID
- 04a889…e630:43
ClearSkinPrice details
clearSkinPrice
func(id string)- OID
- 04a889…e630:44
clearSkinPrice details
SetSkinPrices
func(packed string)SetSkinPrices prices many designs in one call. A panel that lets somebody retune a collection produces a batch of changes, and sixty separate transactions is sixty flat fees for what is one decision. Same encoding as AllSkins reads back — tab between the id and the price, newline between records — so the two halves of the panel speak one format. An empty price field CLEARS the override rather than setting zero. The panel shows an empty box for "no opinion" and a 0 for "free on purpose", and those must not collapse into each other on the way here.
- OID
- 04a889…e630:45
SetSkinPrices details
setSkinPrices
func(packed string)- OID
- 04a889…e630:46
setSkinPrices details
parseU64
func(s string) (int64, bool)parseU64 is strconv.ParseInt without the import, and without accepting a sign — every price here is a non-negative amount of ugnot, and "-0" or "+5" arriving from a form should be refused rather than guessed at.
- OID
- 04a889…e630:47
parseU64 details
ListFreeSkins
func() stringListFreeSkins names every definition that costs nothing right now, counting overrides — so it answers "what can I have for free", which is the question, rather than "what does the rule say", which is trivia.
- OID
- 04a889…e630:48
ListFreeSkins details
AllPrices
func() stringAllPrices is every id and its price, for a shop that wants the lot in one read. AllSkins carries the price too; this exists for a panel that wants prices without the rest and for checking the two agree.
- OID
- 04a889…e630:49
AllPrices details
ArtForName
func(label string, domain string) stringArtForName is the one question the renderer asks, and it is answered here so the rule lives with the data it depends on. It returns the sprite a name should be drawn with, or "" for the ordinary card. Everything the renderer would otherwise have to remember to check is inside it, because a renderer that has to remember three things before drawing is one that eventually forgets one. The checks, and what each of them makes self-cleaning: the name wears a token at all that token's definition has not been WITHDRAWN — so a takedown reverts every name wearing it, everywhere, at once, with no migration and no per-name cleanup the binding is FRESH, pinned to this registration — so a name that lapsed and was taken by somebody else inherits nothing the LOCK still matches — belt and braces, since Attach checks it too Note what is NOT checked: who holds the token in their wallet. Nobody does. It is in this realm's custody for as long as it is attached, which is what makes the skin travel with the name instead of with a wallet.
- OID
- 04a889…e630:50
ArtForName details
SkinInfo
func(id string) (name string, art string, lock string, artist .uverse.address, withdrawn bool, minted int64)- OID
- 04a889…e630:52
SkinInfo details
SkinExists
func(id string) bool- OID
- 04a889…e630:53
SkinExists details
ListSkins
func() []stringListSkins returns every definition id, in tree order. Small by construction — this is a curated shop, not a submission firehose — and if that ever stops being true it wants paging rather than a bigger answer.
- OID
- 04a889…e630:54
ListSkins details
AllSkins
func() stringAllSkins packs the whole catalogue into ONE read. A shop page needs every definition's name, lock and availability, and asking for them one at a time is sixty round trips before anything appears on screen. The ART is deliberately NOT here: it is the bulk of the data, it is committed in the repo as the canonical source, and a page that already has it does not need to fetch it again. Six fields: id, name, lock, withdrawn, minted, price in ugnot. Tab-separated fields, newline-separated records — not JSON, because building JSON by string concatenation in a realm is how quoting bugs get shipped, and a shop is not worth that risk. No field can contain a tab or a newline: ids and locks are \[a-z0-9-] by rule, and a name is checked here rather than assumed.
- OID
- 04a889…e630:55
AllSkins details
skinOfToken
func(tid string) (string, bool)- OID
- 04a889…e630:56
skinOfToken details
SkinOfToken
func(tid string) stringSkinOfToken is skinOfToken for callers outside the realm.
- OID
- 04a889…e630:57
SkinOfToken details
GetAdmin
func() .uverse.address- OID
- 04a889…e630:58
GetAdmin details
GetTokenURIBase
func() string- OID
- 04a889…e630:59
GetTokenURIBase details
IsTrustedShop
func(a .uverse.address) bool- OID
- 04a889…e630:60
IsTrustedShop details
Name
func() string- OID
- 04a889…e630:61
Name details
Symbol
func() string- OID
- 04a889…e630:62
Symbol details
GetName
func() string- OID
- 04a889…e630:63
GetName details
GetSymbol
func() string- OID
- 04a889…e630:64
GetSymbol details
TokenCount
func() int64- OID
- 04a889…e630:65
TokenCount details
TotalSupply
func() int64- OID
- 04a889…e630:66
TotalSupply details
ID
func() stringID is the collection id wallets and indexers key on: "\<this realm's path>.MNSKIN.0000000".
- OID
- 04a889…e630:67
ID details
BalanceOf
func(owner .uverse.address) int64- OID
- 04a889…e630:68
BalanceOf details
OwnerOf
func(tid v0.TokenID) .uverse.address- OID
- 04a889…e630:69
OwnerOf details
GetApproved
func(tid v0.TokenID) .uverse.address- OID
- 04a889…e630:70
GetApproved details
IsApprovedForAll
func(owner .uverse.address, operator .uverse.address) bool- OID
- 04a889…e630:71
IsApprovedForAll details
skin
typeValue
v2.skin
binding
typeA live attachment. \`registered\` pins it to one REGISTRATION of the name, not to the label: if the name lapses and somebody else takes it, the timestamp changes and this binding is stale, so the new registrant inherits nothing.
Value
v2.binding
token
*v0.Token- OID
- 04a889…e630:109
token details
ledger
*v0.PrivateLedger- OID
- 04a889…e630:111
ledger details
skins
*v0.Tree// definition id -> \*skin
- OID
- 04a889…e630:75
skins details
tokenSkin
*v0.Tree- OID
- 04a889…e630:78
tokenSkin details
bound
*v0.Tree// token id -> \*binding
- OID
- 04a889…e630:81
bound details
nameBound
*v0.Tree// "domain\*label" -> token id
- OID
- 04a889…e630:84
nameBound details
tokenURIBase
stringValue
"/r/nym-mnsrehearsal001/nsview/v2:skin/"
admin
.uverse.addressValue
<gnolang.StringValue>
trustedShop
*v0.Tree- OID
- 04a889…e630:89
trustedShop details
price
int64Value
0
maxSkinName
untyped bigintValue
(40 <untyped> bigint)
maxSkinArt
untyped bigint// a 32x32 sprite is about 1,100 bytes
Value
(2048 <untyped> bigint)
maxSkinID
untyped bigint// appears in every token id and every binding
Value
(24 <untyped> bigint)
assertIsAdmin
func()- OID
- 04a889…e630:92
assertIsAdmin details
assertIsAdminOrShop
func()- OID
- 04a889…e630:94
assertIsAdminOrShop details
DefineSkin
func(id string, name string, art string, lock string, artist .uverse.address)DefineSkin registers a design. It does not mint anything. Definitions and copies are separate calls on purpose: defining is the expensive, careful, one-off act that puts an artist's work and a sprite on chain, while minting is the cheap repeated one. Rolling them together would mean re-sending the artwork with every sale. A definition may be REDEFINED only while nothing has been minted from it. After the first copy exists, the art is what its holders bought and it stops being ours to edit — the only lever left is withdrawal, which is honest about what it is doing.
- OID
- 04a889…e630:95
DefineSkin details
Mint
func(id string, to .uverse.address) stringMint issues one copy of a definition to somebody. The serial is the definition's own running count, so token ids read as "ember#1", "ember#2". Never reused: the counter only goes up, even across burns, because a reissued id would let a new token inherit whatever an old one was pointed at.
- OID
- 04a889…e630:96
Mint details
Withdraw
func(id string, withdrawn bool)Withdraw is the takedown control, and SKINS.md §6 makes it non-negotiable: it has to exist before a single outside submission is accepted, which means it has to exist now, while there is nothing to take down and it is cheap to build. Somebody will submit a character somebody else owns, or a bank's logo, or something vile, and it will render inside other people's wallets with our name beside it. When that happens the fix has to be one call, not a migration. It does NOT burn anything. The holder keeps the token; it simply stops rendering, and every name pointing at it falls back to the ordinary card at once. Burning somebody's property to solve our problem is a different act with different consequences, and it is not this one.
- OID
- 04a889…e630:97
Withdraw details
SetTokenURIBase
func(base string)- OID
- 04a889…e630:98
SetTokenURIBase details
SetAdmin
func(a .uverse.address)- OID
- 04a889…e630:99
SetAdmin details
TrustShop
func(a .uverse.address, trusted bool)- OID
- 04a889…e630:100
TrustShop details
TransferFrom
func(from .uverse.address, to .uverse.address, tid v0.TokenID)- OID
- 04a889…e630:101
TransferFrom details
Approve
func(to .uverse.address, tid v0.TokenID)- OID
- 04a889…e630:102
Approve details
SetApprovalForAll
func(operator .uverse.address, approved bool)- OID
- 04a889…e630:103
SetApprovalForAll details
mustSkin
func(id string) *v2.skin- OID
- 04a889…e630:104
mustSkin details
assertValidID
func(id string)An id goes into every token id and into the pointer a name stores, so it has to survive being split on "#" and compared byte for byte.
- OID
- 04a889…e630:105
assertValidID details
assertValidLabel
func(l string)- OID
- 04a889…e630:106
assertValidLabel details
assertValidArt
func(enc string)assertValidArt mirrors the sprite checks nsview makes, and it is here for one reason: nsview REJECTS THE WHOLE SPRITE SILENTLY. A malformed skin does not draw a broken picture, it draws nothing at all, with no error anywhere a person would see it. Storing one would mean selling somebody a blank card and finding out weeks later. So the shape is checked at the point of storage, where the failure is a panic in a transaction nobody has paid for yet. Deliberately checks the SHAPE and not the rendering. This realm does not know how a card is composed and should not learn — that is the whole reason the renderer is swappable.
- OID
- 04a889…e630:107