render.gno
5.42 Kb · 208 lines
1package acr
2
3import (
4 "net/url"
5 "strconv"
6
7 "gno.land/p/jeronimoalbi/pager"
8 "gno.land/p/nt/ufmt"
9 "gno.land/r/akkadia/admin"
10)
11
12const pageSize = 20
13
14// Render handles RESTful routing and returns Markdown responses
15func Render(iUrl string) string {
16 u, err := url.Parse(iUrl)
17 if err != nil {
18 return renderError("Invalid URL")
19 }
20
21 query := u.Query()
22
23 switch u.Path {
24 case "":
25 return renderHome()
26 case "top/balance":
27 return renderTopByBalance(iUrl)
28 case "top/minted":
29 return renderTopByMinted(iUrl)
30 case "account":
31 addr := query.Get("address")
32 if addr == "" {
33 return renderError("Address required")
34 }
35 return renderAccount(addr)
36 default:
37 return renderError("Page not found")
38 }
39}
40
41// renderHome renders the home page with stats, description, and links
42func renderHome() string {
43 output := ufmt.Sprintf("# %s (%s)\n\n", token.GetName(), token.GetSymbol())
44
45 output += `## Introduction
46
47**Akkadia Community Rune (ACR)** is the utility token of the Akkadia ecosystem.
48Users earn ACR through in-game activities and community participation.
49
50`
51 output += "## Token Info\n\n"
52 output += ufmt.Sprintf("* **Decimals**: %d\n", token.GetDecimals())
53 output += ufmt.Sprintf("* **Total Supply**: %s\n", formatAmount(token.TotalSupply()))
54 output += ufmt.Sprintf("* **Known Accounts**: %d\n", token.KnownAccounts())
55
56 output += `
57## Quick Links
58
59* [Top Holders by Balance](/r/akkadia/acr:top/balance)
60* [Top Holders by Minted](/r/akkadia/acr:top/minted)
61
62## Account Lookup
63
64<gno-form path="account">
65 <gno-input name="address" placeholder="Enter wallet address (g1...)" />
66</gno-form>
67`
68 return output
69}
70
71// renderTopByBalance renders paginated list of top holders by balance
72func renderTopByBalance(iUrl string) string {
73 output := "# Top Holders by Balance\n\n"
74 output += "[← Home](/r/akkadia/acr:)\n\n"
75
76 totalAccounts := token.KnownAccounts()
77 if totalAccounts == 0 {
78 output += "*No accounts yet.*\n"
79 return output
80 }
81
82 pages, err := pager.New(iUrl, totalAccounts, pager.WithPageSize(pageSize))
83 if err != nil {
84 return renderError("Invalid page")
85 }
86
87 // Convert pager offset to 1-indexed page for ListTopUsersByBalance
88 page := (pages.Offset() / pages.PageSize()) + 1
89 count := pages.PageSize()
90
91 users := ListTopUsersByBalance(page, count)
92
93 if len(users) == 0 {
94 output += "*No data available.*\n"
95 return output
96 }
97
98 output += "| Rank | Address | Balance |\n"
99 output += "|------|---------|--------|\n"
100
101 startRank := pages.Offset() + 1
102 for i, u := range users {
103 addr := u["user"]
104 balance, _ := strconv.ParseInt(u["balance"], 10, 64)
105 addrLink := ufmt.Sprintf("%s [[View on Explorer](%s/m/explorer/player?address=%s)]", addr, admin.GetExplorerURL(), addr)
106 output += ufmt.Sprintf("| %d | %s | %s |\n", startRank+i, addrLink, formatAmount(balance))
107 }
108
109 if pages.HasPages() {
110 output += "\n" + pager.Picker(pages)
111 }
112
113 return output
114}
115
116// renderTopByMinted renders paginated list of top holders by minted amount
117func renderTopByMinted(iUrl string) string {
118 output := "# Top Holders by Minted Amount\n\n"
119 output += "[← Home](/r/akkadia/acr:)\n\n"
120
121 totalAccounts := token.KnownAccounts()
122 if totalAccounts == 0 {
123 output += "*No accounts yet.*\n"
124 return output
125 }
126
127 pages, err := pager.New(iUrl, totalAccounts, pager.WithPageSize(pageSize))
128 if err != nil {
129 return renderError("Invalid page")
130 }
131
132 // Convert pager offset to 1-indexed page for ListTopUsersByMinting
133 page := (pages.Offset() / pages.PageSize()) + 1
134 count := pages.PageSize()
135
136 users := ListTopUsersByMinting(page, count)
137
138 if len(users) == 0 {
139 output += "*No data available.*\n"
140 return output
141 }
142
143 output += "| Rank | Address | Total Minted |\n"
144 output += "|------|---------|-------------|\n"
145
146 startRank := pages.Offset() + 1
147 for i, u := range users {
148 addr := u["user"]
149 minted, _ := strconv.ParseInt(u["minted"], 10, 64)
150 addrLink := ufmt.Sprintf("%s [[View on Explorer](%s/m/explorer/player?address=%s)]", addr, admin.GetExplorerURL(), addr)
151 output += ufmt.Sprintf("| %d | %s | %s |\n", startRank+i, addrLink, formatAmount(minted))
152 }
153
154 if pages.HasPages() {
155 output += "\n" + pager.Picker(pages)
156 }
157
158 return output
159}
160
161// renderAccount renders a single account's ACR details
162func renderAccount(addrStr string) string {
163 addr := address(addrStr)
164 if !addr.IsValid() {
165 return renderError("Invalid address")
166 }
167
168 balance := BalanceOf(addr)
169 minted := MintedOf(addr)
170
171 output := "# Account Details\n\n"
172 output += "[← Home](/r/akkadia/acr:)\n\n"
173
174 output += ufmt.Sprintf("* **Address**: %s [[View on Explorer](%s/m/explorer/player?address=%s)]\n", addrStr, admin.GetExplorerURL(), addrStr)
175 output += ufmt.Sprintf("* **Balance**: %s %s\n", formatAmount(balance), token.GetSymbol())
176 output += ufmt.Sprintf("* **Total Minted**: %s %s\n", formatAmount(minted), token.GetSymbol())
177
178 return output
179}
180
181// renderError renders an error page
182func renderError(message string) string {
183 output := "# Error\n\n"
184 output += ufmt.Sprintf("> %s\n\n", message)
185 output += "[← Back to Home](/r/akkadia/acr:)\n"
186 return output
187}
188
189// formatAmount formats token amount with proper decimal places
190func formatAmount(amount int64) string {
191 decimals := token.GetDecimals()
192 if decimals == 0 {
193 return ufmt.Sprintf("%d", amount)
194 }
195
196 // Calculate divisor (10^decimals)
197 divisor := int64(1)
198 for i := 0; i < decimals; i++ {
199 divisor *= 10
200 }
201
202 intPart := amount / divisor
203 fracPart := amount % divisor
204
205 // Format with proper decimal places
206 format := ufmt.Sprintf("%%d.%%0%dd", decimals)
207 return ufmt.Sprintf(format, intPart, fracPart)
208}