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.

reader.gno

5.39 Kb · 201 lines
  1// Package reader is the batch JSON read API for the frontend and the
  2// aggregator (design §2.5.6). It is private (re-deployable in place, no
  3// other realm may import it) and holds no state or money: everything comes
  4// from core, market and r/sys/users.
  5//
  6// Every string goes out through safe.JSONWriter, so stored text can never
  7// forge a record (RA H-1: the old TSV reader split on tabs and newlines).
  8// Art is never batched: one token per Art call, because SVG rendering is
  9// what exhausted the query gas in nftread1 (RA L-9).
 10package reader
 11
 12import (
 13	"gno.land/r/sys/users"
 14
 15	"gno.land/p/g19dzk54hhsdc3gllpksuj6gu4a3e6fl8cr6rn4w/sinopia/engine/v1"
 16	"gno.land/p/g19dzk54hhsdc3gllpksuj6gu4a3e6fl8cr6rn4w/sinopia/safe/v1"
 17	"gno.land/r/g19dzk54hhsdc3gllpksuj6gu4a3e6fl8cr6rn4w/sinopia/core"
 18	market "gno.land/r/g19dzk54hhsdc3gllpksuj6gu4a3e6fl8cr6rn4w/sinopia/market/v1"
 19)
 20
 21// Batch limits. Hypothesis (S-4): sized for the genesis engine under the
 22// public qeval gas cap; measure on Onyx and lower if a full page runs out.
 23const (
 24	MaxCards       = 50
 25	MaxTraitCounts = 500
 26	MaxStudio      = 200
 27)
 28
 29// Cards is one gallery page: [{tid, owner, ownerName, traits, listing}] for
 30// tokens offset+1 … offset+limit. Burned tokens are skipped; listing is
 31// {id, price} or null.
 32func Cards(cid string, offset, limit int64) string {
 33	info := core.Info(cid)
 34	w := &safe.JSONWriter{}
 35	w.BeginArray()
 36	for _, tid := range tokenRange(info.Minted, offset, limit, MaxCards) {
 37		owner, err := core.OwnerOf(cid, tid)
 38		if err != nil {
 39			continue // burned
 40		}
 41		w.BeginObject()
 42		w.StrField("tid", tid)
 43		w.StrField("owner", owner.String())
 44		w.StrField("ownerName", nameOf(owner))
 45		w.Key("traits")
 46		writeTraits(w, core.TokenTraitList(cid, tid))
 47		w.Key("listing")
 48		if id, price, ok := market.ActiveListing(cid, tid); ok {
 49			w.BeginObject()
 50			w.StrField("id", id)
 51			w.IntField("price", price)
 52			w.EndObject()
 53		} else {
 54			w.Raw("null")
 55		}
 56		w.EndObject()
 57	}
 58	w.EndArray()
 59	return w.String()
 60}
 61
 62// TraitCounts counts trait values over tokens offset+1 … offset+limit:
 63// {"n": tokens counted, "counts": {type: {value: count}}}. Rarity % for a
 64// 1000-token collection takes two calls. Before Final every token reports
 65// Status=Unrevealed.
 66func TraitCounts(cid string, offset, limit int64) string {
 67	info := core.Info(cid)
 68	c := newCounter()
 69	for _, tid := range tokenRange(info.Minted, offset, limit, MaxTraitCounts) {
 70		if _, err := core.OwnerOf(cid, tid); err != nil {
 71			continue
 72		}
 73		c.add(core.TokenTraitList(cid, tid))
 74	}
 75	return c.json()
 76}
 77
 78// StudioTraits is TraitCounts for an engine and params over n sample seeds
 79// (engine.SampleSeed(from) …), for the Studio's rarity preview.
 80func StudioTraits(engineID, params string, from, n int64) string {
 81	if n <= 0 || n > MaxStudio {
 82		n = MaxStudio
 83	}
 84	if from < 0 {
 85		from = 0
 86	}
 87	c := newCounter()
 88	for i := from; i < from+n; i++ {
 89		c.add(core.EngineTraits(engineID, params, engine.SampleSeed(int(i))))
 90	}
 91	return c.json()
 92}
 93
 94// Art is one token's data URI (placeholder before Final).
 95func Art(cid, tid string) string {
 96	uri, err := core.TokenURI(cid, tid)
 97	if err != nil {
 98		panic(err)
 99	}
100	return uri
101}
102
103func Collections(offset, limit int64) string { return core.Collections(offset, limit) }
104
105// Profile is {address, name, collections, listings} for one address.
106func Profile(addr address) string {
107	w := &safe.JSONWriter{}
108	w.BeginObject()
109	w.StrField("address", addr.String())
110	w.StrField("name", nameOf(addr))
111	w.Key("collections")
112	w.Raw(core.CollectionsByCreator(addr, 0, MaxCards))
113	w.Key("listings")
114	w.Raw(market.ListingsBySeller(addr, 0, MaxCards))
115	w.EndObject()
116	return w.String()
117}
118
119// --- helpers -----------------------------------------------------------------
120
121// nameOf is the r/sys/users name of addr, or "".
122func nameOf(addr address) string {
123	if u := users.ResolveAddress(addr); u != nil {
124		return u.Name()
125	}
126	return ""
127}
128
129func tokenRange(minted, offset, limit, max int64) []string {
130	if limit <= 0 || limit > max {
131		limit = max
132	}
133	if offset < 0 {
134		offset = 0
135	}
136	var out []string
137	for n := offset + 1; n <= minted && n <= offset+limit; n++ {
138		out = append(out, itoa(n))
139	}
140	return out
141}
142
143func writeTraits(w *safe.JSONWriter, ts []engine.Trait) {
144	w.BeginArray()
145	for _, t := range ts {
146		w.BeginObject()
147		w.StrField("trait_type", t.Type)
148		w.StrField("value", t.Value)
149		w.EndObject()
150	}
151	w.EndArray()
152}
153
154// counter keeps first-seen order so the JSON is deterministic.
155type counter struct {
156	n      int64
157	types  []string
158	values map[string][]string       // type -> values in first-seen order
159	counts map[string]map[string]int64 // type -> value -> count
160}
161
162func newCounter() *counter {
163	return &counter{values: map[string][]string{}, counts: map[string]map[string]int64{}}
164}
165
166func (c *counter) add(ts []engine.Trait) {
167	c.n++
168	for _, t := range ts {
169		byValue, ok := c.counts[t.Type]
170		if !ok {
171			byValue = map[string]int64{}
172			c.counts[t.Type] = byValue
173			c.types = append(c.types, t.Type)
174		}
175		if _, seen := byValue[t.Value]; !seen {
176			c.values[t.Type] = append(c.values[t.Type], t.Value)
177		}
178		byValue[t.Value]++
179	}
180}
181
182func (c *counter) json() string {
183	w := &safe.JSONWriter{}
184	w.BeginObject()
185	w.IntField("n", c.n)
186	w.Key("counts")
187	w.BeginObject()
188	for _, typ := range c.types {
189		w.Key(typ)
190		w.BeginObject()
191		for _, v := range c.values[typ] {
192			w.IntField(v, c.counts[typ][v])
193		}
194		w.EndObject()
195	}
196	w.EndObject()
197	w.EndObject()
198	return w.String()
199}
200
201func itoa(n int64) string { return safe.PadInt(n, 1) }