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

json.gno

5.09 Kb · 148 lines
  1package planes
  2
  3import (
  4	"strings"
  5
  6	"gno.land/p/onbloc/json/v0"
  7)
  8
  9// The JSON documents are built with gno.land/p/onbloc/json/v0, which quotes
 10// every string. Keys come out in the order they are written: a gno map
 11// iterates in insertion order, and the library keeps an object's members in
 12// one. Numbers are float64 in the library and printed without exponent or
 13// decimal point when whole, which every number here is, far under 2^53.
 14
 15// PlaneJSON returns one plane as a JSON object, minted or not:
 16//
 17//	{ "id": 42, "name": "Storm Saint", "model": "delta", "pilot": "gnome",
 18//	  "body": "#1e1b4b", "wing": "#1e293b", "accent": "#00f5ff", "tier": "epic",
 19//	  "laser": "#39ff14", "image": "https://gnofly.xyz/nft/collections/planes/042.png", "owner": "g1..." }
 20//
 21// The output has no whitespace. "owner" is "" for a plane not minted, or
 22// burned. An id outside 1 to 777 returns "". Which plane a pilot flies is
 23// not on-chain: the game server checks the owner when a pilot picks one.
 24func PlaneJSON(id int64) string {
 25	p, ok := getPlane(id)
 26	if !ok {
 27		return ""
 28	}
 29	b := json.Builder()
 30	writePlane(b, p)
 31	b.WriteString("image", imageURL(id))
 32	b.WriteString("owner", ownerOf(id).String())
 33	return encode(b.Node())
 34}
 35
 36// PlanesOf returns the ids of the planes addr owns as a JSON array, in
 37// ascending order: [3,42,700]. It is [] for an address without planes.
 38func PlanesOf(addr address) string {
 39	ids := []*json.Node{}
 40	if n, err := Token.BalanceOf(addr); err == nil && n > 0 {
 41		// Ids are dense, 1 to 777: a scan that stops at the last plane of
 42		// addr is cheaper than keeping a list per owner up to date at every
 43		// mint and transfer.
 44		for id := int64(1); id <= supply && int64(len(ids)) < n; id++ {
 45			if ownerOf(id) == addr {
 46				ids = append(ids, json.NumberNode("", float64(id)))
 47			}
 48		}
 49	}
 50	return encode(json.ArrayNode("", ids))
 51}
 52
 53// maxMintedPage is the most ids MintedJSON looks at in one call.
 54const maxMintedPage = int64(200)
 55
 56// MintedJSON returns who owns the minted planes among the ids offset+1 to
 57// offset+limit, as a JSON array in id order:
 58//
 59//	[{"id":3,"owner":"g1..."},{"id":42,"owner":""},...]
 60//
 61// Ids not minted are left out; "owner" is "" for a burned plane. limit is
 62// capped at 200, so four calls (offsets 0, 200, 400, 600) cover the 777,
 63// whatever was minted: planes are drawn at random, minted ids are not a
 64// prefix. It is what the website's grid reads. An empty range is [].
 65func MintedJSON(offset, limit int64) string {
 66	if limit > maxMintedPage {
 67		limit = maxMintedPage
 68	}
 69	entries := []*json.Node{}
 70	if offset >= 0 && limit > 0 {
 71		last := offset + limit
 72		if last > supply {
 73			last = supply
 74		}
 75		for id := offset + 1; id <= last; id++ {
 76			if !wasMinted(id) {
 77				continue
 78			}
 79			entry := json.Builder().
 80				WriteNumber("id", float64(id)).
 81				WriteString("owner", ownerOf(id).String())
 82			entries = append(entries, entry.Node())
 83		}
 84	}
 85	return encode(json.ArrayNode("", entries))
 86}
 87
 88// CollectionJSON returns the state of the collection as one JSON object:
 89//
 90//	{ "name": "gnofly planes", "symbol": "PLANE",
 91//	  "supply": 777, "minted": 12, "burned": 0,
 92//	  "price": 77000000, "open": true, "cap": 7,
 93//	  "royaltyBps": 500, "treasury": "g1...",
 94//	  "imageBase": "https://gnofly.xyz/nft/collections/planes/",
 95//	  "tiers": { "common": { "supply": 500, "minted": 8 },
 96//	             "rare": { "supply": 200, "minted": 3 },
 97//	             "epic": { "supply": 70, "minted": 1 },
 98//	             "legendary": { "supply": 7, "minted": 0 } } }
 99//
100// The output has no whitespace. "minted" counts every mint so far, burned
101// planes included; "minted" minus "burned" is what exists.
102func CollectionJSON() string {
103	b := json.Builder().
104		WriteString("name", tokenName).
105		WriteString("symbol", tokenSymbol).
106		WriteNumber("supply", float64(supply)).
107		WriteNumber("minted", float64(Minted())).
108		WriteNumber("burned", float64(Minted()-Token.TotalSupply())).
109		WriteNumber("price", float64(price)).
110		WriteBool("open", mintOpen).
111		WriteNumber("cap", float64(mintCap)).
112		WriteNumber("royaltyBps", float64(RoyaltyBps())).
113		WriteString("treasury", roles.Treasury().String()).
114		WriteString("imageBase", imageBase).
115		WriteObject("tiers", func(tb *json.NodeBuilder) {
116			for i, tier := range strings.Split(tiers, ",") {
117				tb.WriteObject(tier, func(t *json.NodeBuilder) {
118					t.WriteNumber("supply", float64(tierSupply[i]))
119					t.WriteNumber("minted", float64(tierMinted[i]))
120				})
121			}
122		})
123	return encode(b.Node())
124}
125
126// writePlane writes the nine fields of a plane, in the order of a row, so
127// that a caller can add fields after them.
128func writePlane(b *json.NodeBuilder, p plane) {
129	b.WriteNumber("id", float64(p.id)).
130		WriteString("name", p.name).
131		WriteString("model", p.model).
132		WriteString("pilot", p.pilot).
133		WriteString("body", p.body).
134		WriteString("wing", p.wing).
135		WriteString("accent", p.accent).
136		WriteString("tier", p.tier).
137		WriteString("laser", p.laser)
138}
139
140// encode marshals a document built above. It cannot fail on a document
141// built from the builder, so a failure is a bug and panics.
142func encode(n *json.Node) string {
143	out, err := json.Marshal(n)
144	if err != nil {
145		panic(err)
146	}
147	return string(out)
148}