render.gno
10.67 Kb ยท 360 lines
1package gnopendao3
2
3import (
4 "strconv"
5 "strings"
6
7 "gno.land/p/nt/commondao"
8 "gno.land/p/nt/ufmt"
9 "gno.land/p/leon/svgbtn"
10 "gno.land/p/moul/txlink"
11)
12
13// ============= RENDER =============
14
15func Render(path string) string {
16 if path == "" {
17 return renderHome()
18 }
19
20 if strings.HasPrefix(path, "listing/") {
21 idStr := strings.TrimPrefix(path, "listing/")
22 id, _ := strconv.Atoi(idStr)
23 return renderListing(id)
24 }
25
26 if strings.HasPrefix(path, "sale/") {
27 idStr := strings.TrimPrefix(path, "sale/")
28 id, _ := strconv.Atoi(idStr)
29 return renderSale(id)
30 }
31
32 if path == "stats" {
33 return renderStats()
34 }
35
36 if path == "proposals" {
37 return renderProposals()
38 }
39
40 if strings.HasPrefix(path, "proposal/") {
41 idStr := strings.TrimPrefix(path, "proposal/")
42 id, _ := strconv.Atoi(idStr)
43 return renderProposal(uint64(id))
44 }
45
46 return "Page not found"
47}
48
49func renderHome() string {
50 output := "# ๐ช GNOPENSEA DAO\n\n"
51 output += "Decentralized NFT Marketplace with DAO Governance\n\n"
52 output += "Compatible **GRC-721** + **GRC-2981** (Automatic Royalties)\n\n"
53 output += "---\n\n"
54 output += ufmt.Sprintf("**Marketplace Address:** %s\n\n", marketplaceAddr.String())
55 output += "---\n\n"
56
57 output += "## Statistics\n\n"
58 output += ufmt.Sprintf("- **Active listings:** %d\n", GetActiveListingsCount())
59 output += ufmt.Sprintf("- **Total sales:** %d\n", GetTotalSales())
60 output += ufmt.Sprintf("- **Total volume:** %s\n", formatPrice(GetTotalVolume()))
61 output += ufmt.Sprintf("- **Royalties paid:** %s\n", formatPrice(GetTotalRoyaltiesPaid()))
62 output += ufmt.Sprintf("- **Marketplace fee:** %s\n", formatFee(marketplaceFee))
63 output += ufmt.Sprintf("- **Balance:** %s\n\n", formatPrice(GetBalance()))
64
65 output += "[View detailed statistics](/r/pierre115/gnopendao3:stats)\n\n"
66 output += "---\n\n"
67
68 // DAO Section
69 output += "## ๐๏ธ DAO Governance\n\n"
70 output += ufmt.Sprintf("**Total DAO Members:** %d\n\n", GetTotalMembers())
71
72 activeProposals := marketplaceDAO.ActiveProposals()
73 if activeProposals.Size() > 0 {
74 output += ufmt.Sprintf("**Active Proposals:** %d\n\n", activeProposals.Size())
75 output += "[๐ View all proposals](/r/pierre115/gnopendao3:proposals)\n\n"
76 } else {
77 output += "**Active Proposals:** 0\n\n"
78 output += "*No active proposals*\n\n"
79 }
80
81 output += "**Join the DAO:**\n"
82 output += "```bash\n"
83 output += "gnokey maketx call \\\n"
84 output += " -pkgpath \"gno.land/r/pierre115/gnopendao3\" \\\n"
85 output += " -func \"JoinDAO\" \\\n"
86 output += " -send \"1000000ugnot\" \\\n"
87 output += " -broadcast yourkey\n"
88 output += "```\n\n"
89
90 output += "---\n\n"
91
92 // Active listings
93 if GetActiveListingsCount() > 0 {
94 output += "## NFTs for sale\n\n"
95
96 listings.Iterate("", "", func(key string, value interface{}) bool {
97 listing := value.(*Listing)
98 if listing.Active {
99 output += renderListingPreview(listing)
100 }
101 return false
102 })
103 } else {
104 output += "## No NFTs for sale\n\n"
105 output += "*Be the first to list an NFT!*\n"
106 }
107
108 return output
109}
110
111func renderListingPreview(listing *Listing) string {
112 // Calculate breakdown
113 sellerGets, marketFee, royalty, royaltyAddr := GetRoyaltyBreakdown(listing.ListingId)
114
115 output := ufmt.Sprintf("### ๐ท๏ธ Listing #%d\n\n", listing.ListingId)
116 output += ufmt.Sprintf("**Token ID:** %s\n\n", listing.TokenId.String())
117 output += ufmt.Sprintf("**Price:** %s\n\n", formatPrice(listing.Price))
118
119 if royalty > 0 {
120 output += ufmt.Sprintf("Royalty: %s (%s)\n\n",
121 formatPrice(royalty),
122 formatPercentage(royalty, listing.Price))
123 }
124
125 output += ufmt.Sprintf("**Seller receives:** %s\n\n", formatPrice(sellerGets))
126 output += ufmt.Sprintf("**Market Fees:** %s\n\n", formatPrice(marketFee))
127 output += ufmt.Sprintf("**Royalty Address:** %s\n\n", royaltyAddr.String())
128 output += ufmt.Sprintf("**Marketplace Address:** %s\n\n", marketplaceAddr.String())
129 output += ufmt.Sprintf("[View details](/r/pierre115/gnopendao3:listing/%d)\n\n", listing.ListingId)
130 output += "---\n\n"
131 return output
132}
133
134func renderListing(listingId int) string {
135 listing := getListing(listingId)
136 if listing == nil {
137 return "# Listing not found"
138 }
139
140 status := "Active"
141 if !listing.Active {
142 status = "Sold/Cancelled"
143 }
144
145 output := ufmt.Sprintf("# Listing #%d - %s\n\n", listingId, status)
146
147 output += "## Details\n\n"
148 output += ufmt.Sprintf("**Token ID:** %s\n\n", listing.TokenId.String())
149 output += ufmt.Sprintf("**Price:** %s\n\n", formatPrice(listing.Price))
150 output += ufmt.Sprintf("**Seller:** `%s`\n\n", listing.Seller.String())
151 output += ufmt.Sprintf("**Listed at block:** %d\n\n", listing.ListedAt)
152
153 if listing.Active {
154 sellerGets, marketFee, royalty, royaltyAddr := GetRoyaltyBreakdown(listingId)
155
156 output += "---\n\n"
157 output += "## Price breakdown\n\n"
158 output += ufmt.Sprintf("- **Total price:** %s (100%%)\n", formatPrice(listing.Price))
159 output += ufmt.Sprintf("- **Marketplace fee (%s):** %s\n",
160 formatFee(marketplaceFee), formatPrice(marketFee))
161
162 if royalty > 0 {
163 output += ufmt.Sprintf("- **Creator royalty (%s):** %s\n",
164 formatPercentage(royalty, listing.Price), formatPrice(royalty))
165 output += ufmt.Sprintf(" - Beneficiary: `%s`\n", royaltyAddr.String())
166 } else {
167 output += "- **Royalty:** None\n"
168 }
169
170 output += ufmt.Sprintf("- **Seller receives:** %s (%s)\n\n",
171 formatPrice(sellerGets), formatPercentage(sellerGets, listing.Price))
172
173 output += "## Purchase\n\n"
174 output += "```bash\n"
175 output += "gnokey maketx call \\\n"
176 output += " -pkgpath \"gno.land/r/pierre115/gnopendao3\" \\\n"
177 output += " -func \"BuyNFT\" \\\n"
178 output += ufmt.Sprintf(" -args \"%d\" \\\n", listingId)
179 output += ufmt.Sprintf(" -send \"%dugnot\" \\\n", listing.Price)
180 output += " -broadcast yourkey\n"
181 output += "```\n"
182 }
183
184 output += "\n[โ Back](/r/pierre115/gnopendao3)\n"
185
186 return output
187}
188
189func renderSale(saleId int) string {
190 value, exists := sales.Get(strconv.Itoa(saleId))
191 if !exists {
192 return "# Sale not found"
193 }
194
195 sale := value.(*Sale)
196
197 output := ufmt.Sprintf("# Sale #%d\n\n", saleId)
198 output += ufmt.Sprintf("**Token ID:** %s\n\n", sale.TokenId)
199 output += ufmt.Sprintf("**Price:** %s\n\n", formatPrice(sale.Price))
200 output += ufmt.Sprintf("**Buyer:** `%s`\n\n", sale.Buyer.String())
201 output += ufmt.Sprintf("**Seller:** `%s`\n\n", sale.Seller.String())
202 output += ufmt.Sprintf("**Block:** %d\n\n", sale.SoldAt)
203
204 output += "## ๐ธ Distribution\n\n"
205 output += ufmt.Sprintf("- **Marketplace fee:** %s\n", formatPrice(sale.MarketplaceFee))
206
207 if sale.RoyaltyFee > 0 {
208 output += ufmt.Sprintf("- **Royalty:** %s โ `%s`\n",
209 formatPrice(sale.RoyaltyFee), sale.RoyaltyReceiver.String())
210 }
211
212 sellerReceived := sale.Price - sale.MarketplaceFee - sale.RoyaltyFee
213 output += ufmt.Sprintf("- **Seller received:** %s\n", formatPrice(sellerReceived))
214
215 return output
216}
217
218func renderStats() string {
219 output := "# ๐ Marketplace Statistics\n\n"
220
221 totalVolume := GetTotalVolume()
222 totalSales := GetTotalSales()
223 totalRoyalties := GetTotalRoyaltiesPaid()
224
225 output += ufmt.Sprintf("**Total volume:** %s\n\n", formatPrice(totalVolume))
226 output += ufmt.Sprintf("**Number of sales:** %d\n\n", totalSales)
227 output += ufmt.Sprintf("**Royalties paid:** %s (%s of volume)\n\n",
228 formatPrice(totalRoyalties), formatPercentage(totalRoyalties, totalVolume))
229
230 if totalSales > 0 {
231 avgPrice := totalVolume / int64(totalSales)
232 output += ufmt.Sprintf("**Average price:** %s\n\n", formatPrice(avgPrice))
233 }
234
235 output += "\n[โ Back](/r/pierre115/gnopendao3)\n"
236
237 return output
238}
239
240// ============= DAO RENDER FUNCTIONS =============
241
242func renderProposals() string {
243 output := "# ๐ Active DAO Proposals\n\n"
244
245 proposals := marketplaceDAO.ActiveProposals()
246
247 if proposals.Size() == 0 {
248 output += "*No active proposals at the moment*\n\n"
249 output += "DAO members can create proposals to:\n"
250 output += "- Approve new NFT collections\n"
251 output += "- Remove existing collections\n"
252 output += "- Update marketplace fees\n\n"
253 output += "[โ Back](/r/pierre115/gnopendao3)\n"
254 return output
255 }
256
257 proposals.Iterate(0, proposals.Size(), false, func(p *commondao.Proposal) bool {
258 output += renderProposalPreview(p)
259 return false
260 })
261
262 output += "\n[โ Back](/r/pierre115/gnopendao3)\n"
263
264 return output
265}
266
267func renderProposalPreview(p *commondao.Proposal) string {
268 output := ufmt.Sprintf("## Proposal #%d\n\n", p.ID())
269 output += ufmt.Sprintf("**%s**\n\n", p.Definition().Title())
270
271 // Count votes
272 yesVotes := 0
273 noVotes := 0
274 p.VotingRecord().Iterate(0, p.VotingRecord().Size(), false, func(v commondao.Vote) bool {
275 if string(v.Choice) == "yes" {
276 yesVotes++
277 } else if string(v.Choice) == "no" {
278 noVotes++
279 }
280 return false
281 })
282
283 output += ufmt.Sprintf("**Yes:** %d | **No:** %d | **Total:** %d\n\n", yesVotes, noVotes, yesVotes+noVotes)
284
285 if p.HasVotingDeadlinePassed() {
286 output += "โฐ **Voting ended**\n\n"
287 } else {
288 output += "โ
**Voting open**\n\n"
289 }
290
291 output += ufmt.Sprintf("[View & Vote](/r/pierre115/gnopendao3:proposal/%d)\n\n", p.ID())
292 output += "---\n\n"
293
294 return output
295}
296
297func renderProposal(proposalID uint64) string {
298 proposal := marketplaceDAO.ActiveProposals().Get(proposalID)
299 if proposal == nil {
300 return "# Proposal not found"
301 }
302
303 output := ufmt.Sprintf("# Proposal #%d\n\n", proposalID)
304 output += ufmt.Sprintf("## %s\n\n", proposal.Definition().Title())
305 output += ufmt.Sprintf("%s\n\n", proposal.Definition().Body())
306
307 output += "---\n\n"
308
309 // Vote counts
310 yesVotes := 0
311 noVotes := 0
312 totalVotes := 0
313
314 proposal.VotingRecord().Iterate(0, proposal.VotingRecord().Size(), false, func(v commondao.Vote) bool {
315 if string(v.Choice) == "yes" {
316 yesVotes++
317 } else if string(v.Choice) == "no" {
318 noVotes++
319 }
320 totalVotes++
321 return false
322 })
323
324 output += "## Current Results\n\n"
325 output += ufmt.Sprintf("- **Yes votes:** %d\n", yesVotes)
326 output += ufmt.Sprintf("- **No votes:** %d\n", noVotes)
327 output += ufmt.Sprintf("- **Total votes:** %d\n", totalVotes)
328
329 totalMembers := GetTotalMembers()
330 quorumRequired := (totalMembers * QUORUM) / 100
331 output += ufmt.Sprintf("- **Quorum required:** %d/%d votes\n\n", totalVotes, quorumRequired)
332
333 if proposal.HasVotingDeadlinePassed() {
334 output += "โฐ **Voting period has ended**\n\n"
335 } else {
336 output += "โ
**Voting is open**\n\n"
337
338 output += "---\n\n"
339 output += "## Cast Your Vote\n\n"
340
341 // Vote YES button
342 linkyes := txlink.NewLink("Vote").
343 AddArgs("proposalID", ufmt.Sprintf("%d", proposalID)).
344 AddArgs("choice", "yes").
345 URL()
346 output += svgbtn.SuccessButton(100, 30, "YES", linkyes) + "\n\n"
347
348
349 // Vote NO button
350 linkno := txlink.NewLink("Vote").
351 AddArgs("proposalID", ufmt.Sprintf("%d", proposalID)).
352 AddArgs("choice", "no").
353 URL()
354 output += svgbtn.DangerButton(100, 30, "NO", linkno) + "\n\n"
355 }
356
357 output += "---\n\n"
358 output += "[โ Back to proposals](/r/pierre115/gnopendao:proposals) | [โ Home](/r/pierre115/gnopendao)\n"
359
360 return output
361}