render.gno
4.42 Kb · 152 lines
1package 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 strings.HasPrefix(path, "collection/"):
22 return renderCollection(strings.TrimPrefix(path, "collection/"))
23 default:
24 return "## 404\nUnknown path: `" + path + "`\n"
25 }
26}
27
28func renderHome() string {
29 var b strings.Builder
30 b.WriteString("# 🖼️ NFT Marketplace\n\n")
31 b.WriteString(ufmt.Sprintf("- **Collections**: %d\n", len(collOrder)))
32 b.WriteString(ufmt.Sprintf("- **Active listings**: %d\n", listings.Size()))
33 b.WriteString(ufmt.Sprintf("- **Platform fee**: %s%%\n\n", bpsToPct(feeBps)))
34
35 b.WriteString("## Collections\n\n")
36 if len(collOrder) == 0 {
37 b.WriteString("_No collections yet. Be the first: call `CreateCollection`._\n\n")
38 }
39 for _, id := range collOrder {
40 v, ok := collections.Get(id)
41 if !ok {
42 continue
43 }
44 c := v.(*Collection)
45 b.WriteString(ufmt.Sprintf("- [%s (%s)](/r/demo/nftmarket:collection/%s) — minted %s",
46 c.name, c.symbol, c.id, strconv.FormatInt(c.minted, 10)))
47 if c.maxSupply > 0 {
48 b.WriteString(ufmt.Sprintf("/%s", strconv.FormatInt(c.maxSupply, 10)))
49 }
50 b.WriteString(ufmt.Sprintf(", mint %s GNOT\n", ugnotToGnot(c.mintPrice)))
51 }
52
53 b.WriteString("\n## Recent listings\n\n")
54 b.WriteString(renderListingsTable(10))
55 return b.String()
56}
57
58func renderCollection(id string) string {
59 v, ok := collections.Get(id)
60 if !ok {
61 return "## 404\nNo such collection: `" + id + "`\n"
62 }
63 c := v.(*Collection)
64
65 var b strings.Builder
66 b.WriteString(ufmt.Sprintf("# %s (%s)\n\n", c.name, c.symbol))
67 b.WriteString(ufmt.Sprintf("- **Creator**: %s\n", c.creator.String()))
68 b.WriteString(ufmt.Sprintf("- **Mint price**: %s GNOT\n", ugnotToGnot(c.mintPrice)))
69 if c.maxSupply > 0 {
70 b.WriteString(ufmt.Sprintf("- **Supply**: %s / %s\n",
71 strconv.FormatInt(c.minted, 10), strconv.FormatInt(c.maxSupply, 10)))
72 } else {
73 b.WriteString(ufmt.Sprintf("- **Supply**: %s (unlimited)\n", strconv.FormatInt(c.minted, 10)))
74 }
75 b.WriteString(ufmt.Sprintf("- **Royalty**: %s%%\n", bpsToPct(c.royaltyBps)))
76 if c.baseURI != "" {
77 b.WriteString(ufmt.Sprintf("- **Metadata base**: `%s`\n", c.baseURI))
78 }
79
80 b.WriteString("\n## Listings in this collection\n\n")
81 header := false
82 listings.Iterate("", "", func(key string, value any) bool {
83 l := value.(*Listing)
84 if l.collID != id {
85 return false
86 }
87 if !header {
88 b.WriteString("| Token | Price (GNOT) | Seller |\n|---|---|---|\n")
89 header = true
90 }
91 b.WriteString(ufmt.Sprintf("| %s | %s | %s |\n",
92 l.tokenID, ugnotToGnot(l.price), l.seller.String()))
93 return false
94 })
95 if !header {
96 b.WriteString("_No active listings._\n")
97 }
98 return b.String()
99}
100
101func renderAllListings() string {
102 return "# All listings\n\n" + renderListingsTable(0)
103}
104
105// renderListingsTable renders up to limit listings (0 = no limit) as a table.
106func renderListingsTable(limit int) string {
107 if listings.Size() == 0 {
108 return "_No active listings._\n"
109 }
110 var b strings.Builder
111 b.WriteString("| Collection | Token | Price (GNOT) | Seller |\n|---|---|---|---|\n")
112 n := 0
113 listings.Iterate("", "", func(key string, value any) bool {
114 l := value.(*Listing)
115 b.WriteString(ufmt.Sprintf("| %s | %s | %s | %s |\n",
116 l.collID, l.tokenID, ugnotToGnot(l.price), l.seller.String()))
117 n++
118 return limit > 0 && n >= limit
119 })
120 return b.String()
121}
122
123// ugnotToGnot formats a ugnot amount as a GNOT decimal string (6 decimals).
124func ugnotToGnot(u int64) string {
125 whole := u / 1_000_000
126 frac := u % 1_000_000
127 if frac == 0 {
128 return strconv.FormatInt(whole, 10)
129 }
130 // zero-pad the fractional part to 6 digits, then trim trailing zeros.
131 fs := strconv.FormatInt(frac, 10)
132 for len(fs) < 6 {
133 fs = "0" + fs
134 }
135 fs = strings.TrimRight(fs, "0")
136 return strconv.FormatInt(whole, 10) + "." + fs
137}
138
139// bpsToPct formats basis points as a percentage string (e.g. 250 -> "2.5").
140func bpsToPct(bps int64) string {
141 whole := bps / 100
142 frac := bps % 100
143 if frac == 0 {
144 return strconv.FormatInt(whole, 10)
145 }
146 fs := strconv.FormatInt(frac, 10)
147 if len(fs) < 2 {
148 fs = "0" + fs
149 }
150 fs = strings.TrimRight(fs, "0")
151 return strconv.FormatInt(whole, 10) + "." + fs
152}