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.

read.gno

6.51 Kb · 215 lines
  1package nsskin
  2
  3import (
  4	"strings"
  5
  6	"gno.land/p/nt/grc721/v0"
  7	"gno.land/p/nt/ufmt/v0"
  8	"gno.land/r/nym-mnsrehearsal001/nsdata/v2"
  9)
 10
 11/*
 12ArtForName is the one question the renderer asks, and it is answered here
 13so the rule lives with the data it depends on.
 14
 15It returns the sprite a name should be drawn with, or "" for the ordinary
 16card. Everything the renderer would otherwise have to remember to check
 17is inside it, because a renderer that has to remember three things before
 18drawing is one that eventually forgets one.
 19
 20The checks, and what each of them makes self-cleaning:
 21
 22	the name wears a token at all
 23	that token's definition has not been WITHDRAWN — so a takedown
 24	  reverts every name wearing it, everywhere, at once, with no
 25	  migration and no per-name cleanup
 26	the binding is FRESH, pinned to this registration — so a name that
 27	  lapsed and was taken by somebody else inherits nothing
 28	the LOCK still matches — belt and braces, since Attach checks it too
 29
 30Note what is NOT checked: who holds the token in their wallet. Nobody
 31does. It is in this realm's custody for as long as it is attached, which
 32is what makes the skin travel with the name instead of with a wallet.
 33*/
 34func ArtForName(label, domain string) string {
 35	tid := SkinOnName(label, domain)
 36	if tid == "" {
 37		return ""
 38	}
 39	v := bound.Get(tid)
 40	if v == nil {
 41		return ""
 42	}
 43	b := v.(*binding)
 44
 45	if !nsdata.NameExists(label, domain) {
 46		return ""
 47	}
 48	_, registered, _, _, _, _ := nsdata.GetNameInfo(label, domain)
 49	if registered != b.registered {
 50		return "" // a different registration of the same label
 51	}
 52
 53	id, ok := skinOfToken(tid)
 54	if !ok {
 55		return ""
 56	}
 57	// Normalized on the way in, so a token minted from a non-canonical
 58	// id before Buy/Mint were fixed starts rendering instead of staying
 59	// a blank card its holder already paid for.
 60	sv := skins.Get(strings.ToLower(strings.TrimSpace(id)))
 61	if sv == nil {
 62		return ""
 63	}
 64	s := sv.(*skin)
 65	if s.withdrawn {
 66		return ""
 67	}
 68	if s.lock != "" && s.lock != domain {
 69		return ""
 70	}
 71	return s.art
 72}
 73
 74// SkinInfo describes a DEFINITION, for a shop listing or a skin's own
 75// card. Withdrawn is returned rather than hidden: a holder is entitled
 76// to know, and a listing that silently vanishes is worse than one that
 77// says what happened.
 78func SkinInfo(id string) (name, art, lock string, artist address, withdrawn bool, minted int64) {
 79	s := mustSkin(id)
 80	return s.name, s.art, s.lock, s.artist, s.withdrawn, s.minted
 81}
 82
 83func SkinExists(id string) bool {
 84	return skins.Has(strings.ToLower(strings.TrimSpace(id)))
 85}
 86
 87// ListSkins returns every definition id, in tree order. Small by
 88// construction — this is a curated shop, not a submission firehose — and
 89// if that ever stops being true it wants paging rather than a bigger
 90// answer.
 91func ListSkins() []string {
 92	var out []string
 93	skins.Iterate("", "", func(k string, _ any) bool {
 94		out = append(out, k)
 95		return false
 96	})
 97	return out
 98}
 99
