Search Apps Documentation Source Content File Folder Download Copy Actions Download

render.gno

14.51 Kb ยท 493 lines
  1package gnopendao5
  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 path == "archive" {
 41		return renderArchive()
 42	}
 43
 44	if strings.HasPrefix(path, "proposal/") {
 45		idStr := strings.TrimPrefix(path, "proposal/")
 46		id, _ := strconv.Atoi(idStr)
 47		return renderProposal(uint64(id))
 48	}
 49
 50	if strings.HasPrefix(path, "archived/") {
 51		idStr := strings.TrimPrefix(path, "archived/")
 52		id, _ := strconv.Atoi(idStr)
 53		return renderArchivedProposal(uint64(id))
 54	}
 55
 56	return "Page not found"
 57}
 58
 59func renderHome() string {
 60	output := "# ๐Ÿช GNOPENSEA DAO 5\n\n"
 61	output += "Decentralized NFT Marketplace with DAO Governance\n\n"
 62	output += "Compatible **GRC-721** + **GRC-2981** (Automatic Royalties)\n\n"
 63	output += "---\n\n"
 64	output += ufmt.Sprintf("**Marketplace Address:** %s\n\n", marketplaceAddr.String())
 65	output += "---\n\n"
 66
 67	output += "## Statistics\n\n"
 68	output += ufmt.Sprintf("- **Active listings:** %d\n", GetActiveListingsCount())
 69	output += ufmt.Sprintf("- **Total sales:** %d\n", GetTotalSales())
 70	output += ufmt.Sprintf("- **Total volume:** %s\n", formatPrice(GetTotalVolume()))
 71	output += ufmt.Sprintf("- **Royalties paid:** %s\n", formatPrice(GetTotalRoyaltiesPaid()))
 72	output += ufmt.Sprintf("- **Marketplace fee:** %s\n", formatFee(marketplaceFee))
 73	output += ufmt.Sprintf("- **Balance:** %s\n\n", formatPrice(GetBalance()))
 74
 75	output += "[View detailed statistics](/r/pierre115/gnopendao5:stats)\n\n"
 76	output += "---\n\n"
 77
 78	// DAO Section
 79	output += "## ๐Ÿ›๏ธ DAO Governance\n\n"
 80	output += ufmt.Sprintf("**Total DAO Members:** %d\n\n", GetTotalMembers())
 81
 82	activeProposals := marketplaceDAO.ActiveProposals()
 83	if activeProposals.Size() > 0 {
 84		output += ufmt.Sprintf("**Active Proposals:** %d\n\n", activeProposals.Size())
 85		output += "[๐Ÿ“‹ View all proposals](/r/pierre115/gnopendao5:proposals)\n\n"
 86	} else {
 87		output += "**Active Proposals:** 0\n\n"
 88		output += "*No active proposals*\n\n"
 89	}
 90
 91	finishedProposals := marketplaceDAO.FinishedProposals()
 92	output += ufmt.Sprintf("[๐Ÿ“š View archive (%d)](/r/pierre115/gnopendao:archive)\n\n", finishedProposals.Size())
 93
 94	// Vote NO button
 95	linkjoin := txlink.NewLink("JoinDAO").
 96		URL()
 97	output += svgbtn.SuccessButton(100, 30, "NO", linkjoin) + "\n\n"
 98
 99	output += "**Join the DAO:**\n"
100	output += "```bash\n"
101	output += "gnokey maketx call \\\n"
102	output += "  -pkgpath \"gno.land/r/pierre115/gnopendao5\" \\\n"
103	output += "  -func \"JoinDAO\" \\\n"
104	output += "  -send \"1000000ugnot\" \\\n"
105	output += "  -broadcast yourkey\n"
106	output += "```\n\n"
107
108	output += "---\n\n"
109
110	// Active listings
111	if GetActiveListingsCount() > 0 {
112		output += "## NFTs for sale\n\n"
113
114		listings.Iterate("", "", func(key string, value interface{}) bool {
115			listing := value.(*Listing)
116			if listing.Active {
117				output += renderListingPreview(listing)
118			}
119			return false
120		})
121	} else {
122		output += "## No NFTs for sale\n\n"
123		output += "*Be the first to list an NFT!*\n"
124	}
125
126	return output
127}
128
129func renderListingPreview(listing *Listing) string {
130	// Calculate breakdown
131	sellerGets, marketFee, royalty, royaltyAddr := GetRoyaltyBreakdown(listing.ListingId)
132
133	output := ufmt.Sprintf("### ๐Ÿท๏ธ Listing #%d\n\n", listing.ListingId)
134	output += ufmt.Sprintf("**Token ID:** %s\n\n", listing.TokenId.String())
135	output += ufmt.Sprintf("**Price:** %s\n\n", formatPrice(listing.Price))
136
137	if royalty > 0 {
138		output += ufmt.Sprintf("Royalty: %s (%s)\n\n",
139			formatPrice(royalty),
140			formatPercentage(royalty, listing.Price))
141	}
142
143	output += ufmt.Sprintf("**Seller receives:** %s\n\n", formatPrice(sellerGets))
144	output += ufmt.Sprintf("**Market Fees:** %s\n\n", formatPrice(marketFee))
145	output += ufmt.Sprintf("**Royalty Address:** %s\n\n", royaltyAddr.String())
146	output += ufmt.Sprintf("**Marketplace Address:** %s\n\n", marketplaceAddr.String())
147	output += ufmt.Sprintf("[View details](/r/pierre115/gnopendao5:listing/%d)\n\n", listing.ListingId)
148	output += "---\n\n"
149	return output
150}
151
152func renderListing(listingId int) string {
153	listing := getListing(listingId)
154	if listing == nil {
155		return "# Listing not found"
156	}
157
158	status := "Active"
159	if !listing.Active {
160		status = "Sold/Cancelled"
161	}
162
163	output := ufmt.Sprintf("# Listing #%d - %s\n\n", listingId, status)
164
165	output += "## Details\n\n"
166	output += ufmt.Sprintf("**Token ID:** %s\n\n", listing.TokenId.String())
167	output += ufmt.Sprintf("**Price:** %s\n\n", formatPrice(listing.Price))
168	output += ufmt.Sprintf("**Seller:** `%s`\n\n", listing.Seller.String())
169	output += ufmt.Sprintf("**Listed at block:** %d\n\n", listing.ListedAt)
170
171	if listing.Active {
172		sellerGets, marketFee, royalty, royaltyAddr := GetRoyaltyBreakdown(listingId)
173
174		output += "---\n\n"
175		output += "## Price breakdown\n\n"
176		output += ufmt.Sprintf("- **Total price:** %s (100%%)\n", formatPrice(listing.Price))
177		output += ufmt.Sprintf("- **Marketplace fee (%s):** %s\n",
178			formatFee(marketplaceFee), formatPrice(marketFee))
179
180		if royalty > 0 {
181			output += ufmt.Sprintf("- **Creator royalty (%s):** %s\n",
182				formatPercentage(royalty, listing.Price), formatPrice(royalty))
183			output += ufmt.Sprintf("  - Beneficiary: `%s`\n", royaltyAddr.String())
184		} else {
185			output += "- **Royalty:** None\n"
186		}
187
188		output += ufmt.Sprintf("- **Seller receives:** %s (%s)\n\n",
189			formatPrice(sellerGets), formatPercentage(sellerGets, listing.Price))
190
191		output += "## Purchase\n\n"
192		output += "```bash\n"
193		output += "gnokey maketx call \\\n"
194		output += "  -pkgpath \"gno.land/r/pierre115/gnopendao5\" \\\n"
195		output += "  -func \"BuyNFT\" \\\n"
196		output += ufmt.Sprintf("  -args \"%d\" \\\n", listingId)
197		output += ufmt.Sprintf("  -send \"%dugnot\" \\\n", listing.Price)
198		output += "  -broadcast yourkey\n"
199		output += "```\n"
200	}
201
202	output += "\n[โ† Back](/r/pierre115/gnopendao5)\n"
203
204	return output
205}
206
207func renderSale(saleId int) string {
208	value, exists := sales.Get(strconv.Itoa(saleId))
209	if !exists {
210		return "# Sale not found"
211	}
212
213	sale := value.(*Sale)
214
215	output := ufmt.Sprintf("# Sale #%d\n\n", saleId)
216	output += ufmt.Sprintf("**Token ID:** %s\n\n", sale.TokenId)
217	output += ufmt.Sprintf("**Price:** %s\n\n", formatPrice(sale.Price))
218	output += ufmt.Sprintf("**Buyer:** `%s`\n\n", sale.Buyer.String())
219	output += ufmt.Sprintf("**Seller:** `%s`\n\n", sale.Seller.String())
220	output += ufmt.Sprintf("**Block:** %d\n\n", sale.SoldAt)
221
222	output += "## ๐Ÿ’ธ Distribution\n\n"
223	output += ufmt.Sprintf("- **Marketplace fee:** %s\n", formatPrice(sale.MarketplaceFee))
224
225	if sale.RoyaltyFee > 0 {
226		output += ufmt.Sprintf("- **Royalty:** %s โ†’ `%s`\n",
227			formatPrice(sale.RoyaltyFee), sale.RoyaltyReceiver.String())
228	}
229
230	sellerReceived := sale.Price - sale.MarketplaceFee - sale.RoyaltyFee
231	output += ufmt.Sprintf("- **Seller received:** %s\n", formatPrice(sellerReceived))
232
233	return output
234}
235
236func renderStats() string {
237	output := "# ๐Ÿ“Š Marketplace Statistics\n\n"
238
239	totalVolume := GetTotalVolume()
240	totalSales := GetTotalSales()
241	totalRoyalties := GetTotalRoyaltiesPaid()
242
243	output += ufmt.Sprintf("**Total volume:** %s\n\n", formatPrice(totalVolume))
244	output += ufmt.Sprintf("**Number of sales:** %d\n\n", totalSales)
245	output += ufmt.Sprintf("**Royalties paid:** %s (%s of volume)\n\n",
246		formatPrice(totalRoyalties), formatPercentage(totalRoyalties, totalVolume))
247
248	if totalSales > 0 {
249		avgPrice := totalVolume / int64(totalSales)
250		output += ufmt.Sprintf("**Average price:** %s\n\n", formatPrice(avgPrice))
251	}
252
253	output += "\n[โ† Back](/r/pierre115/gnopendao5)\n"
254
255	return output
256}
257
258// ============= DAO RENDER FUNCTIONS =============
259
260func renderProposals() string {
261	output := "# ๐Ÿ“‹ Active DAO Proposals\n\n"
262
263	proposals := marketplaceDAO.ActiveProposals()
264
265	if proposals.Size() == 0 {
266		output += "*No active proposals at the moment*\n\n"
267		output += "DAO members can create proposals to:\n"
268		output += "- Approve new NFT collections\n"
269		output += "- Remove existing collections\n"
270		output += "- Update marketplace fees\n\n"
271		output += "[โ† Back](/r/pierre115/gnopendao5)\n"
272		return output
273	}
274
275	proposals.Iterate(0, proposals.Size(), false, func(p *commondao.Proposal) bool {
276		output += renderProposalPreview(p)
277		return false
278	})
279
280	output += "\n[โ† Back](/r/pierre115/gnopendao5)\n"
281
282	return output
283}
284
285func renderProposalPreview(p *commondao.Proposal) string {
286	output := ufmt.Sprintf("## Proposal #%d\n\n", p.ID())
287	output += ufmt.Sprintf("**%s**\n\n", p.Definition().Title())
288
289	// Count votes
290	yesVotes := 0
291	noVotes := 0
292	p.VotingRecord().Iterate(0, p.VotingRecord().Size(), false, func(v commondao.Vote) bool {
293		if string(v.Choice) == "yes" {
294			yesVotes++
295		} else if string(v.Choice) == "no" {
296			noVotes++
297		}
298		return false
299	})
300
301	output += ufmt.Sprintf("**Yes:** %d | **No:** %d | **Total:** %d\n\n", yesVotes, noVotes, yesVotes+noVotes)
302
303	if p.HasVotingDeadlinePassed() {
304		output += "โฐ **Voting ended**\n\n"
305	} else {
306		output += "โœ… **Voting open**\n\n"
307	}
308
309	output += ufmt.Sprintf("[View & Vote](/r/pierre115/gnopendao5:proposal/%d)\n\n", p.ID())
310	output += "---\n\n"
311
312	return output
313}
314
315func renderProposal(proposalID uint64) string {
316	proposal := marketplaceDAO.ActiveProposals().Get(proposalID)
317	if proposal == nil {
318		return "# Proposal not found"
319	}
320
321	output := ufmt.Sprintf("# Proposal #%d\n\n", proposalID)
322	output += ufmt.Sprintf("## %s\n\n", proposal.Definition().Title())
323	output += ufmt.Sprintf("%s\n\n", proposal.Definition().Body())
324
325	output += "---\n\n"
326
327	// Vote counts
328	yesVotes := 0
329	noVotes := 0
330	totalVotes := 0
331
332	proposal.VotingRecord().Iterate(0, proposal.VotingRecord().Size(), false, func(v commondao.Vote) bool {
333		if string(v.Choice) == "yes" {
334			yesVotes++
335		} else if string(v.Choice) == "no" {
336			noVotes++
337		}
338		totalVotes++
339		return false
340	})
341
342	output += "## Current Results\n\n"
343	output += ufmt.Sprintf("- **Yes votes:** %d\n", yesVotes)
344	output += ufmt.Sprintf("- **No votes:** %d\n", noVotes)
345	output += ufmt.Sprintf("- **Total votes:** %d\n", totalVotes)
346
347	totalMembers := GetTotalMembers()
348	quorumRequired := (totalMembers * QUORUM) / 100
349	output += ufmt.Sprintf("- **Quorum required:** %d/%d votes\n\n", totalVotes, quorumRequired)
350
351	if proposal.HasVotingDeadlinePassed() {
352		output += "โฐ **Voting period has ended**\n\n"
353	} else {
354		output += "โœ… **Voting is open**\n\n"
355
356		output += "---\n\n"
357		output += "## Cast Your Vote\n\n"
358
359		// Vote YES button
360		linkyes := txlink.NewLink("Vote").
361			AddArgs("proposalID", ufmt.Sprintf("%d", proposalID)).
362			AddArgs("choice", "yes").
363			URL()
364		output += svgbtn.SuccessButton(100, 30, "YES", linkyes) + "\n\n"
365
366
367		// Vote NO button
368		linkno := txlink.NewLink("Vote").
369			AddArgs("proposalID", ufmt.Sprintf("%d", proposalID)).
370			AddArgs("choice", "no").
371			URL()
372		output += svgbtn.DangerButton(100, 30, "NO", linkno) + "\n\n"
373	}
374
375	output += "---\n\n"
376	output += "[โ† Back to proposals](/r/pierre115/gnopendao:proposals) | [โ† Home](/r/pierre115/gnopendao)\n"
377
378	return output
379}
380
381
382// ============= ARCHIVE RENDER FUNCTIONS =============
383
384func renderArchive() string {
385	output := "# ๐Ÿ“š Proposal Archive\n\n"
386
387	finishedProposals := marketplaceDAO.FinishedProposals()
388
389	if finishedProposals.Size() == 0 {
390		output += "*No finished proposals yet*\n\n"
391		output += "[โ† Back](/r/pierre115/gnopendao)\n"
392		return output
393	}
394
395	output += ufmt.Sprintf("**Total archived proposals:** %d\n\n", finishedProposals.Size())
396	output += "---\n\n"
397
398	finishedProposals.Iterate(0, finishedProposals.Size(), false, func(p *commondao.Proposal) bool {
399		output += renderArchivedProposalPreview(p)
400		return false
401	})
402
403	output += "\n[โ† Back](/r/pierre115/gnopendao)\n"
404
405	return output
406}
407
408func renderArchivedProposalPreview(p *commondao.Proposal) string {
409	output := ufmt.Sprintf("## Proposal #%d - %s\n\n", p.ID(), p.Status())
410	output += ufmt.Sprintf("**%s**\n\n", p.Definition().Title())
411
412	// Count final votes
413	yesVotes := 0
414	noVotes := 0
415	p.VotingRecord().Iterate(0, p.VotingRecord().Size(), false, func(v commondao.Vote) bool {
416		if string(v.Choice) == "yes" || string(v.Choice) == "YES" {
417			yesVotes++
418		} else if string(v.Choice) == "no" || string(v.Choice) == "NO" {
419			noVotes++
420		}
421		return false
422	})
423
424	output += ufmt.Sprintf("**Final Result:** Yes: %d | No: %d\n\n", yesVotes, noVotes)
425
426	statusEmoji := "โœ…"
427	if p.Status() == "failed" {
428		statusEmoji = "โŒ"
429	}
430	output += ufmt.Sprintf("%s **Status:** %s\n\n", statusEmoji, p.Status())
431
432	if p.StatusReason() != "" {
433		output += ufmt.Sprintf("**Reason:** %s\n\n", p.StatusReason())
434	}
435
436	output += ufmt.Sprintf("[View details](/r/pierre115/gnopendao:archived/%d)\n\n", p.ID())
437	output += "---\n\n"
438
439	return output
440}
441
442func renderArchivedProposal(proposalID uint64) string {
443	proposal := marketplaceDAO.FinishedProposals().Get(proposalID)
444	if proposal == nil {
445		return "# Archived proposal not found"
446	}
447
448	output := ufmt.Sprintf("# Proposal #%d - %s\n\n", proposalID, proposal.Status())
449	output += ufmt.Sprintf("## %s\n\n", proposal.Definition().Title())
450	output += ufmt.Sprintf("%s\n\n", proposal.Definition().Body())
451
452	output += "---\n\n"
453
454	// Final vote counts
455	yesVotes := 0
456	noVotes := 0
457	totalVotes := 0
458
459	proposal.VotingRecord().Iterate(0, proposal.VotingRecord().Size(), false, func(v commondao.Vote) bool {
460		if string(v.Choice) == "yes" || string(v.Choice) == "YES" {
461			yesVotes++
462		} else if string(v.Choice) == "no" || string(v.Choice) == "NO" {
463			noVotes++
464		}
465		totalVotes++
466		return false
467	})
468
469	output += "## Final Results\n\n"
470	output += ufmt.Sprintf("- **Yes votes:** %d\n", yesVotes)
471	output += ufmt.Sprintf("- **No votes:** %d\n", noVotes)
472	output += ufmt.Sprintf("- **Total votes:** %d\n", totalVotes)
473
474	totalMembers := GetTotalMembers()
475	quorumRequired := (totalMembers * QUORUM) / 100
476	output += ufmt.Sprintf("- **Quorum required:** %d/%d votes\n\n", totalVotes, quorumRequired)
477
478	statusEmoji := "โœ…"
479	if proposal.Status() == "failed" {
480		statusEmoji = "โŒ"
481	}
482	output += ufmt.Sprintf("%s **Final Status:** %s\n\n", statusEmoji, proposal.Status())
483
484	if proposal.StatusReason() != "" {
485		output += ufmt.Sprintf("**Status Reason:** %s\n\n", proposal.StatusReason())
486	}
487
488	output += ufmt.Sprintf("**Voting Ended:** %s\n\n", proposal.VotingDeadline().Format("2006-01-02 15:04:05"))
489
490	output += "---\n\n"
491	output += "[โ† Back to archive](/r/pierre115/gnopendao:archive) | [โ† Home](/r/pierre115/gnopendao)\n"
492
493	return output
494}