render.gno
5.35 Kb · 178 lines
1package gnogle_nftmarket
2
3import (
4 "strconv"
5 "strings"
6
7 "gno.land/p/nt/ufmt/v0"
8)
9
10// Render renders the realm as Markdown for the gno.land web UI.
11//
12// "" -> marketplace home (collections + recent listings)
13// "collection/<id>" -> a single collection and its active listings
14// "listings" -> every active listing
15func Render(path string) string {
16 switch {
17 case path == "":
18 return renderHome()
19 case path == "listings":
20 return renderAllListings()
21 case path == "auctions":
22 return "# Live auctions\n\n" + renderAuctionsTable()
23 case strings.HasPrefix(path, "collection/"):
24 return renderCollection(strings.TrimPrefix(path, "collection/"))
25 default:
26 return "## 404\nUnknown path: `" + path + "`\n"
27 }
28}
29
30func renderHome() string {
31 var b strings.Builder
32 b.WriteString("# 🖼️ NFT Marketplace\n\n")
33 b.WriteString(ufmt.Sprintf("- **Collections**: %d\n", len(collOrder)))
34 b.WriteString(ufmt.Sprintf("- **Active listings**: %d\n", listings.Size()))
35 b.WriteString(ufmt.Sprintf("- **Active auctions**: %d\n", auctions.Size()))
36 b.WriteString(ufmt.Sprintf("- **Platform fee**: %s%%\n\n", bpsToPct(feeBps)))
37
38 b.WriteString("## Collections\n\n")
39 if len(collOrder) == 0 {
40 b.WriteString("_No collections yet. Be the first: call `CreateCollection`._\n\n")
41 }
42 for _, id := range collOrder {
43 v, ok := collections.Get(id)
44 if !ok {
45 continue
46 }
47 c := v.(*Collection)
48 b.WriteString(ufmt.Sprintf("- [%s (%s)](/r/g18wk4a80cr7dqa25vfka2yug5n3pd50udled6y3/gnogle_nftmarket:collection/%s) — minted %s",
49 c.name, c.symbol, c.id, strconv.FormatInt(c.minted, 10)))
50 if c.maxSupply > 0 {
51 b.WriteString(ufmt.Sprintf("/%s", strconv.FormatInt(c.maxSupply, 10)))
52 }
53 b.WriteString(ufmt.Sprintf(", mint %s GNOT\n", ugnotToGnot(c.mintPrice)))
54 }
55
56 b.WriteString("\n## Recent listings\n\n")
57 b.WriteString(renderListingsTable(10))
58 b.WriteString("\n## Live auctions\n\n")
59 b.WriteString(renderAuctionsTable())
60 return b.String()
61}
62
63func renderCollection(id string) string {
64 v, ok := collections.Get(id)
65 if !ok {
66 return "## 404\nNo such collection: `" + id + "`\n"
67 }
68 c := v.(*Collection)
69
70 var b strings.Builder
71 b.WriteString(ufmt.Sprintf("# %s (%s)\n\n", c.name, c.symbol))
72 b.WriteString(ufmt.Sprintf("- **Creator**: %s\n", c.creator.String()))
73 b.WriteString(ufmt.Sprintf("- **Mint price**: %s GNOT\n", ugnotToGnot(c.mintPrice)))
74 if c.maxSupply > 0 {
75 b.WriteString(ufmt.Sprintf("- **Supply**: %s / %s\n",
76 strconv.FormatInt(c.minted, 10), strconv.FormatInt(c.maxSupply, 10)))
77 } else {
78 b.WriteString(ufmt.Sprintf("- **Supply**: %s (unlimited)\n", strconv.FormatInt(c.minted, 10)))
79 }
80 b.WriteString(ufmt.Sprintf("- **Royalty**: %s%%\n", bpsToPct(c.royaltyBps)))
81 if c.baseURI != "" {
82 b.WriteString(ufmt.Sprintf("- **Metadata base**: `%s`\n", c.baseURI))
83 }
84
85 b.WriteString("\n## Listings in this collection\n\n")
86 header := false
87 listings.Iterate("", "", func(key string, value any) bool {
88 l := value.(*Listing)
89 if l.collID != id {
90 return false
91 }
92 if !header {
93 b.WriteString("| Token | Price (GNOT) | Seller |\n|---|---|---|\n")
94 header = true
95 }
96 b.WriteString(ufmt.Sprintf("| %s | %s | %s |\n",
97 l.tokenID, ugnotToGnot(l.price), l.seller.String()))
98 return false
99 })
100 if !header {
101 b.WriteString("_No active listings._\n")
102 }
103 return b.String()
104}
105
106func renderAllListings() string {
107 return "# All listings\n\n" + renderListingsTable(0)
108}
109
110// renderListingsTable renders up to limit listings (0 = no limit) as a table.
111func renderListingsTable(limit int) string {
112 if listings.Size() == 0 {
113 return "_No active listings._\n"
114 }
115 var b strings.Builder
116 b.WriteString("| Collection | Token | Price (GNOT) | Seller |\n|---|---|---|---|\n")
117 n := 0
118 listings.Iterate("", "", func(key string, value any) bool {
119 l := value.(*Listing)
120 b.WriteString(ufmt.Sprintf("| %s | %s | %s | %s |\n",
121 l.collID, l.tokenID, ugnotToGnot(l.price), l.seller.String()))
122 n++
123 return limit > 0 && n >= limit
124 })
125 return b.String()
126}
127
128// renderAuctionsTable renders all live auctions as a Markdown table.
129func renderAuctionsTable() string {
130 if auctions.Size() == 0 {
131 return "_No live auctions._\n"
132 }
133 var b strings.Builder
134 b.WriteString("| Collection | Token | Min bid | Highest bid | Bidder |\n|---|---|---|---|---|\n")
135 auctions.Iterate("", "", func(key string, value any) bool {
136 a := value.(*Auction)
137 hb, bidder := "—", "—"
138 if a.highestBid > 0 {
139 hb = ugnotToGnot(a.highestBid)
140 bidder = a.highestBidder.String()
141 }
142 b.WriteString(ufmt.Sprintf("| %s | %s | %s | %s | %s |\n",
143 a.collID, a.tokenID, ugnotToGnot(a.minBid), hb, bidder))
144 return false
145 })
146 return b.String()
147}
148
149// ugnotToGnot formats a ugnot amount as a GNOT decimal string (6 decimals).
150func ugnotToGnot(u int64) string {
151 whole := u / 1_000_000
152 frac := u % 1_000_000
153 if frac == 0 {
154 return strconv.FormatInt(whole, 10)
155 }
156 // zero-pad the fractional part to 6 digits, then trim trailing zeros.
157 fs := strconv.FormatInt(frac, 10)
158 for len(fs) < 6 {
159 fs = "0" + fs
160 }
161 fs = strings.TrimRight(fs, "0")
162 return strconv.FormatInt(whole, 10) + "." + fs
163}
164
165// bpsToPct formats basis points as a percentage string (e.g. 250 -> "2.5").
166func bpsToPct(bps int64) string {
167 whole := bps / 100
168 frac := bps % 100
169 if frac == 0 {
170 return strconv.FormatInt(whole, 10)
171 }
172 fs := strconv.FormatInt(frac, 10)
173 if len(fs) < 2 {
174 fs = "0" + fs
175 }
176 fs = strings.TrimRight(fs, "0")
177 return strconv.FormatInt(whole, 10) + "." + fs
178}