Search Apps Documentation Source Content File Folder Download Copy Actions Download

render.gno

5.30 Kb · 178 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 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/demo/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}