Skip to content

Commit e975608

Browse files
committed
add: remove placeholder in tx and add a leaderboard
1 parent 06ebd7b commit e975608

File tree

5 files changed

+169
-20
lines changed

5 files changed

+169
-20
lines changed

packages/r/greg007/gnobounty/application.gno

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,12 @@ func Vote(_ realm, applicationID uint64, vote string) {
139139
panic(err)
140140
}
141141

142+
// Record vote locally
143+
app.Votes = append(app.Votes, VoteRecord{
144+
Voter: caller,
145+
Choice: voteChoice,
146+
})
147+
142148
// Try to execute the proposal
143149
// Tally() will return true when all validators have voted
144150
// Execute() will handle both approval and rejection
@@ -178,12 +184,10 @@ func GetApplicationsForValidator(validatorAddr address) []*Application {
178184
applications.Iterate("", "", func(key string, value interface{}) bool {
179185
app := value.(*Application)
180186
// Check if validator is a member of the application's DAO
181-
// We need to check if this validator was selected for this application
182-
// by iterating through applications and checking the selected validators
183187
if app.DAO != nil && app.Status == StatusPending {
184-
// We can't easily get the members from the DAO interface
185-
// Instead, we'll add the application and let the validator see all pending ones
186-
apps = append(apps, app)
188+
if app.DAO.Members().Has(validatorAddr) {
189+
apps = append(apps, app)
190+
}
187191
}
188192
return false
189193
})
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package gnobounty
2+
3+
import (
4+
"gno.land/p/nt/ufmt"
5+
)
6+
7+
// LeaderboardEntry holds the stats for a user
8+
type LeaderboardEntry struct {
9+
Address address
10+
BountiesCreated int
11+
BountiesApplied int
12+
ValidationsPerformed int
13+
Score int
14+
}
15+
16+
// GetLeaderboard aggregates stats for all users
17+
func GetLeaderboard() []LeaderboardEntry {
18+
stats := make(map[address]*LeaderboardEntry)
19+
20+
getEntry := func(addr address) *LeaderboardEntry {
21+
if _, ok := stats[addr]; !ok {
22+
stats[addr] = &LeaderboardEntry{Address: addr}
23+
}
24+
return stats[addr]
25+
}
26+
27+
// Count bounties created (10 points)
28+
bounties.Iterate("", "", func(key string, value interface{}) bool {
29+
if value == nil {
30+
return false
31+
}
32+
bounty, ok := value.(*Bounty)
33+
if !ok || bounty == nil {
34+
return false
35+
}
36+
entry := getEntry(bounty.Creator)
37+
entry.BountiesCreated++
38+
entry.Score += 10
39+
return false
40+
})
41+
42+
// Count applications and validations
43+
applications.Iterate("", "", func(key string, value interface{}) bool {
44+
if value == nil {
45+
return false
46+
}
47+
app, ok := value.(*Application)
48+
if !ok || app == nil {
49+
return false
50+
}
51+
52+
// Only count approved applications (20 points)
53+
if app.Status == StatusApproved {
54+
entry := getEntry(app.Applicant)
55+
entry.BountiesApplied++
56+
entry.Score += 20
57+
}
58+
59+
// Count validations from local record (5 points)
60+
for _, vote := range app.Votes {
61+
entry := getEntry(vote.Voter)
62+
entry.ValidationsPerformed++
63+
entry.Score += 5
64+
}
65+
66+
return false
67+
})
68+
69+
// Convert map to slice
70+
var result []LeaderboardEntry
71+
for _, entry := range stats {
72+
result = append(result, *entry)
73+
}
74+
75+
// Sort by Score descending
76+
// Manual bubble sort since sort.Slice is not available
77+
for i := 0; i < len(result)-1; i++ {
78+
for j := 0; j < len(result)-i-1; j++ {
79+
if result[j].Score < result[j+1].Score {
80+
// Swap
81+
result[j], result[j+1] = result[j+1], result[j]
82+
}
83+
}
84+
}
85+
86+
return result
87+
}
88+
89+
// RenderLeaderboard renders the leaderboard page
90+
func RenderLeaderboard() string {
91+
entries := GetLeaderboard()
92+
93+
output := "# 🏆 Community Leaderboard\n\n"
94+
output += "Top contributors to the GnoBounty ecosystem.\n\n"
95+
output += "**Scoring:** Bounty Creation (10pts) | Approved Application (20pts) | Validation (5pts)\n\n"
96+
97+
output += "| Rank | User | Score | 💰 Bounties | 📝 Applications | ⚖️ Validations |\n"
98+
output += "|---|---|---|---|---|---|\n"
99+
100+
for i, entry := range entries {
101+
rank := ""
102+
switch i {
103+
case 0:
104+
rank = "🥇"
105+
case 1:
106+
rank = "🥈"
107+
case 2:
108+
rank = "🥉"
109+
default:
110+
rank = ufmt.Sprintf("%d", i+1)
111+
}
112+
113+
output += ufmt.Sprintf("| %s | %s | **%d** | %d | %d | %d |\n",
114+
rank,
115+
entry.Address,
116+
entry.Score,
117+
entry.BountiesCreated,
118+
entry.BountiesApplied,
119+
entry.ValidationsPerformed,
120+
)
121+
}
122+
123+
if len(entries) == 0 {
124+
output += "\nNo activity recorded yet."
125+
}
126+
127+
return output
128+
}

packages/r/greg007/gnobounty/logic.gno

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,20 @@ import (
1010
)
1111

1212
// CreateBounty creates a new bounty post
13+
// title: short title for the bounty
1314
// issueURL: link to the GitHub issue or other issue tracker
1415
// description: description of what needs to be done
1516
// Caller must send GNOT tokens with this transaction
16-
func CreateBounty(_ realm, issueURL, description string) uint64 {
17+
func CreateBounty(_ realm, title, issueURL, description string) uint64 {
1718
// Validate inputs
19+
if title == "" {
20+
panic("title is required")
21+
}
1822
if issueURL == "" {
19-
panic("issue URL cannot be empty")
23+
panic("issue URL is required")
2024
}
2125
if description == "" {
22-
panic("description cannot be empty")
26+
panic("description is required")
2327
}
2428

2529
// Get the sent coins
@@ -45,6 +49,7 @@ func CreateBounty(_ realm, issueURL, description string) uint64 {
4549
bountyCount++
4650
bounty := &Bounty{
4751
ID: bountyCount,
52+
Title: title,
4853
IssueURL: issueURL,
4954
Description: description,
5055
Amount: gnotAmount,

packages/r/greg007/gnobounty/render.gno

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ func ListBounties() string {
1414
minSend := ufmt.Sprintf("%dugnot", minimumBounty)
1515
createBountyLink := txlink.NewLink("CreateBounty").
1616
AddArgs(
17-
"issueURL", "https://github.com/your-org/your-repo/issues/123",
18-
"description", "Describe the work your bounty covers",
17+
"title", "",
18+
"issueURL", "",
19+
"description", "",
1920
).
2021
SetSend(minSend).
2122
URL()
@@ -31,6 +32,8 @@ func ListBounties() string {
3132
output += ufmt.Sprintf(" _(minimum reward: %s)_", minSend)
3233
output += " | "
3334
output += md.Link("📋 View All Validators", "/r/greg007/gnobounty:validators")
35+
output += " | "
36+
output += md.Link("🏆 Leaderboard", "/r/greg007/gnobounty:leaderboard")
3437
output += "\n\n"
3538

3639
// Add validator stats
@@ -39,6 +42,7 @@ func ListBounties() string {
3942

4043
output += "---\n\n"
4144
output += "## 💎 Active Bounties\n\n"
45+
output += "**How to apply:** To apply to a bounty, you need to give your **MERGED PR** (important) URL in the transaction. You also need to write your Gno address in the description of your PR to verify your identity. If no address is written, the bounty can be refused.\n\n"
4246

4347
if bountyCount == 0 {
4448
output += "No bounties available"
@@ -48,7 +52,7 @@ func ListBounties() string {
4852
bounties.Iterate("", "", func(key string, value interface{}) bool {
4953
bounty := value.(*Bounty)
5054
if !bounty.IsClaimed {
51-
output += ufmt.Sprintf("## Bounty #%d\n", bounty.ID)
55+
output += ufmt.Sprintf("## Bounty #%d: %s\n", bounty.ID, bounty.Title)
5256
output += ufmt.Sprintf("**Issue:** %s\n", bounty.IssueURL)
5357
output += ufmt.Sprintf("**Description:** %s\n", bounty.Description)
5458
output += ufmt.Sprintf("**Reward:** %d ugnot (%.2f GNOT)\n", bounty.Amount, float64(bounty.Amount)/1000000.0)
@@ -59,7 +63,7 @@ func ListBounties() string {
5963
applyLink := txlink.NewLink("ApplyForBounty").
6064
AddArgs(
6165
"bountyID", ufmt.Sprintf("%d", bounty.ID),
62-
"prLink", "https://github.com/your-org/your-repo/pull/123",
66+
"prLink", "",
6367
).
6468
URL()
6569
output += md.Link("📝 Apply for this bounty", applyLink)
@@ -83,7 +87,7 @@ func GetBountyDetails(id uint64) string {
8387
return "Bounty not found"
8488
}
8589

86-
output := ufmt.Sprintf("# Bounty #%d\n\n", bounty.ID)
90+
output := ufmt.Sprintf("# Bounty #%d: %s\n\n", bounty.ID, bounty.Title)
8791
output += ufmt.Sprintf("**Issue URL:** %s\n", bounty.IssueURL)
8892
output += ufmt.Sprintf("**Description:** %s\n", bounty.Description)
8993
output += ufmt.Sprintf("**Reward:** %d ugnot (%.2f GNOT)\n", bounty.Amount, float64(bounty.Amount)/1000000.0)
@@ -96,12 +100,13 @@ func GetBountyDetails(id uint64) string {
96100
output += ufmt.Sprintf("**Claimed at:** %s\n", bounty.ClaimedAt.Format("2006-01-02 15:04:05"))
97101
} else {
98102
output += ufmt.Sprintf("**Status:** OPEN\n\n")
103+
output += "**How to apply:** To apply to a bounty, you need to give your **MERGED PR** (important) URL in the transaction. You also need to write your Gno address in the description of your PR to verify your identity. If no address is written, the bounty can be refused.\n\n"
99104

100105
// Add apply button
101106
applyLink := txlink.NewLink("ApplyForBounty").
102107
AddArgs(
103108
"bountyID", ufmt.Sprintf("%d", id),
104-
"prLink", "https://github.com/your-org/your-repo/pull/123",
109+
"prLink", "",
105110
).
106111
URL()
107112
output += md.Link("Apply for this bounty", applyLink) + "\n\n"
@@ -267,15 +272,10 @@ func RenderValidators() string {
267272
output := ListValidators()
268273

269274
// Add register button
270-
registerValidatorLink := txlink.NewLink("AddValidator").
271-
AddArgs(
272-
"validatorAddr", "your_address_here",
273-
).
274-
URL()
275+
registerValidatorLink := txlink.NewLink("AddValidator").URL()
275276
output += "## Register\n\n"
276277
output += md.Link("👨‍⚖️ Register as Validator", registerValidatorLink)
277278
output += "\n\n"
278-
output += "_Note: Replace 'your_address_here' with your actual address when registering._\n"
279279

280280
return output
281281
}
@@ -291,6 +291,10 @@ func Render(path string) string {
291291
return RenderValidators()
292292
}
293293

294+
if path == "leaderboard" {
295+
return RenderLeaderboard()
296+
}
297+
294298
// Parse bounty ID from path
295299
id, err := strconv.Atoi(path)
296300
if err != nil {

packages/r/greg007/gnobounty/types.gno

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
// Bounty represents a bounty post for an issue
1010
type Bounty struct {
1111
ID uint64
12+
Title string // Short title for the bounty
1213
IssueURL string
1314
Description string
1415
Amount int64 // Amount in ugnot
@@ -29,6 +30,13 @@ type Application struct {
2930
Status ApplicationStatus
3031
DAO *commondao.CommonDAO // Private DAO for this application
3132
ProposalID uint64 // Proposal ID in the DAO
33+
Votes []VoteRecord // Local record of votes
34+
}
35+
36+
// VoteRecord represents a vote cast by a validator
37+
type VoteRecord struct {
38+
Voter address
39+
Choice commondao.VoteChoice
3240
}
3341

3442
// ApplicationStatus represents the status of an application

0 commit comments

Comments
 (0)