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.

types.gno

6.15 Kb · 148 lines
  1// Package nsskin holds purchasable card art — the skins — as tokens of
  2// their own, and binds them to names.
  3//
  4// WHY THIS IS ITS OWN REALM AND NOT PART OF nsdata. SKINS.md §8 listed
  5// it as undecided; it is decided here, because the reasoning turned out
  6// to be one-sided rather than balanced.
  7//
  8//	nsdata IS PERMANENT. It cannot be replaced, ever. A second kind of
  9//	token in it means every mistake in the skin model is permanent too,
 10//	and the skin model is the newest and least settled thing here.
 11//
 12//	nsdata.OwnerOf DELIBERATELY PANICS FOR EXPIRED TOKENS so a lapsed
 13//	name cannot be listed anywhere. Skins do not expire. That machinery
 14//	is exactly wrong for them, and a conditional on token kind inside
 15//	the one realm that can never be fixed is the worst place for it.
 16//
 17//	A NEW REALM CAN BE REPLACED while it holds a handful of tokens. The
 18//	vault cannot, at any point, for any reason.
 19//
 20// So being wrong here costs a migration nobody notices. Being wrong
 21// inside nsdata costs forever.
 22//
 23// # A SKIN IS TWO THINGS
 24//
 25//	A DEFINITION is the artwork: a name, a sprite, an optional extension
 26//	lock, and the artist to pay. One per design, stored once — a
 27//	thousand holders of one design cost one sprite, not a thousand.
 28//
 29//	A TOKEN is somebody's copy. Editions are unlimited, because SKINS.md
 30//	is explicit that a cap is a floor price with extra steps and resale
 31//	value is never part of the pitch.
 32//
 33// # A SKIN BINDS TO ONE NAME, AND TRAVELS WITH IT
 34//
 35// This reverses an earlier recommendation of mine, and the new answer is
 36// better. I had argued for keeping them separate — a pointer from the
 37// name, reverting on sale — because names expire and skins should not.
 38// The objection to bundling was that a buyer sees a skinned card on a
 39// marketplace and receives an unskinned name.
 40//
 41// Binding solves that rather than working around it. The skin genuinely
 42// comes with the name, so the card is not misleading, and the small
 43// disclosure line the old model needed inside the artwork disappears.
 44// And a skin still does not die with a name: when the name goes, the
 45// skin comes back.
 46//
 47// # WHY ATTACHING ESCROWS THE TOKEN, WHICH IS THE WHOLE CUSTODY ANSWER
 48//
 49// The worry: a name listed on a marketplace that takes CUSTODY. If the
 50// skin lived in the seller's wallet, a custodial listing would hold the
 51// name while the skin sat somewhere else, and untangling that on expiry
 52// would need cooperation we cannot compel.
 53//
 54// So attaching moves the token to THIS REALM'S OWN ADDRESS. While
 55// attached it is in nobody's wallet: it cannot be listed, cannot be sold
 56// out from under the name, and a marketplace escrowing the NAME never
 57// touches it. The question of freeing a skin from somebody else's
 58// custody does not arise, because it was never in it.
 59//
 60// Taking it out again is one rule with one fallback:
 61//
 62//	WHILE THE NAME EXISTS, its CURRENT OWNER may claim the skin. That is
 63//	what makes it travel: buy the name, claim the skin.
 64//
 65//	IF THE NAME RECORD IS GONE — lapsed and reclaimed — whoever attached
 66//	it may take it back, which is the "it returns to your wallet" case.
 67//
 68// One limitation, stated rather than hidden: buy a name with a skin,
 69// never claim it, and let that name lapse completely, and the skin
 70// returns to whoever attached it. Claiming on purchase avoids it
 71// entirely, and the site prompts for exactly that.
 72package nsskin
 73
 74import (
 75	"gno.land/p/nt/avl/v0"
 76	"gno.land/p/nt/grc721/v0"
 77)
 78
 79type skin struct {
 80	name   string // as the shop shows it
 81	art    string // the sprite string, in the format nsview parses
 82	lock   string // an extension label, or "" for one that goes anywhere
 83	artist address
 84	// WITHDRAWN IS NOT DELETED, and the distinction is the whole
 85	// takedown mechanism (SKINS.md §6). Deleting a definition orphans
 86	// every token pointing at it. Withdrawing leaves everything in place
 87	// and stops it drawing — every name wearing it reverts at once, with
 88	// no migration and no per-name cleanup, because the card is
 89	// generated on every read.
 90	withdrawn bool
 91	minted    int64 // how many copies exist, and the next serial
 92}
 93
 94// A live attachment. `registered` pins it to one REGISTRATION of the
 95// name, not to the label: if the name lapses and somebody else takes it,
 96// the timestamp changes and this binding is stale, so the new registrant
 97// inherits nothing.
 98type binding struct {
 99	label      string
100	domain     string
101	registered int64
102	binder     address // who attached it, and who gets it back if the name dies
103}
104
105var (
106	// Constructed in init, which is where it can be handed the realm:
107	// the chain's own GRC-721, so every mint and transfer is stamped
108	// gno.land/p/nt/grc721/v0. token is the read side; ledger, the write
109	// side, never leaves this realm.
110	token  *grc721.Token
111	ledger *grc721.PrivateLedger
112	skins  = avl.NewTree() // definition id -> *skin
113
114	// token id -> definition id. Derivable by splitting the token id,
115	// but stored so a lookup is a tree read rather than string surgery
116	// that has to agree with the minter forever.
117	tokenSkin = avl.NewTree()
118
119	bound     = avl.NewTree() // token id      -> *binding
120	nameBound = avl.NewTree() // "domain*label" -> token id
121
122	// Where a skin's own page lives, for the externalUrl in its metadata.
123	// A gnoweb path ("/r/.../nsview:skin/") or a full https URL. The
124	// IMAGE is drawn here (art.gno), from the definition's own sprite,
125	// because wallets put TokenURI straight into an <img>.
126	tokenURIBase = "/r/demo/nsview:skin/"
127
128	// Day-to-day authority: define, mint, withdraw. Deliberately a plain
129	// address rather than the vault's timelocked governance — this realm
130	// is replaceable, so it does not need machinery built for one that
131	// is not.
132	admin address
133
134	// Realms allowed to mint on somebody's behalf, which is what a shop
135	// is. Empty at first: the launch batch is minted by the admin.
136	trustedShop = avl.NewTree()
137
138	// What one costs, in ugnot. Zero until an admin sets it, so a
139	// misconfigured deploy gives skins away rather than charging an
140	// accidental amount — the failure that costs nobody money.
141	price int64
142)
143
144const (
145	maxSkinName = 40
146	maxSkinArt  = 2048 // a 32x32 sprite is about 1,100 bytes
147	maxSkinID   = 24   // appears in every token id and every binding
148)