Search Apps Documentation Source Content File Folder Download Copy Actions Download

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}