read.gno
5.24 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 market\n\n")
128 b.WriteString("Planes of [gnofly](https://gnofly.xyz) for sale. A listed plane is held by this realm until it is bought or the listing is cancelled. ")
129 b.WriteString("Each sale pays the planes realm's royalty to the gnofly treasury, the rest to the seller.\n\n")
130 if paused {
131 b.WriteString("New listings are paused: listed planes can still be bought or cancelled.\n\n")
132 }
133 if listings.Size() == 0 {
134 b.WriteString("No plane is for sale.\n")
135 return b.String()
136 }
137 b.WriteString("| Plane | Tier | Price | Seller |\n|---|---|---|---|\n")
138 listings.Iterate("", "", func(k string, v any) bool {
139 id, _ := strconv.Atoi(k)
140 l := v.(*listing)
141 p := planeInfo(id)
142 b.WriteString("| [#" + k + " " + p.name + "](https://gnofly.xyz/nft/collections/planes/" + strconv.Itoa(id) + ") | " +
143 p.tier + " | " + gnot(l.price) + " | " + l.seller.String() + " |\n")
144 return false
145 })
146 return b.String()
147}
148
149// gnot writes ugnot as GNOT: 1000000 is "1 GNOT", 1500000 "1.5 GNOT".
150func gnot(ugnot int64) string {
151 whole, frac := ugnot/1_000_000, ugnot%1_000_000
152 if frac == 0 {
153 return strconv.FormatInt(whole, 10) + " GNOT"
154 }
155 f := strings.TrimRight(strconv.FormatInt(frac+1_000_000, 10)[1:], "0")
156 return strconv.FormatInt(whole, 10) + "." + f + " GNOT"
157}
158
159// Address is this realm's address: the one sellers approve on the planes
160// realm, and the owner of every listed plane.
161func Address() address { return self }