read.gno
5.25 Kb · 161 lines
1package gnofly
2
3import (
4 "strconv"
5 "strings"
6
7 "gno.land/p/nt/grc721/v0"
8 "gno.land/p/onbloc/json/v0"
9
10 "gno.land/r/g1t2kg2vtr3fukg43eujkn6x53gfdyakhngt4sfd/gnofly/nfts/planes"
11)
12
13// The reads are in the shapes Bazaar's site (bazaar.gnomi.fun) parses on a
14// collection package, so that this market can be registered there later
15// without a new version. Lines are fields joined by "|":
16//
17// id|name|owner|seller|price|listed|image|collection|rarity|traits|revealed|slot
18//
19// owner is the planes realm's owner, this realm for a listed plane (escrow);
20// seller and price are empty and 0 when the plane is not listed. collection
21// is the slug Bazaar would know this realm by: the last path element.
22
23// slug is the last element of this realm's path.
24const slug = "gnofly"
25
26// maxOpen is the most listings ListOpen returns, as Bazaar's own realms do.
27const maxOpen = 50
28
29// Name and Symbol are the planes collection's: the market sells nothing else.
30func Name() string { return "gnofly planes" }
31func Symbol() string { return "PLANE" }
32
33// Minted returns how many planes were minted.
34func Minted() int { return int(planes.Minted()) }
35
36// OwnerOf returns the owner of a plane on the planes realm: this realm while
37// it is listed.
38func OwnerOf(tid string) (address, error) {
39 return planes.OwnerOf(grc721.TokenID(tid))
40}
41
42// TokenURI returns the image of a plane, an https URL.
43func TokenURI(tid string) string {
44 uri, err := planes.TokenURI(grc721.TokenID(tid))
45 if err != nil {
46 return ""
47 }
48 return uri
49}
50
51// TokensOf returns the ids of the planes owner holds, comma-separated, in
52// id order: "3,42,700". A listed plane is not among them: it is held here.
53func TokensOf(owner string) string {
54 ids := strings.TrimSuffix(strings.TrimPrefix(planes.PlanesOf(address(owner)), "["), "]")
55 return ids
56}
57
58// ListOpen returns the listings, one line each, in id order, at most 50.
59func ListOpen() string {
60 var lines []string
61 listings.Iterate("", "", func(k string, v any) bool {
62 id, _ := strconv.Atoi(k)
63 lines = append(lines, line(id, v.(*listing)))
64 return len(lines) >= maxOpen
65 })
66 return strings.Join(lines, "\n")
67}
68
69// ItemLine returns the line of plane id, listed or not. It panics for an id
70// that is not a minted plane: Bazaar's site skips the ids it cannot read.
71func ItemLine(id int) string {
72 ownerOf(id) // panics for an id that is not minted
73 return line(id, getListing(id))
74}
75
76// line builds the line of plane id; l is its listing, nil when it has none.
77func line(id int, l *listing) string {
78 p := planeInfo(id)
79 owner, _ := planes.OwnerOf(tid(id))
80 seller, price, listed := "", "0", "false"
81 if l != nil {
82 seller, price, listed = l.seller.String(), strconv.FormatInt(l.price, 10), "true"
83 }
84 traits := "model:" + p.model + ",pilot:" + p.pilot + ",laser:" + p.laser
85 return strings.Join([]string{
86 strconv.Itoa(id), clean(p.name), owner.String(), seller, price, listed,
87 clean(p.image), slug, p.tier, clean(traits), "true", "",
88 }, "|")
89}
90
91type plane struct {
92 name, image, tier, model, pilot, laser string
93}
94
95// planeInfo reads plane id from the planes realm's PlaneJSON.
96func planeInfo(id int) plane {
97 node, err := json.Unmarshal([]byte(planes.PlaneJSON(int64(id))))
98 if err != nil {
99 panic(errUnknownPlane)
100 }
101 str := func(k string) string {
102 v, err := node.GetKey(k)
103 if err != nil {
104 return ""
105 }
106 s, err := v.GetString()
107 if err != nil {
108 return ""
109 }
110 return s
111 }
112 return plane{
113 name: str("name"), image: str("image"), tier: str("tier"),
114 model: str("model"), pilot: str("pilot"), laser: str("laser"),
115 }
116}
117
118// clean keeps a field from breaking a line: no "|" and no line break. The
119// planes realm's names and URLs have none; this is a guard, not a filter.
120func clean(s string) string {
121 return strings.NewReplacer("|", " ", "\n", " ", "\r", " ").Replace(s)
122}
123
124// Render shows the listings on gnoweb.
125func Render(_ string) string {
126 var b strings.Builder
127 b.WriteString("# gnofly planes on Bazaar\n\n")
128 b.WriteString("The [gnofly](https://gnofly.xyz) planes for sale on [Bazaar](https://bazaar.gnomi.fun), the collection `gnofly`. ")
129 b.WriteString("A listed plane is held by this realm until it is bought or the listing is cancelled. ")
130 b.WriteString("Each sale pays Bazaar's fee and the planes' royalty to the gnofly treasury, the rest to the seller.\n\n")
131 if !registered {
132 b.WriteString("Not registered on Bazaar yet.\n\n")
133 }
134 if paused {
135 b.WriteString("New listings are paused: listed planes can still be bought or cancelled.\n\n")
136 }
137 if listings.Size() == 0 {
138 b.WriteString("No plane is for sale.\n")
139 return b.String()
140 }
141 b.WriteString("| Plane | Tier | Price | Seller |\n|---|---|---|---|\n")
142 listings.Iterate("", "", func(k string, v any) bool {
143 id, _ := strconv.Atoi(k)
144 l := v.(*listing)
145 p := planeInfo(id)
146 b.WriteString("| [#" + k + " " + p.name + "](https://gnofly.xyz/nft/collections/planes/" + strconv.Itoa(id) + ") | " +
147 p.tier + " | " + gnot(l.price) + " | " + l.seller.String() + " |\n")
148 return false
149 })
150 return b.String()
151}
152
153// gnot writes ugnot as GNOT: 1000000 is "1 GNOT", 1500000 "1.5 GNOT".
154func gnot(ugnot int64) string {
155 whole, frac := ugnot/1_000_000, ugnot%1_000_000
156 if frac == 0 {
157 return strconv.FormatInt(whole, 10) + " GNOT"
158 }
159 f := strings.TrimRight(strconv.FormatInt(frac+1_000_000, 10)[1:], "0")
160 return strconv.FormatInt(whole, 10) + "." + f + " GNOT"
161}