Search Apps Documentation Source Content File Folder Download Copy Actions Download

render.gno

10.67 Kb ยท 360 lines
  1package gnopendao4
  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 4\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/gnopendao4: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/gnopendao4: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/gnopendao4\" \\\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/gnopendao4: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/gnopendao4\" \\\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/gnopendao4)\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/gnopendao4)\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/gnopendao4)\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/gnopendao4)\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/gnopendao4: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}