100/*
101AllSkins packs the whole catalogue into ONE read.
102
103A shop page needs every definition's name, lock and availability, and
104asking for them one at a time is sixty round trips before anything
105appears on screen. The ART is deliberately NOT here: it is the bulk of
106the data, it is committed in the repo as the canonical source, and a
107page that already has it does not need to fetch it again.
108
109Six fields: id, name, lock, withdrawn, minted, price in ugnot.
110
111Tab-separated fields, newline-separated records — not JSON, because
112building JSON by string concatenation in a realm is how quoting bugs get
113shipped, and a shop is not worth that risk. No field can contain a tab
114or a newline: ids and locks are [a-z0-9-] by rule, and a name is checked
115here rather than assumed.
116*/
117func AllSkins() string {
118	var b strings.Builder
119	skins.Iterate("", "", func(k string, v any) bool {
120		s := v.(*skin)
121		nm := s.name
122		if strings.ContainsAny(nm, "\t\n") {
123			nm = "?" // never seen: DefineSkin would have to have accepted it
124		}
125		w := "0"
126		if s.withdrawn {
127			w = "1"
128		}
129		// PRICE IS THE SIXTH FIELD, appended rather than inserted, so a
130		// page shipped before per-design pricing keeps reading the five
131		// it knows and simply does not see this one.
132		b.WriteString(k + "\t" + nm + "\t" + s.lock + "\t" + w + "\t" +
133			ufmt.Sprintf("%d", s.minted) + "\t" +
134			ufmt.Sprintf("%d", PriceOf(k)) + "\n")
135		return false
136	})
137	return b.String()
138}
139
140func skinOfToken(tid string) (string, bool) {
141	v := tokenSkin.Get(tid)
142	if v == nil {
143		return "", false
144	}
145	return v.(string), true
146}
147
148// SkinOfToken is skinOfToken for callers outside the realm.
149func SkinOfToken(tid string) string {
150	id, _ := skinOfToken(tid)
151	return id
152}
153
154func GetAdmin() address       { return admin }
155func GetTokenURIBase() string { return tokenURIBase }
156func IsTrustedShop(a address) bool {
157	return trustedShop.Has(a.String())
158}
159
160// ---------------------------------------------------------------- grc721
161
162func Name() string       { return token.GetName() }
163func Symbol() string     { return token.GetSymbol() }
164func GetName() string    { return token.GetName() }
165func GetSymbol() string  { return token.GetSymbol() }
166func TokenCount() int64  { return token.TotalSupply() }
167func TotalSupply() int64 { return token.TotalSupply() }
168
169// ID is the collection id wallets and indexers key on:
170// "<this realm's path>.MNSKIN.0000000".
171func ID() string { return token.ID() }
172
173func BalanceOf(owner address) int64 {
174	bal, err := token.BalanceOf(owner)
175	if err != nil {
176		panic(err)
177	}
178	return bal
179}
180
181// OwnerOf does NOT hide a withdrawn skin's owner, and that is the
182// opposite of what nsdata does for an expired name.
183//
184// There, hiding the owner is an anti-scam measure: a lapsed name must
185// not be listable, because the seller no longer controls it. Here the
186// holder still owns the token completely — withdrawal only stops it
187// being DRAWN. Hiding the owner would break transfers and approvals for
188// somebody who has done nothing wrong, to solve a problem that is not
189// theirs.
190func OwnerOf(tid grc721.TokenID) address {
191	owner, err := token.OwnerOf(tid)
192	if err != nil {
193		panic(err)
194	}
195	return owner
196}
197
198// Total, the same way nsdata.GetApproved is: the zero address means
199// nobody is approved, and only an unknown token id panics. Collapsing
200// "no approval" into "no such token" would leave no caller able to fall
201// back to the operator grant without catching a panic first.
202func GetApproved(tid grc721.TokenID) address {
203	a, err := token.GetApproved(tid)
204	if err != nil {
205		if _, oerr := token.OwnerOf(tid); oerr != nil {
206			panic(err)
207		}
208		return ""
209	}
210	return a
211}
212
213func IsApprovedForAll(owner, operator address) bool {
214	return token.IsApprovedForAll(owner, operator)
215}