From f3a177ce362294234f44115e80805070fb4b376a Mon Sep 17 00:00:00 2001 From: moonia Date: Tue, 8 Apr 2025 20:00:05 +0200 Subject: [PATCH 01/20] feat(examples): DAO to propose polls and vote --- examples/gno.land/p/moonia/dao/admin.gno | 1 + examples/gno.land/p/moonia/dao/dao.gno | 71 +++++++++ examples/gno.land/p/moonia/dao/gno.mod | 1 + examples/gno.land/p/moonia/dao/interfaces.gno | 26 +++ examples/gno.land/p/moonia/dao/proposals.gno | 148 ++++++++++++++++++ examples/gno.land/p/moonia/utils/gno.mod | 1 + examples/gno.land/p/moonia/utils/utils.gno | 30 ++++ examples/gno.land/r/moonia/home/actions.gno | 41 +++++ examples/gno.land/r/moonia/home/gno.mod | 1 + examples/gno.land/r/moonia/home/home.gno | 36 +++++ examples/gno.land/r/moonia/home/init.gno | 22 +++ 11 files changed, 378 insertions(+) create mode 100644 examples/gno.land/p/moonia/dao/admin.gno create mode 100644 examples/gno.land/p/moonia/dao/dao.gno create mode 100644 examples/gno.land/p/moonia/dao/gno.mod create mode 100644 examples/gno.land/p/moonia/dao/interfaces.gno create mode 100644 examples/gno.land/p/moonia/dao/proposals.gno create mode 100644 examples/gno.land/p/moonia/utils/gno.mod create mode 100644 examples/gno.land/p/moonia/utils/utils.gno create mode 100644 examples/gno.land/r/moonia/home/actions.gno create mode 100644 examples/gno.land/r/moonia/home/gno.mod create mode 100644 examples/gno.land/r/moonia/home/home.gno create mode 100644 examples/gno.land/r/moonia/home/init.gno diff --git a/examples/gno.land/p/moonia/dao/admin.gno b/examples/gno.land/p/moonia/dao/admin.gno new file mode 100644 index 00000000000..d5591c33cd4 --- /dev/null +++ b/examples/gno.land/p/moonia/dao/admin.gno @@ -0,0 +1 @@ +package dao \ No newline at end of file diff --git a/examples/gno.land/p/moonia/dao/dao.gno b/examples/gno.land/p/moonia/dao/dao.gno new file mode 100644 index 00000000000..80166ff0dde --- /dev/null +++ b/examples/gno.land/p/moonia/dao/dao.gno @@ -0,0 +1,71 @@ +package dao + +import ( + "std" + "strconv" + "gno.land/p/moonia/utils" +) + +type DAO struct { + Admin std.Address + Whitelist map[std.Address]bool +} + +func NewDAO() *DAO { + return &DAO{ + Admin: "", + Whitelist: make(map[std.Address]bool), + } +} + +func (d *DAO) SetAdmin() string { + if d.Admin != "" { + panic("Admin is already set.") + } + caller := utils.GetCaller() + d.Admin = caller + return "Admin set to: " + caller.String() +} + +func (d *DAO) ShowAdmin() string { + if d.Admin == "" { + return "_No admin set._" + } + return "Admin: `" + d.Admin.String() + "`" +} + +func (d *DAO) JoinDAO() string { + caller := utils.GetCaller() + if d.Whitelist[caller] { + panic("You're already a member of the DAO.") + } + d.Whitelist[caller] = true + return "You have successfully joined the DAO." +} + +func (d *DAO) LeaveDAO() string { + caller := utils.GetCaller() + if !d.Whitelist[caller] { + panic("You are not a member of the DAO.") + } + delete(d.Whitelist, caller) + return "You have successfully left the DAO." +} + +func (d *DAO) ShowWhitelist() string { + out := "## Whitelist Members ✅\n\n" + if len(d.Whitelist) == 0 { + return out + "_Whitelist is empty._\n" + } + for addr := range d.Whitelist { + out += "- " + addr.String() + "\n" + } + return out +} + +func (d *DAO) Stats(totalProposals, activeProposals int) string { + return "### Stats\n\n" + + "- Total Proposals: " + strconv.Itoa(totalProposals) + "\n" + + "- Active Proposals: " + strconv.Itoa(activeProposals) + "\n" + + "- Whitelist Members: " + strconv.Itoa(len(d.Whitelist)) + "\n" +} diff --git a/examples/gno.land/p/moonia/dao/gno.mod b/examples/gno.land/p/moonia/dao/gno.mod new file mode 100644 index 00000000000..dc32c6b67ff --- /dev/null +++ b/examples/gno.land/p/moonia/dao/gno.mod @@ -0,0 +1 @@ +module gno.land/p/moonia/dao \ No newline at end of file diff --git a/examples/gno.land/p/moonia/dao/interfaces.gno b/examples/gno.land/p/moonia/dao/interfaces.gno new file mode 100644 index 00000000000..1111e4862d6 --- /dev/null +++ b/examples/gno.land/p/moonia/dao/interfaces.gno @@ -0,0 +1,26 @@ +package dao + +// import "std" + +type IAdmin interface { + SetAdmin() string + TransferAdmin(newAdmin string) string + // KickMember(addr std.Address) string +} + +type IDao interface { + JoinDAO() string + LeaveDAO() string + ShowWhitelist() string + Stats(totalProposals, activeProposals int) string +} + +type IProposal interface { + CreateProposal(title string, description string) string + CloseProposal(indexStr string) string + Vote(indexStr string, voteYesStr string) string + EditProposal(indexStr, newTitle, newDescription string) string + GetProposal(indexStr string) Proposal + ShowProposals() string + // SetVotingPeriod(duration string) string // by the proposal owner (maybe admin too) +} diff --git a/examples/gno.land/p/moonia/dao/proposals.gno b/examples/gno.land/p/moonia/dao/proposals.gno new file mode 100644 index 00000000000..23581d950eb --- /dev/null +++ b/examples/gno.land/p/moonia/dao/proposals.gno @@ -0,0 +1,148 @@ +package dao + +import ( + "std" + "strconv" + "gno.land/p/moonia/utils" + "gno.land/p/moul/txlink" + "gno.land/p/moul/md" +) + +type Proposal struct { + Title string + Description string + Creator std.Address + YesVotes int + NoVotes int + Voters map[std.Address]bool + Active bool +} + +type ProposalStore struct { + Proposals []Proposal + DAO *DAO +} + +func NewProposalStore(dao *DAO) *ProposalStore { + return &ProposalStore{ + Proposals: []Proposal{}, + DAO: dao, + } +} + +func (ps *ProposalStore) CreateProposal(title string, description string) string { + caller := utils.GetCaller() + if !ps.DAO.Whitelist[caller] { + panic("Only whitelisted members can create proposals.") + } + p := Proposal{ + Title: title, + Description: description, + Creator: caller, + Voters: make(map[std.Address]bool), + Active: true, + } + ps.Proposals = append(ps.Proposals, p) + return "Proposal created: " + title +} + +func (ps *ProposalStore) CreateProposalTest() string { + return ps.CreateProposal("Survey", "Would you like to visit Guatemala?") +} + +func (ps *ProposalStore) CloseProposal(indexStr string) string { + index := utils.ParseIndex(indexStr, len(ps.Proposals)) + p := &ps.Proposals[index] + if p.Creator != utils.GetCaller() { + panic("Only the proposal creator can close it.") + } + if !p.Active { + panic("Proposal is already closed.") + } + p.Active = false + return "Proposal '" + p.Title + "' has been closed." +} + +// gnokey maketx call -pkgpath "gno.land/r/moonia/home" -func "Vote" -args "0" -args "true" -gas-fee 1000000ugnot -gas-wanted 5000000 -broadcast -chainid "dev" -remote "tcp://127.0.0.1:26657" g15dz69sch7fkhc9gk57hpe4qea77thmy20apu9x +func (ps *ProposalStore) Vote(indexStr string, voteYesStr string) string { + index := utils.ParseIndex(indexStr, len(ps.Proposals)) + voteYes := voteYesStr == "true" + caller := utils.GetCaller() + + if !ps.DAO.Whitelist[caller] { + panic("Only whitelisted members can vote.") + } + + p := &ps.Proposals[index] + if !p.Active { + panic("Voting is closed for this proposal.") + } + if p.Voters[caller] { + panic("You have already voted.") + } + + p.Voters[caller] = true + if voteYes { + p.YesVotes++ + return "Vote recorded: YES for '" + p.Title + "'" + } else { + p.NoVotes++ + return "Vote recorded: NO for '" + p.Title + "'" + } +} + +func (ps *ProposalStore) ShowProposals() string { + activeOut := "## Active Proposals\n\n" + closedOut := "## Closed Proposals\n\n" + hasActive := false + hasClosed := false + + for i, p := range ps.Proposals { + proposalStr := "**[" + strconv.Itoa(i) + "]** " + p.Title + "\n" + proposalStr += p.Description + "\n\n" + proposalStr += "by _" + p.Creator.String() + "_\n\n" + proposalStr += "✅ " + strconv.Itoa(p.YesVotes) + " | ❌ " + strconv.Itoa(p.NoVotes) + "\n" + + if p.Active { + hasActive = true + proposalStr += "(Active) — " + + md.Link("Vote Yes", txlink.Call("Vote", "args", strconv.Itoa(i), "args", "true")) + " | " + + md.Link("Vote No", txlink.Call("Vote", "args", strconv.Itoa(i), "args", "false")) + "\n\n" + + md.Link("❌ Close proposal", txlink.Call("CloseProposal", "args", strconv.Itoa(i))) + "\n\n ---" + activeOut += proposalStr + "\n\n" + } else { + hasClosed = true + proposalStr += "(Closed)\n" + closedOut += proposalStr + "\n\n ---- \n\n" + } + } + + if !hasActive { + activeOut += "_No active proposals._\n\n" + } + if !hasClosed { + closedOut += "_No closed proposals._\n\n" + } + return activeOut + "\n" + closedOut +} + +// gnokey maketx call -pkgpath "gno.land/r/moonia/home" -func "EditProposal" -args "2" -args "hehehehe" -args "hihihihi" -gas-fee 1000000ugnot -gas-wanted 5000000 -broadcast -chainid "dev" -remote "tcp://127.0.0.1:26657" g15dz69sch7fkhc9gk57hpe4qea77thmy20apu9x +func (ps *ProposalStore) EditProposal(indexStr, newTitle, newDescription string) string { + index := utils.ParseIndex(indexStr, len(ps.Proposals)) + p := &ps.Proposals[index] + if p.Creator != utils.GetCaller() { + panic("Only the creator can edit the proposal.") + } + if !p.Active { + panic("Cannot edit a closed proposal.") + } + p.Title = newTitle + p.Description = newDescription + return "Proposal updated: " + newTitle + newDescription +} + +// gnokey maketx call -pkgpath "gno.land/r/moonia/home" -func "GetProposal" -args "0" -gas-fee 1000000ugnot -gas-wanted 5000000 -broadcast -chainid "dev" -remote "tcp://127.0.0.1:26657" g15dz69sch7fkhc9gk57hpe4qea77thmy20apu9x +func (ps *ProposalStore) GetProposal(indexStr string) Proposal { + index := utils.ParseIndex(indexStr, len(ps.Proposals)) + return ps.Proposals[index] +} diff --git a/examples/gno.land/p/moonia/utils/gno.mod b/examples/gno.land/p/moonia/utils/gno.mod new file mode 100644 index 00000000000..a67c87cc0d7 --- /dev/null +++ b/examples/gno.land/p/moonia/utils/gno.mod @@ -0,0 +1 @@ +module gno.land/p/moonia/utils \ No newline at end of file diff --git a/examples/gno.land/p/moonia/utils/utils.gno b/examples/gno.land/p/moonia/utils/utils.gno new file mode 100644 index 00000000000..d48fd86480e --- /dev/null +++ b/examples/gno.land/p/moonia/utils/utils.gno @@ -0,0 +1,30 @@ +package utils + +import ( + "std" + "strconv" + "strings" +) + +func GetCaller() std.Address { + return std.PreviousRealm().Address() +} + +func Parse(fullpath string) (string, map[string]string) { + parts := strings.Split(fullpath, ":") + if len(parts) == 2 { + return parts[1], map[string]string{} + } + return "", map[string]string{} +} + +func ParseIndex(indexStr string, max int) int { + index, err := strconv.Atoi(indexStr) + if err != nil { + panic("Invalid index.") + } + if index < 0 || index >= max { + panic("Proposal does not exist.") + } + return index +} \ No newline at end of file diff --git a/examples/gno.land/r/moonia/home/actions.gno b/examples/gno.land/r/moonia/home/actions.gno new file mode 100644 index 00000000000..cbeac342c2d --- /dev/null +++ b/examples/gno.land/r/moonia/home/actions.gno @@ -0,0 +1,41 @@ +package home + +import ( + "gno.land/p/moonia/dao" +) + +func SetAdmin() string { + return ds.DAO.SetAdmin() +} + +func JoinDAO() string { + return ds.DAO.JoinDAO() +} + +func LeaveDAO() string { + return ds.DAO.LeaveDAO() +} + +func CreateProposalTest() string { + return ds.Proposals.CreateProposalTest() +} + +func CreateProposal(title string, description string) string { + return ds.Proposals.CreateProposal(title, description) +} + +func Vote(indexStr string, voteYesStr string) string { + return ds.Proposals.Vote(indexStr, voteYesStr) +} + +func CloseProposal(indexStr string) string { + return ds.Proposals.CloseProposal(indexStr) +} + +func EditProposal(indexStr, newTitle, newDescription string) string { + return ds.Proposals.EditProposal(indexStr, newTitle, newDescription) +} + +func GetProposal(indexStr string) dao.Proposal { + return ds.Proposals.GetProposal(indexStr) +} \ No newline at end of file diff --git a/examples/gno.land/r/moonia/home/gno.mod b/examples/gno.land/r/moonia/home/gno.mod new file mode 100644 index 00000000000..990965df8ba --- /dev/null +++ b/examples/gno.land/r/moonia/home/gno.mod @@ -0,0 +1 @@ +module gno.land/r/moonia/home \ No newline at end of file diff --git a/examples/gno.land/r/moonia/home/home.gno b/examples/gno.land/r/moonia/home/home.gno new file mode 100644 index 00000000000..b723cef7fb7 --- /dev/null +++ b/examples/gno.land/r/moonia/home/home.gno @@ -0,0 +1,36 @@ +package home + +import ( + "gno.land/p/moul/txlink" + "gno.land/p/moul/md" +) + +func countActive() int { + count := 0 + for _, p := range ds.Proposals.Proposals { + if p.Active { + count++ + } + } + return count +} + +func renderDAO() string { + out := "# Moonia's DAO\n\n" + out += ds.DAO.ShowAdmin() + "\n\n" + out += ds.DAO.Stats(len(ds.Proposals.Proposals), countActive()) + "\n" + out += "## Actions:\n\n" + if ds.DAO.Admin == "" { + out += "- " + md.Link("Set Admin (once)", txlink.Call("SetAdmin")) + "\n" + } + out += "- " + md.Link("Join DAO", txlink.Call("JoinDAO")) + "\n" + out += "- " + md.Link("Leave DAO", txlink.Call("LeaveDAO")) + "\n" + out += "- " + md.Link("Create Sample Proposal", txlink.Call("CreateProposalTest")) + "\n" + out += ds.DAO.ShowWhitelist() + "\n" + out += ds.Proposals.ShowProposals() + return out +} + +func Render(path string) string { + return renderDAO() +} diff --git a/examples/gno.land/r/moonia/home/init.gno b/examples/gno.land/r/moonia/home/init.gno new file mode 100644 index 00000000000..28b210705c6 --- /dev/null +++ b/examples/gno.land/r/moonia/home/init.gno @@ -0,0 +1,22 @@ +package home + +import ( + "gno.land/p/moonia/dao" +) + +var ds DAOState + +type DAOState struct { + DAO *dao.DAO + Proposals *dao.ProposalStore +} + +func init() { + myDao := dao.NewDAO() + myProps := dao.NewProposalStore(myDao) + + ds = DAOState{ + DAO: myDao, + Proposals: myProps, + } +} \ No newline at end of file From dd8ac24b7210b431887ffc72e8daa8bff94e184e Mon Sep 17 00:00:00 2001 From: moonia Date: Tue, 8 Apr 2025 20:21:59 +0200 Subject: [PATCH 02/20] fix: unused parsing function --- examples/gno.land/p/moonia/utils/utils.gno | 9 --------- 1 file changed, 9 deletions(-) diff --git a/examples/gno.land/p/moonia/utils/utils.gno b/examples/gno.land/p/moonia/utils/utils.gno index d48fd86480e..454430899f9 100644 --- a/examples/gno.land/p/moonia/utils/utils.gno +++ b/examples/gno.land/p/moonia/utils/utils.gno @@ -3,21 +3,12 @@ package utils import ( "std" "strconv" - "strings" ) func GetCaller() std.Address { return std.PreviousRealm().Address() } -func Parse(fullpath string) (string, map[string]string) { - parts := strings.Split(fullpath, ":") - if len(parts) == 2 { - return parts[1], map[string]string{} - } - return "", map[string]string{} -} - func ParseIndex(indexStr string, max int) int { index, err := strconv.Atoi(indexStr) if err != nil { From d374e16b5261d559b630188ba06688da954a4c4a Mon Sep 17 00:00:00 2001 From: moonia Date: Wed, 9 Apr 2025 12:11:28 +0200 Subject: [PATCH 03/20] fix: admin is automatically added to whitelist --- examples/gno.land/p/moonia/dao/dao.gno | 1 + examples/gno.land/r/moonia/home/home.gno | 18 ++++++++++++++++++ examples/gno.land/r/moonia/home/init.gno | 22 ---------------------- 3 files changed, 19 insertions(+), 22 deletions(-) delete mode 100644 examples/gno.land/r/moonia/home/init.gno diff --git a/examples/gno.land/p/moonia/dao/dao.gno b/examples/gno.land/p/moonia/dao/dao.gno index 80166ff0dde..309756b34a6 100644 --- a/examples/gno.land/p/moonia/dao/dao.gno +++ b/examples/gno.land/p/moonia/dao/dao.gno @@ -24,6 +24,7 @@ func (d *DAO) SetAdmin() string { } caller := utils.GetCaller() d.Admin = caller + d.JoinDAO() return "Admin set to: " + caller.String() } diff --git a/examples/gno.land/r/moonia/home/home.gno b/examples/gno.land/r/moonia/home/home.gno index b723cef7fb7..5881e2a2373 100644 --- a/examples/gno.land/r/moonia/home/home.gno +++ b/examples/gno.land/r/moonia/home/home.gno @@ -3,8 +3,26 @@ package home import ( "gno.land/p/moul/txlink" "gno.land/p/moul/md" + "gno.land/p/moonia/dao" ) +var ds DAOState + +type DAOState struct { + DAO *dao.DAO + Proposals *dao.ProposalStore +} + +func init() { + myDao := dao.NewDAO() + myProps := dao.NewProposalStore(myDao) + + ds = DAOState{ + DAO: myDao, + Proposals: myProps, + } +} + func countActive() int { count := 0 for _, p := range ds.Proposals.Proposals { diff --git a/examples/gno.land/r/moonia/home/init.gno b/examples/gno.land/r/moonia/home/init.gno deleted file mode 100644 index 28b210705c6..00000000000 --- a/examples/gno.land/r/moonia/home/init.gno +++ /dev/null @@ -1,22 +0,0 @@ -package home - -import ( - "gno.land/p/moonia/dao" -) - -var ds DAOState - -type DAOState struct { - DAO *dao.DAO - Proposals *dao.ProposalStore -} - -func init() { - myDao := dao.NewDAO() - myProps := dao.NewProposalStore(myDao) - - ds = DAOState{ - DAO: myDao, - Proposals: myProps, - } -} \ No newline at end of file From 0557d24fbb7bf74a2055305ccde7cbb7efec7442 Mon Sep 17 00:00:00 2001 From: moonia Date: Wed, 9 Apr 2025 19:20:50 +0200 Subject: [PATCH 04/20] feat: Kick and add members by admin --- examples/gno.land/p/moonia/dao/admin.gno | 56 ++++++++++++++++++- examples/gno.land/p/moonia/dao/dao.gno | 10 ---- examples/gno.land/p/moonia/dao/interfaces.gno | 7 ++- examples/gno.land/r/moonia/home/actions.gno | 43 +++++++++++++- 4 files changed, 101 insertions(+), 15 deletions(-) diff --git a/examples/gno.land/p/moonia/dao/admin.gno b/examples/gno.land/p/moonia/dao/admin.gno index d5591c33cd4..3b44f5eb430 100644 --- a/examples/gno.land/p/moonia/dao/admin.gno +++ b/examples/gno.land/p/moonia/dao/admin.gno @@ -1 +1,55 @@ -package dao \ No newline at end of file +package dao + +import ( + "gno.land/p/moonia/utils" + "std" +) + +// Admin Methods // + +func (d *DAO) SetAdmin() string { + if d.Admin != "" { + panic("Admin is already set.") + } + caller := utils.GetCaller() + d.Admin = caller + d.JoinDAO() + + return "Admin set to: " + caller.String() +} + +func (d *DAO) IsAdmin(addr std.Address) bool { + return addr == d.Admin +} + +// Member Methods // + +func (d *DAO) AddMember(addr std.Address) string { + caller := utils.GetCaller() + + if !d.IsAdmin(caller) { + panic("Only the admin can add members.") + } + if d.Whitelist[addr] { + return "Address is already a member." + } + d.Whitelist[addr] = true + return "Member added: " + addr.String() +} + +func (d *DAO) KickMember(addr std.Address) string { + caller := utils.GetCaller() + + if !d.IsAdmin(caller) { + panic("Only the admin can kick members.") + } + if !d.Whitelist[addr] { + return "Member not found." + } + delete(d.Whitelist, addr) + return "Member kicked: " + addr.String() +} + +func (d* DAO) ListMembers() map[std.Address]bool { + return d.Whitelist +} \ No newline at end of file diff --git a/examples/gno.land/p/moonia/dao/dao.gno b/examples/gno.land/p/moonia/dao/dao.gno index 309756b34a6..a77b9cde9d8 100644 --- a/examples/gno.land/p/moonia/dao/dao.gno +++ b/examples/gno.land/p/moonia/dao/dao.gno @@ -18,16 +18,6 @@ func NewDAO() *DAO { } } -func (d *DAO) SetAdmin() string { - if d.Admin != "" { - panic("Admin is already set.") - } - caller := utils.GetCaller() - d.Admin = caller - d.JoinDAO() - return "Admin set to: " + caller.String() -} - func (d *DAO) ShowAdmin() string { if d.Admin == "" { return "_No admin set._" diff --git a/examples/gno.land/p/moonia/dao/interfaces.gno b/examples/gno.land/p/moonia/dao/interfaces.gno index 1111e4862d6..29c5afb969b 100644 --- a/examples/gno.land/p/moonia/dao/interfaces.gno +++ b/examples/gno.land/p/moonia/dao/interfaces.gno @@ -1,11 +1,12 @@ package dao -// import "std" +import "std" type IAdmin interface { SetAdmin() string - TransferAdmin(newAdmin string) string - // KickMember(addr std.Address) string + TransferAdmin(addr string) string + KickMember(addr std.Address) string + AddMember(addr std.Address) string } type IDao interface { diff --git a/examples/gno.land/r/moonia/home/actions.gno b/examples/gno.land/r/moonia/home/actions.gno index cbeac342c2d..ba937201631 100644 --- a/examples/gno.land/r/moonia/home/actions.gno +++ b/examples/gno.land/r/moonia/home/actions.gno @@ -2,12 +2,42 @@ package home import ( "gno.land/p/moonia/dao" + "std" ) +// Admin Methods // + +func IsAdmin(addr std.Address) bool { + return ds.DAO.IsAdmin(addr) +} + func SetAdmin() string { return ds.DAO.SetAdmin() } +// func TransferAdmin(addr string) string { +// return ds.DAO.TransferAdmin(addr) +// } + +func KickMember(addr std.Address) string { + return ds.DAO.KickMember(addr) +} + +func AddMember(addr std.Address) string { + return ds.DAO.AddMember(addr) +} + +func ListMembers() map[std.Address]bool { + return ds.DAO.Whitelist +} + +// DAO Methods // + +// TODO: +// func RequestDAO() string { +// return ds.DAO.RequestDAO() +// } + func JoinDAO() string { return ds.DAO.JoinDAO() } @@ -16,6 +46,12 @@ func LeaveDAO() string { return ds.DAO.LeaveDAO() } +func IsMember(addr std.Address) bool { + return ds.DAO.Whitelist[addr] +} + +// Proposals Methods // + func CreateProposalTest() string { return ds.Proposals.CreateProposalTest() } @@ -38,4 +74,9 @@ func EditProposal(indexStr, newTitle, newDescription string) string { func GetProposal(indexStr string) dao.Proposal { return ds.Proposals.GetProposal(indexStr) -} \ No newline at end of file +} + +// TODO: +// func SetVotingPeriod(period int64) string { +// return ds.Proposals.SetVotingPeriod(period) +// } \ No newline at end of file From c53fa3a2d03b75c01f9575ba9f9bf7bce2122bc6 Mon Sep 17 00:00:00 2001 From: moonia Date: Wed, 9 Apr 2025 19:24:26 +0200 Subject: [PATCH 05/20] fix: rm useless comments for gnokey --- examples/gno.land/p/moonia/dao/proposals.gno | 3 --- 1 file changed, 3 deletions(-) diff --git a/examples/gno.land/p/moonia/dao/proposals.gno b/examples/gno.land/p/moonia/dao/proposals.gno index 23581d950eb..55c444db272 100644 --- a/examples/gno.land/p/moonia/dao/proposals.gno +++ b/examples/gno.land/p/moonia/dao/proposals.gno @@ -63,7 +63,6 @@ func (ps *ProposalStore) CloseProposal(indexStr string) string { return "Proposal '" + p.Title + "' has been closed." } -// gnokey maketx call -pkgpath "gno.land/r/moonia/home" -func "Vote" -args "0" -args "true" -gas-fee 1000000ugnot -gas-wanted 5000000 -broadcast -chainid "dev" -remote "tcp://127.0.0.1:26657" g15dz69sch7fkhc9gk57hpe4qea77thmy20apu9x func (ps *ProposalStore) Vote(indexStr string, voteYesStr string) string { index := utils.ParseIndex(indexStr, len(ps.Proposals)) voteYes := voteYesStr == "true" @@ -126,7 +125,6 @@ func (ps *ProposalStore) ShowProposals() string { return activeOut + "\n" + closedOut } -// gnokey maketx call -pkgpath "gno.land/r/moonia/home" -func "EditProposal" -args "2" -args "hehehehe" -args "hihihihi" -gas-fee 1000000ugnot -gas-wanted 5000000 -broadcast -chainid "dev" -remote "tcp://127.0.0.1:26657" g15dz69sch7fkhc9gk57hpe4qea77thmy20apu9x func (ps *ProposalStore) EditProposal(indexStr, newTitle, newDescription string) string { index := utils.ParseIndex(indexStr, len(ps.Proposals)) p := &ps.Proposals[index] @@ -141,7 +139,6 @@ func (ps *ProposalStore) EditProposal(indexStr, newTitle, newDescription string) return "Proposal updated: " + newTitle + newDescription } -// gnokey maketx call -pkgpath "gno.land/r/moonia/home" -func "GetProposal" -args "0" -gas-fee 1000000ugnot -gas-wanted 5000000 -broadcast -chainid "dev" -remote "tcp://127.0.0.1:26657" g15dz69sch7fkhc9gk57hpe4qea77thmy20apu9x func (ps *ProposalStore) GetProposal(indexStr string) Proposal { index := utils.ParseIndex(indexStr, len(ps.Proposals)) return ps.Proposals[index] From e3b95b16cadfca7a77e86b51385442fc828bf5c2 Mon Sep 17 00:00:00 2001 From: moonia Date: Wed, 9 Apr 2025 20:07:07 +0200 Subject: [PATCH 06/20] feat(wip): set et edit voting period --- examples/gno.land/p/moonia/dao/interfaces.gno | 7 +-- examples/gno.land/p/moonia/dao/proposals.gno | 60 ++++++++++++------- examples/gno.land/p/moonia/utils/utils.gno | 10 ++++ examples/gno.land/r/moonia/home/actions.gno | 15 ++--- 4 files changed, 58 insertions(+), 34 deletions(-) diff --git a/examples/gno.land/p/moonia/dao/interfaces.gno b/examples/gno.land/p/moonia/dao/interfaces.gno index 29c5afb969b..b86c850363f 100644 --- a/examples/gno.land/p/moonia/dao/interfaces.gno +++ b/examples/gno.land/p/moonia/dao/interfaces.gno @@ -17,11 +17,10 @@ type IDao interface { } type IProposal interface { - CreateProposal(title string, description string) string + CreateProposal(title, description string) string CloseProposal(indexStr string) string - Vote(indexStr string, voteYesStr string) string - EditProposal(indexStr, newTitle, newDescription string) string + Vote(indexStr, voteYesStr string) string + EditProposal(indexStr, newTitle, newDescription string, newPeriod int64) string GetProposal(indexStr string) Proposal ShowProposals() string - // SetVotingPeriod(duration string) string // by the proposal owner (maybe admin too) } diff --git a/examples/gno.land/p/moonia/dao/proposals.gno b/examples/gno.land/p/moonia/dao/proposals.gno index 55c444db272..54b531694ec 100644 --- a/examples/gno.land/p/moonia/dao/proposals.gno +++ b/examples/gno.land/p/moonia/dao/proposals.gno @@ -9,18 +9,21 @@ import ( ) type Proposal struct { - Title string - Description string - Creator std.Address - YesVotes int - NoVotes int - Voters map[std.Address]bool - Active bool + Title string + Description string + Creator std.Address + YesVotes int + NoVotes int + Voters map[std.Address]bool + Active bool + VotingPeriod int64 + CreatedAt int64 } type ProposalStore struct { Proposals []Proposal DAO *DAO + } func NewProposalStore(dao *DAO) *ProposalStore { @@ -30,24 +33,30 @@ func NewProposalStore(dao *DAO) *ProposalStore { } } -func (ps *ProposalStore) CreateProposal(title string, description string) string { +func (ps *ProposalStore) CreateProposal(title, description string, period int64) string { caller := utils.GetCaller() if !ps.DAO.Whitelist[caller] { panic("Only whitelisted members can create proposals.") } + if period <= 0 { + panic("Voting period must be greater than 0.") + } + p := Proposal{ - Title: title, - Description: description, - Creator: caller, - Voters: make(map[std.Address]bool), - Active: true, + Title: title, + Description: description, + Creator: caller, + Voters: make(map[std.Address]bool), + Active: true, + VotingPeriod: period, + CreatedAt: utils.GetBlockTime(), } ps.Proposals = append(ps.Proposals, p) - return "Proposal created: " + title + return "Proposal created: " + title + " Voting period: " + strconv.Itoa(int(period)) } func (ps *ProposalStore) CreateProposalTest() string { - return ps.CreateProposal("Survey", "Would you like to visit Guatemala?") + return ps.CreateProposal("Survey", "Would you like to visit Guatemala?", 30) } func (ps *ProposalStore) CloseProposal(indexStr string) string { @@ -63,7 +72,7 @@ func (ps *ProposalStore) CloseProposal(indexStr string) string { return "Proposal '" + p.Title + "' has been closed." } -func (ps *ProposalStore) Vote(indexStr string, voteYesStr string) string { +func (ps *ProposalStore) Vote(indexStr, voteYesStr string) string { index := utils.ParseIndex(indexStr, len(ps.Proposals)) voteYes := voteYesStr == "true" caller := utils.GetCaller() @@ -73,6 +82,13 @@ func (ps *ProposalStore) Vote(indexStr string, voteYesStr string) string { } p := &ps.Proposals[index] + + currentTime := utils.GetBlockTime() + if currentTime - p.CreatedAt >= p.VotingPeriod { + p.Active = false + panic("Voting period is over. Proposal is now closed.") + } + if !p.Active { panic("Voting is closed for this proposal.") } @@ -100,6 +116,7 @@ func (ps *ProposalStore) ShowProposals() string { proposalStr := "**[" + strconv.Itoa(i) + "]** " + p.Title + "\n" proposalStr += p.Description + "\n\n" proposalStr += "by _" + p.Creator.String() + "_\n\n" + proposalStr += "📅 Voting ends at: " + utils.FormatTimestamp(p.CreatedAt + p.VotingPeriod) + "\n\n" proposalStr += "✅ " + strconv.Itoa(p.YesVotes) + " | ❌ " + strconv.Itoa(p.NoVotes) + "\n" if p.Active { @@ -115,7 +132,6 @@ func (ps *ProposalStore) ShowProposals() string { closedOut += proposalStr + "\n\n ---- \n\n" } } - if !hasActive { activeOut += "_No active proposals._\n\n" } @@ -125,7 +141,7 @@ func (ps *ProposalStore) ShowProposals() string { return activeOut + "\n" + closedOut } -func (ps *ProposalStore) EditProposal(indexStr, newTitle, newDescription string) string { +func (ps *ProposalStore) EditProposal(indexStr, newTitle, newDescription string, newPeriod int64) string { index := utils.ParseIndex(indexStr, len(ps.Proposals)) p := &ps.Proposals[index] if p.Creator != utils.GetCaller() { @@ -134,12 +150,16 @@ func (ps *ProposalStore) EditProposal(indexStr, newTitle, newDescription string) if !p.Active { panic("Cannot edit a closed proposal.") } + if newPeriod <= 0 { + panic("Voting period must be greater than 0.") + } p.Title = newTitle p.Description = newDescription - return "Proposal updated: " + newTitle + newDescription + p.VotingPeriod = newPeriod + return "Proposal updated: " + newTitle + newDescription + strconv.Itoa(int(newPeriod)) } func (ps *ProposalStore) GetProposal(indexStr string) Proposal { index := utils.ParseIndex(indexStr, len(ps.Proposals)) return ps.Proposals[index] -} +} \ No newline at end of file diff --git a/examples/gno.land/p/moonia/utils/utils.gno b/examples/gno.land/p/moonia/utils/utils.gno index 454430899f9..492df49e4ea 100644 --- a/examples/gno.land/p/moonia/utils/utils.gno +++ b/examples/gno.land/p/moonia/utils/utils.gno @@ -3,8 +3,18 @@ package utils import ( "std" "strconv" + "time" ) +func GetBlockTime() int64 { + return time.Now().Unix() +} + +func FormatTimestamp(timestamp int64) string { + t := time.Unix(timestamp, 0) + return t.Format("02 Jan 2006, 15:04") +} + func GetCaller() std.Address { return std.PreviousRealm().Address() } diff --git a/examples/gno.land/r/moonia/home/actions.gno b/examples/gno.land/r/moonia/home/actions.gno index ba937201631..8d12baffc29 100644 --- a/examples/gno.land/r/moonia/home/actions.gno +++ b/examples/gno.land/r/moonia/home/actions.gno @@ -56,11 +56,11 @@ func CreateProposalTest() string { return ds.Proposals.CreateProposalTest() } -func CreateProposal(title string, description string) string { - return ds.Proposals.CreateProposal(title, description) +func CreateProposal(title, description string, period int64) string { + return ds.Proposals.CreateProposal(title, description, period) } -func Vote(indexStr string, voteYesStr string) string { +func Vote(indexStr, voteYesStr string) string { return ds.Proposals.Vote(indexStr, voteYesStr) } @@ -68,15 +68,10 @@ func CloseProposal(indexStr string) string { return ds.Proposals.CloseProposal(indexStr) } -func EditProposal(indexStr, newTitle, newDescription string) string { - return ds.Proposals.EditProposal(indexStr, newTitle, newDescription) +func EditProposal(indexStr, newTitle, newDescription string, newPeriod int64) string { + return ds.Proposals.EditProposal(indexStr, newTitle, newDescription, newPeriod) } func GetProposal(indexStr string) dao.Proposal { return ds.Proposals.GetProposal(indexStr) } - -// TODO: -// func SetVotingPeriod(period int64) string { -// return ds.Proposals.SetVotingPeriod(period) -// } \ No newline at end of file From 3b60ed46eda6d552177fa6114fb7d2fe60585f0b Mon Sep 17 00:00:00 2001 From: moonia Date: Wed, 9 Apr 2025 23:16:55 +0200 Subject: [PATCH 07/20] feat: transfer admin + IsMember method --- examples/gno.land/p/moonia/dao/admin.gno | 27 ++++++++++++++++++--- examples/gno.land/p/moonia/dao/dao.gno | 6 ++++- examples/gno.land/r/moonia/home/actions.gno | 10 +++----- examples/gno.land/r/moonia/home/home.gno | 3 +++ 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/examples/gno.land/p/moonia/dao/admin.gno b/examples/gno.land/p/moonia/dao/admin.gno index 3b44f5eb430..0fdcd509a80 100644 --- a/examples/gno.land/p/moonia/dao/admin.gno +++ b/examples/gno.land/p/moonia/dao/admin.gno @@ -7,6 +7,23 @@ import ( // Admin Methods // +func (d *DAO) TransferAdmin(addr std.Address) string { + caller := utils.GetCaller() + + if !d.IsAdmin(caller) { + panic("Only the admin can transfer admin.") + } + if d.IsAdmin(addr) { + return "Address is already the admin." + } + if !d.IsMember(addr) { + panic("New admin must be a member.") + } + + d.Admin = addr + return "Admin transferred to " + addr.String() +} + func (d *DAO) SetAdmin() string { if d.Admin != "" { panic("Admin is already set.") @@ -24,13 +41,17 @@ func (d *DAO) IsAdmin(addr std.Address) bool { // Member Methods // +func (d *DAO) IsMember(addr std.Address) bool { + return d.Whitelist[addr] +} + func (d *DAO) AddMember(addr std.Address) string { caller := utils.GetCaller() if !d.IsAdmin(caller) { panic("Only the admin can add members.") } - if d.Whitelist[addr] { + if d.IsMember(addr) { return "Address is already a member." } d.Whitelist[addr] = true @@ -43,8 +64,8 @@ func (d *DAO) KickMember(addr std.Address) string { if !d.IsAdmin(caller) { panic("Only the admin can kick members.") } - if !d.Whitelist[addr] { - return "Member not found." + if !d.IsMember(addr) { + return "Address is not a member." } delete(d.Whitelist, addr) return "Member kicked: " + addr.String() diff --git a/examples/gno.land/p/moonia/dao/dao.gno b/examples/gno.land/p/moonia/dao/dao.gno index a77b9cde9d8..b0521c350cd 100644 --- a/examples/gno.land/p/moonia/dao/dao.gno +++ b/examples/gno.land/p/moonia/dao/dao.gno @@ -49,7 +49,11 @@ func (d *DAO) ShowWhitelist() string { return out + "_Whitelist is empty._\n" } for addr := range d.Whitelist { - out += "- " + addr.String() + "\n" + if addr == d.Admin { + out += "- " + addr.String() + " (Admin)" + "\n" + } else { + out += "- " + addr.String() + "\n" + } } return out } diff --git a/examples/gno.land/r/moonia/home/actions.gno b/examples/gno.land/r/moonia/home/actions.gno index 8d12baffc29..8a205c1d5f3 100644 --- a/examples/gno.land/r/moonia/home/actions.gno +++ b/examples/gno.land/r/moonia/home/actions.gno @@ -15,9 +15,9 @@ func SetAdmin() string { return ds.DAO.SetAdmin() } -// func TransferAdmin(addr string) string { -// return ds.DAO.TransferAdmin(addr) -// } +func TransferAdmin(addr std.Address) string { + return ds.DAO.TransferAdmin(addr) +} func KickMember(addr std.Address) string { return ds.DAO.KickMember(addr) @@ -46,10 +46,6 @@ func LeaveDAO() string { return ds.DAO.LeaveDAO() } -func IsMember(addr std.Address) bool { - return ds.DAO.Whitelist[addr] -} - // Proposals Methods // func CreateProposalTest() string { diff --git a/examples/gno.land/r/moonia/home/home.gno b/examples/gno.land/r/moonia/home/home.gno index 5881e2a2373..dfa84a00e72 100644 --- a/examples/gno.land/r/moonia/home/home.gno +++ b/examples/gno.land/r/moonia/home/home.gno @@ -4,6 +4,7 @@ import ( "gno.land/p/moul/txlink" "gno.land/p/moul/md" "gno.land/p/moonia/dao" + "gno.land/p/moonia/utils" ) var ds DAOState @@ -34,6 +35,7 @@ func countActive() int { } func renderDAO() string { + caller := utils.GetCaller() out := "# Moonia's DAO\n\n" out += ds.DAO.ShowAdmin() + "\n\n" out += ds.DAO.Stats(len(ds.Proposals.Proposals), countActive()) + "\n" @@ -41,6 +43,7 @@ func renderDAO() string { if ds.DAO.Admin == "" { out += "- " + md.Link("Set Admin (once)", txlink.Call("SetAdmin")) + "\n" } + out += "- " + md.Link("Transfer Admin", txlink.Call("TransferAdmin", "args", caller.String())) + "\n" out += "- " + md.Link("Join DAO", txlink.Call("JoinDAO")) + "\n" out += "- " + md.Link("Leave DAO", txlink.Call("LeaveDAO")) + "\n" out += "- " + md.Link("Create Sample Proposal", txlink.Call("CreateProposalTest")) + "\n" From 6399fcf8a4abee162f09ab03cd005ae2b039ed9c Mon Sep 17 00:00:00 2001 From: moonia Date: Thu, 10 Apr 2025 17:42:44 +0200 Subject: [PATCH 08/20] feat: dashboard to join or create a DAO + view DAO by id --- examples/gno.land/p/moonia/dao/dao.gno | 20 ++++-- examples/gno.land/r/moonia/home/actions.gno | 26 ++++++- examples/gno.land/r/moonia/home/dashboard.gno | 37 ++++++++++ examples/gno.land/r/moonia/home/home.gno | 68 +++++++++++++++---- 4 files changed, 131 insertions(+), 20 deletions(-) create mode 100644 examples/gno.land/r/moonia/home/dashboard.gno diff --git a/examples/gno.land/p/moonia/dao/dao.gno b/examples/gno.land/p/moonia/dao/dao.gno index b0521c350cd..060657b5d92 100644 --- a/examples/gno.land/p/moonia/dao/dao.gno +++ b/examples/gno.land/p/moonia/dao/dao.gno @@ -7,17 +7,27 @@ import ( ) type DAO struct { - Admin std.Address - Whitelist map[std.Address]bool + Admin std.Address + Whitelist map[std.Address]bool + Requests map[std.Address]bool + Name string + Description string } -func NewDAO() *DAO { +func NewDAO(name, desc string) *DAO { return &DAO{ - Admin: "", - Whitelist: make(map[std.Address]bool), + Admin: "", + Whitelist: make(map[std.Address]bool), + Requests: make(map[std.Address]bool), + Name: name, + Description: desc, } } +func (d *DAO) ListRequests() map[std.Address]bool { + return d.Requests +} + func (d *DAO) ShowAdmin() string { if d.Admin == "" { return "_No admin set._" diff --git a/examples/gno.land/r/moonia/home/actions.gno b/examples/gno.land/r/moonia/home/actions.gno index 8a205c1d5f3..e8dd313c542 100644 --- a/examples/gno.land/r/moonia/home/actions.gno +++ b/examples/gno.land/r/moonia/home/actions.gno @@ -31,9 +31,33 @@ func ListMembers() map[std.Address]bool { return ds.DAO.Whitelist } +// func AcceptRequest(addr std.Address) string { +// return ds.DAO.AcceptRequest(addr) +// } + +// func DeclineRequest(addr std.Address) string { +// return ds.DAO.DeclineRequest(addr) +// } + +func ListRequests() map[std.Address]bool { + return ds.DAO.ListRequests() +} + // DAO Methods // -// TODO: +func CreateDAO(id, name, desc string) string { + if daoMap[id] != nil { + return "DAO with this ID already exists." + } + newDAO := dao.NewDAO(name, desc) + daoMap[id] = newDAO + proposalMap[id] = dao.NewProposalStore(newDAO) + + newDAO.SetAdmin() + + return "DAO '" + id + "' created with name: " + name +} + // func RequestDAO() string { // return ds.DAO.RequestDAO() // } diff --git a/examples/gno.land/r/moonia/home/dashboard.gno b/examples/gno.land/r/moonia/home/dashboard.gno new file mode 100644 index 00000000000..6a745eee5d8 --- /dev/null +++ b/examples/gno.land/r/moonia/home/dashboard.gno @@ -0,0 +1,37 @@ +package home + +import ( + // "gno.land/p/moonia/utils" + "gno.land/p/moul/txlink" + "gno.land/p/moul/md" +) + +func renderDashboardDesc() string { + out := "Welcome to the platform that lists all active DAOs on Moonia.\n\n" + out += "🔍 You can explore each DAO’s activities and proposals freely.\n\n" + out += "🗳️ To **participate in votes or submit proposals**, you must request to join the DAO.\n" + out += "The admin will review and accept or decline your request.\n\n" + return out +} + +func renderDashboard() string { + // caller := utils.GetCaller() + out := "# Welcome to the DAO Hub\n\n" + out += renderDashboardDesc() + + out += "## ➕ Create a new DAO:\n" + out += md.Link("Create DAO", txlink.Call("CreateDAO", "args", "id", "args", "title", "args", "description")) + "\n\n" + + out += "## 🗂 Existing DAOs:\n" + if len(daoMap) == 0 { + out += "_No DAOs created yet._\n" + } else { + for id := range daoMap { + out += "- " + md.Link("DAO: "+id, "/r/moonia/home?dao="+id) + "\n" + } + } + out += "\n## 👥 My DAOs:\n" + // TODO: display addr DAOs + out += "_You are not a member of any DAO._\n" + return out +} diff --git a/examples/gno.land/r/moonia/home/home.gno b/examples/gno.land/r/moonia/home/home.gno index dfa84a00e72..7a72f054a4c 100644 --- a/examples/gno.land/r/moonia/home/home.gno +++ b/examples/gno.land/r/moonia/home/home.gno @@ -5,9 +5,12 @@ import ( "gno.land/p/moul/md" "gno.land/p/moonia/dao" "gno.land/p/moonia/utils" + "strings" ) var ds DAOState +var daoMap map[string]*dao.DAO +var proposalMap map[string]*dao.ProposalStore type DAOState struct { DAO *dao.DAO @@ -15,13 +18,8 @@ type DAOState struct { } func init() { - myDao := dao.NewDAO() - myProps := dao.NewProposalStore(myDao) - - ds = DAOState{ - DAO: myDao, - Proposals: myProps, - } + daoMap = make(map[string]*dao.DAO) + proposalMap = make(map[string]*dao.ProposalStore) } func countActive() int { @@ -34,24 +32,66 @@ func countActive() int { return count } +func renderDAOStats() string { + out := "# Statistics for `" + ds.DAO.Name + "`\n" + totalProposals := len(ds.Proposals.Proposals) + activeProposals := countActive() + out += ds.DAO.Stats(totalProposals, activeProposals) + "\n" + + return out +} + func renderDAO() string { caller := utils.GetCaller() - out := "# Moonia's DAO\n\n" + out := "# " + ds.DAO.Name + "\n\n" + out += "_" + ds.DAO.Description + "_\n\n" out += ds.DAO.ShowAdmin() + "\n\n" - out += ds.DAO.Stats(len(ds.Proposals.Proposals), countActive()) + "\n" + out += "## Actions:\n\n" - if ds.DAO.Admin == "" { - out += "- " + md.Link("Set Admin (once)", txlink.Call("SetAdmin")) + "\n" + if !ds.DAO.IsMember(caller) && ds.DAO.Admin != "" { + out += "- " + md.Link("Request to Join DAO", txlink.Call("RequestDAO")) + "\n" } - out += "- " + md.Link("Transfer Admin", txlink.Call("TransferAdmin", "args", caller.String())) + "\n" - out += "- " + md.Link("Join DAO", txlink.Call("JoinDAO")) + "\n" out += "- " + md.Link("Leave DAO", txlink.Call("LeaveDAO")) + "\n" out += "- " + md.Link("Create Sample Proposal", txlink.Call("CreateProposalTest")) + "\n" + for id := range daoMap { + out += "- " + md.Link("View Stats", "/r/moonia/home?dao="+id+"&stats") + "\n" + } + out += "- " + md.Link("Transfer Admin", txlink.Call("TransferAdmin", "args", "addr")) + "\n" out += ds.DAO.ShowWhitelist() + "\n" out += ds.Proposals.ShowProposals() return out } func Render(path string) string { - return renderDAO() + if path == "/" || path == "" { + return renderDashboard() + } + parts := strings.Split(path, "?") + if len(parts) > 1 { + query := parts[1] + args := strings.Split(query, "&") + + var daoID string + showStats := false + + for _, arg := range args { + if strings.HasPrefix(arg, "dao=") { + daoID = arg[len("dao="):] + } else if arg == "stats" { + showStats = true + } + } + d := daoMap[daoID] + if d == nil { + return "DAO not found: `" + daoID + "`" + } + ds.DAO = d + ds.Proposals = proposalMap[daoID] + + if showStats { + return renderDAOStats() + } + return renderDAO() + } + return "Unknown path or query: `" + path + "`" } From 2b53ee6dfc3a44a203abe2c0b7efd59521f60901 Mon Sep 17 00:00:00 2001 From: moonia Date: Thu, 10 Apr 2025 17:53:20 +0200 Subject: [PATCH 09/20] fix: format when displaying existing DAOs --- examples/gno.land/r/moonia/home/dashboard.gno | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/gno.land/r/moonia/home/dashboard.gno b/examples/gno.land/r/moonia/home/dashboard.gno index 6a745eee5d8..4db50043710 100644 --- a/examples/gno.land/r/moonia/home/dashboard.gno +++ b/examples/gno.land/r/moonia/home/dashboard.gno @@ -26,9 +26,9 @@ func renderDashboard() string { if len(daoMap) == 0 { out += "_No DAOs created yet._\n" } else { - for id := range daoMap { - out += "- " + md.Link("DAO: "+id, "/r/moonia/home?dao="+id) + "\n" - } + for id, dao := range daoMap { + out += "- " + md.Link(dao.Name, "/r/moonia/home?dao="+id) + " : " + dao.Description + } } out += "\n## 👥 My DAOs:\n" // TODO: display addr DAOs From 106bc51baabd233bfdd77fad38f6f1cbcf4b913c Mon Sep 17 00:00:00 2001 From: moonia Date: Thu, 10 Apr 2025 21:31:33 +0200 Subject: [PATCH 10/20] refacto: move query parsing function to utils --- examples/gno.land/p/moonia/utils/utils.gno | 20 +++++++++++- examples/gno.land/r/moonia/home/home.gno | 37 ++++++---------------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/examples/gno.land/p/moonia/utils/utils.gno b/examples/gno.land/p/moonia/utils/utils.gno index 492df49e4ea..e65c023c43c 100644 --- a/examples/gno.land/p/moonia/utils/utils.gno +++ b/examples/gno.land/p/moonia/utils/utils.gno @@ -4,6 +4,7 @@ import ( "std" "strconv" "time" + "strings" ) func GetBlockTime() int64 { @@ -28,4 +29,21 @@ func ParseIndex(indexStr string, max int) int { panic("Proposal does not exist.") } return index -} \ No newline at end of file +} + +func ParseQuery(path string) (daoID string, showStats bool) { + parts := strings.Split(path, "?") + if len(parts) < 2 { + return "", false + } + + args := strings.Split(parts[1], "&") + for _, arg := range args { + if strings.HasPrefix(arg, "dao=") { + daoID = arg[len("dao="):] + } else if arg == "stats" { + showStats = true + } + } + return +} diff --git a/examples/gno.land/r/moonia/home/home.gno b/examples/gno.land/r/moonia/home/home.gno index 7a72f054a4c..ce0565336a5 100644 --- a/examples/gno.land/r/moonia/home/home.gno +++ b/examples/gno.land/r/moonia/home/home.gno @@ -5,7 +5,6 @@ import ( "gno.land/p/moul/md" "gno.land/p/moonia/dao" "gno.land/p/moonia/utils" - "strings" ) var ds DAOState @@ -66,32 +65,16 @@ func Render(path string) string { if path == "/" || path == "" { return renderDashboard() } - parts := strings.Split(path, "?") - if len(parts) > 1 { - query := parts[1] - args := strings.Split(query, "&") - - var daoID string - showStats := false - - for _, arg := range args { - if strings.HasPrefix(arg, "dao=") { - daoID = arg[len("dao="):] - } else if arg == "stats" { - showStats = true - } - } - d := daoMap[daoID] - if d == nil { - return "DAO not found: `" + daoID + "`" - } - ds.DAO = d - ds.Proposals = proposalMap[daoID] + daoID, showStats := utils.ParseQuery(path) + d := daoMap[daoID] + if d == nil { + return "DAO not found: `" + daoID + "`" + } + ds.DAO = d + ds.Proposals = proposalMap[daoID] - if showStats { - return renderDAOStats() - } - return renderDAO() + if showStats { + return renderDAOStats() } - return "Unknown path or query: `" + path + "`" + return renderDAO() } From f99e50a94badc2b1de94cf45a46d9c600ca03781 Mon Sep 17 00:00:00 2001 From: moonia Date: Fri, 11 Apr 2025 14:34:56 +0200 Subject: [PATCH 11/20] fix: requests methods by admin + see DAOs user --- examples/gno.land/p/moonia/dao/admin.gno | 28 +++++++++++++++++-- examples/gno.land/p/moonia/dao/dao.gno | 11 +++++--- examples/gno.land/r/moonia/home/actions.gno | 20 ++++++------- examples/gno.land/r/moonia/home/dashboard.gno | 19 +++++++++---- examples/gno.land/r/moonia/home/home.gno | 14 +++++++++- 5 files changed, 67 insertions(+), 25 deletions(-) diff --git a/examples/gno.land/p/moonia/dao/admin.gno b/examples/gno.land/p/moonia/dao/admin.gno index 0fdcd509a80..3da46fac94c 100644 --- a/examples/gno.land/p/moonia/dao/admin.gno +++ b/examples/gno.land/p/moonia/dao/admin.gno @@ -19,7 +19,6 @@ func (d *DAO) TransferAdmin(addr std.Address) string { if !d.IsMember(addr) { panic("New admin must be a member.") } - d.Admin = addr return "Admin transferred to " + addr.String() } @@ -30,8 +29,7 @@ func (d *DAO) SetAdmin() string { } caller := utils.GetCaller() d.Admin = caller - d.JoinDAO() - + d.Whitelist[caller] = true return "Admin set to: " + caller.String() } @@ -39,6 +37,30 @@ func (d *DAO) IsAdmin(addr std.Address) bool { return addr == d.Admin } +func (d *DAO) AcceptRequest(addr std.Address) string { + caller := utils.GetCaller() + if !d.IsAdmin(caller) { + panic("Only admin can accept requests.") + } + if !d.Requests[addr] { + panic("No such request.") + } + delete(d.Requests, addr) + return d.AddMember(addr) +} + +func (d *DAO) DeclineRequest(addr std.Address) string { + caller := utils.GetCaller() + if !d.IsAdmin(caller) { + panic("Only admin can decline requests.") + } + if !d.Requests[addr] { + panic("No such request.") + } + delete(d.Requests, addr) + return "Request declined for " + addr.String() +} + // Member Methods // func (d *DAO) IsMember(addr std.Address) bool { diff --git a/examples/gno.land/p/moonia/dao/dao.gno b/examples/gno.land/p/moonia/dao/dao.gno index 060657b5d92..2b2578b8243 100644 --- a/examples/gno.land/p/moonia/dao/dao.gno +++ b/examples/gno.land/p/moonia/dao/dao.gno @@ -35,13 +35,16 @@ func (d *DAO) ShowAdmin() string { return "Admin: `" + d.Admin.String() + "`" } -func (d *DAO) JoinDAO() string { +func (d *DAO) RequestDAO() string { caller := utils.GetCaller() if d.Whitelist[caller] { - panic("You're already a member of the DAO.") + return "You are already a member." } - d.Whitelist[caller] = true - return "You have successfully joined the DAO." + if d.Requests[caller] { + return "You have already requested to join." + } + d.Requests[caller] = true + return "Request to join sent." } func (d *DAO) LeaveDAO() string { diff --git a/examples/gno.land/r/moonia/home/actions.gno b/examples/gno.land/r/moonia/home/actions.gno index e8dd313c542..971ec5751f4 100644 --- a/examples/gno.land/r/moonia/home/actions.gno +++ b/examples/gno.land/r/moonia/home/actions.gno @@ -31,13 +31,13 @@ func ListMembers() map[std.Address]bool { return ds.DAO.Whitelist } -// func AcceptRequest(addr std.Address) string { -// return ds.DAO.AcceptRequest(addr) -// } +func AcceptRequest(addr std.Address) string { + return ds.DAO.AcceptRequest(addr) +} -// func DeclineRequest(addr std.Address) string { -// return ds.DAO.DeclineRequest(addr) -// } +func DeclineRequest(addr std.Address) string { + return ds.DAO.DeclineRequest(addr) +} func ListRequests() map[std.Address]bool { return ds.DAO.ListRequests() @@ -58,12 +58,8 @@ func CreateDAO(id, name, desc string) string { return "DAO '" + id + "' created with name: " + name } -// func RequestDAO() string { -// return ds.DAO.RequestDAO() -// } - -func JoinDAO() string { - return ds.DAO.JoinDAO() +func RequestDAO() string { + return ds.DAO.RequestDAO() } func LeaveDAO() string { diff --git a/examples/gno.land/r/moonia/home/dashboard.gno b/examples/gno.land/r/moonia/home/dashboard.gno index 4db50043710..0aebc3cf4a4 100644 --- a/examples/gno.land/r/moonia/home/dashboard.gno +++ b/examples/gno.land/r/moonia/home/dashboard.gno @@ -1,7 +1,7 @@ package home import ( - // "gno.land/p/moonia/utils" + "gno.land/p/moonia/utils" "gno.land/p/moul/txlink" "gno.land/p/moul/md" ) @@ -15,7 +15,7 @@ func renderDashboardDesc() string { } func renderDashboard() string { - // caller := utils.GetCaller() + caller := utils.GetCaller() out := "# Welcome to the DAO Hub\n\n" out += renderDashboardDesc() @@ -30,8 +30,17 @@ func renderDashboard() string { out += "- " + md.Link(dao.Name, "/r/moonia/home?dao="+id) + " : " + dao.Description } } - out += "\n## 👥 My DAOs:\n" - // TODO: display addr DAOs - out += "_You are not a member of any DAO._\n" + out += "\n## 👥 Your DAOs:\n" + found := false + for id, dao := range daoMap { + if dao.IsAdmin(caller) || dao.IsMember(caller) { + out += "- " + md.Link(dao.Name, "/r/moonia/home?dao="+id) + "\n" + found = true + } + } + if !found { + out += "_You are not a member of any DAO._\n" + } + return out } diff --git a/examples/gno.land/r/moonia/home/home.gno b/examples/gno.land/r/moonia/home/home.gno index ce0565336a5..7ce81fd9340 100644 --- a/examples/gno.land/r/moonia/home/home.gno +++ b/examples/gno.land/r/moonia/home/home.gno @@ -49,7 +49,7 @@ func renderDAO() string { out += "## Actions:\n\n" if !ds.DAO.IsMember(caller) && ds.DAO.Admin != "" { out += "- " + md.Link("Request to Join DAO", txlink.Call("RequestDAO")) + "\n" - } + } out += "- " + md.Link("Leave DAO", txlink.Call("LeaveDAO")) + "\n" out += "- " + md.Link("Create Sample Proposal", txlink.Call("CreateProposalTest")) + "\n" for id := range daoMap { @@ -57,6 +57,18 @@ func renderDAO() string { } out += "- " + md.Link("Transfer Admin", txlink.Call("TransferAdmin", "args", "addr")) + "\n" out += ds.DAO.ShowWhitelist() + "\n" + if ds.DAO.IsAdmin(caller) { + out += "\n## 📨 Join Requests:\n" + if len(ds.DAO.Requests) == 0 { + out += "_No pending requests._\n" + } else { + for addr := range ds.DAO.Requests { + out += "- " + addr.String() + " " + + md.Link("[Accept]", txlink.Call("AcceptRequest", "args", addr.String())) + " " + + md.Link("[Decline]", txlink.Call("DeclineRequest", "args", addr.String())) + "\n" + } + } + } out += ds.Proposals.ShowProposals() return out } From c81eec30f7c4f5cb027643d3377689b53759790c Mon Sep 17 00:00:00 2001 From: moonia Date: Fri, 11 Apr 2025 15:10:10 +0200 Subject: [PATCH 12/20] fix: CreateProposalSample function name --- examples/gno.land/p/moonia/dao/proposals.gno | 2 +- examples/gno.land/r/moonia/home/actions.gno | 4 ++-- examples/gno.land/r/moonia/home/home.gno | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/gno.land/p/moonia/dao/proposals.gno b/examples/gno.land/p/moonia/dao/proposals.gno index 54b531694ec..9fffe0502bd 100644 --- a/examples/gno.land/p/moonia/dao/proposals.gno +++ b/examples/gno.land/p/moonia/dao/proposals.gno @@ -55,7 +55,7 @@ func (ps *ProposalStore) CreateProposal(title, description string, period int64) return "Proposal created: " + title + " Voting period: " + strconv.Itoa(int(period)) } -func (ps *ProposalStore) CreateProposalTest() string { +func (ps *ProposalStore) CreateProposalSample() string { return ps.CreateProposal("Survey", "Would you like to visit Guatemala?", 30) } diff --git a/examples/gno.land/r/moonia/home/actions.gno b/examples/gno.land/r/moonia/home/actions.gno index 971ec5751f4..3cea27a6939 100644 --- a/examples/gno.land/r/moonia/home/actions.gno +++ b/examples/gno.land/r/moonia/home/actions.gno @@ -68,8 +68,8 @@ func LeaveDAO() string { // Proposals Methods // -func CreateProposalTest() string { - return ds.Proposals.CreateProposalTest() +func CreateProposalSample() string { + return ds.Proposals.CreateProposalSample() } func CreateProposal(title, description string, period int64) string { diff --git a/examples/gno.land/r/moonia/home/home.gno b/examples/gno.land/r/moonia/home/home.gno index 7ce81fd9340..272399e1d42 100644 --- a/examples/gno.land/r/moonia/home/home.gno +++ b/examples/gno.land/r/moonia/home/home.gno @@ -51,7 +51,7 @@ func renderDAO() string { out += "- " + md.Link("Request to Join DAO", txlink.Call("RequestDAO")) + "\n" } out += "- " + md.Link("Leave DAO", txlink.Call("LeaveDAO")) + "\n" - out += "- " + md.Link("Create Sample Proposal", txlink.Call("CreateProposalTest")) + "\n" + out += "- " + md.Link("Create Sample Proposal", txlink.Call("CreateProposalSample")) + "\n" for id := range daoMap { out += "- " + md.Link("View Stats", "/r/moonia/home?dao="+id+"&stats") + "\n" } From ac048f32ec0290684647321023886c3e144af3a8 Mon Sep 17 00:00:00 2001 From: moonia Date: Fri, 11 Apr 2025 15:32:57 +0200 Subject: [PATCH 13/20] fix: create proposal for DAO using its ID --- examples/gno.land/p/moonia/dao/proposals.gno | 4 ---- examples/gno.land/r/moonia/home/actions.gno | 4 ---- examples/gno.land/r/moonia/home/home.gno | 15 ++++++++++++++- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/examples/gno.land/p/moonia/dao/proposals.gno b/examples/gno.land/p/moonia/dao/proposals.gno index 9fffe0502bd..2321b57085b 100644 --- a/examples/gno.land/p/moonia/dao/proposals.gno +++ b/examples/gno.land/p/moonia/dao/proposals.gno @@ -55,10 +55,6 @@ func (ps *ProposalStore) CreateProposal(title, description string, period int64) return "Proposal created: " + title + " Voting period: " + strconv.Itoa(int(period)) } -func (ps *ProposalStore) CreateProposalSample() string { - return ps.CreateProposal("Survey", "Would you like to visit Guatemala?", 30) -} - func (ps *ProposalStore) CloseProposal(indexStr string) string { index := utils.ParseIndex(indexStr, len(ps.Proposals)) p := &ps.Proposals[index] diff --git a/examples/gno.land/r/moonia/home/actions.gno b/examples/gno.land/r/moonia/home/actions.gno index 3cea27a6939..f8f2d46ffe3 100644 --- a/examples/gno.land/r/moonia/home/actions.gno +++ b/examples/gno.land/r/moonia/home/actions.gno @@ -68,10 +68,6 @@ func LeaveDAO() string { // Proposals Methods // -func CreateProposalSample() string { - return ds.Proposals.CreateProposalSample() -} - func CreateProposal(title, description string, period int64) string { return ds.Proposals.CreateProposal(title, description, period) } diff --git a/examples/gno.land/r/moonia/home/home.gno b/examples/gno.land/r/moonia/home/home.gno index 272399e1d42..54357bea38f 100644 --- a/examples/gno.land/r/moonia/home/home.gno +++ b/examples/gno.land/r/moonia/home/home.gno @@ -40,6 +40,19 @@ func renderDAOStats() string { return out } +func CreateProposalSample(daoID string) string { + d := daoMap[daoID] + if d == nil { + panic("DAO not found: " + daoID) + } + ds.DAO = d + ds.Proposals = proposalMap[daoID] + if ds.Proposals == nil { + panic("ProposalStore not found for DAO: " + daoID) + } + return ds.Proposals.CreateProposal("Survey", "Would you like to visit Guatemala?", 30) +} + func renderDAO() string { caller := utils.GetCaller() out := "# " + ds.DAO.Name + "\n\n" @@ -51,7 +64,7 @@ func renderDAO() string { out += "- " + md.Link("Request to Join DAO", txlink.Call("RequestDAO")) + "\n" } out += "- " + md.Link("Leave DAO", txlink.Call("LeaveDAO")) + "\n" - out += "- " + md.Link("Create Sample Proposal", txlink.Call("CreateProposalSample")) + "\n" + out += "- " + md.Link("Create Sample Proposal", txlink.Call("CreateProposalSample", "args", "0")) + "\n" for id := range daoMap { out += "- " + md.Link("View Stats", "/r/moonia/home?dao="+id+"&stats") + "\n" } From 28b32a7c0cf8678a47609d9a00f4ace9112687e5 Mon Sep 17 00:00:00 2001 From: moonia Date: Mon, 14 Apr 2025 12:28:14 +0200 Subject: [PATCH 14/20] fix: show stats for dao using its id --- examples/gno.land/r/moonia/home/dashboard.gno | 2 +- examples/gno.land/r/moonia/home/home.gno | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/examples/gno.land/r/moonia/home/dashboard.gno b/examples/gno.land/r/moonia/home/dashboard.gno index 0aebc3cf4a4..bf26809bab1 100644 --- a/examples/gno.land/r/moonia/home/dashboard.gno +++ b/examples/gno.land/r/moonia/home/dashboard.gno @@ -27,7 +27,7 @@ func renderDashboard() string { out += "_No DAOs created yet._\n" } else { for id, dao := range daoMap { - out += "- " + md.Link(dao.Name, "/r/moonia/home?dao="+id) + " : " + dao.Description + out += "- " + md.Link(dao.Name, "/r/moonia/home?dao="+id) + " : " + dao.Description + "\n" } } out += "\n## 👥 Your DAOs:\n" diff --git a/examples/gno.land/r/moonia/home/home.gno b/examples/gno.land/r/moonia/home/home.gno index 54357bea38f..68d1cd3f617 100644 --- a/examples/gno.land/r/moonia/home/home.gno +++ b/examples/gno.land/r/moonia/home/home.gno @@ -53,7 +53,7 @@ func CreateProposalSample(daoID string) string { return ds.Proposals.CreateProposal("Survey", "Would you like to visit Guatemala?", 30) } -func renderDAO() string { +func renderDAO(daoID string) string { caller := utils.GetCaller() out := "# " + ds.DAO.Name + "\n\n" out += "_" + ds.DAO.Description + "_\n\n" @@ -65,9 +65,7 @@ func renderDAO() string { } out += "- " + md.Link("Leave DAO", txlink.Call("LeaveDAO")) + "\n" out += "- " + md.Link("Create Sample Proposal", txlink.Call("CreateProposalSample", "args", "0")) + "\n" - for id := range daoMap { - out += "- " + md.Link("View Stats", "/r/moonia/home?dao="+id+"&stats") + "\n" - } + out += "- " + md.Link("View Stats", "/r/moonia/home?dao="+daoID+"&stats") + "\n" out += "- " + md.Link("Transfer Admin", txlink.Call("TransferAdmin", "args", "addr")) + "\n" out += ds.DAO.ShowWhitelist() + "\n" if ds.DAO.IsAdmin(caller) { @@ -101,5 +99,5 @@ func Render(path string) string { if showStats { return renderDAOStats() } - return renderDAO() + return renderDAO(daoID) } From 0cb818548869ef59814b5498bbd38d320908b7b7 Mon Sep 17 00:00:00 2001 From: moonia Date: Mon, 14 Apr 2025 12:48:34 +0200 Subject: [PATCH 15/20] fix: nil pointer when requestDAO because no dao id specified --- examples/gno.land/r/moonia/home/actions.gno | 7 ++++++- examples/gno.land/r/moonia/home/home.gno | 11 ++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/examples/gno.land/r/moonia/home/actions.gno b/examples/gno.land/r/moonia/home/actions.gno index f8f2d46ffe3..6e83640d70c 100644 --- a/examples/gno.land/r/moonia/home/actions.gno +++ b/examples/gno.land/r/moonia/home/actions.gno @@ -58,7 +58,12 @@ func CreateDAO(id, name, desc string) string { return "DAO '" + id + "' created with name: " + name } -func RequestDAO() string { +func RequestDAO(daoID string) string { + d := daoMap[daoID] + if d == nil { + panic("DAO not found: " + daoID) + } + ds.DAO = d return ds.DAO.RequestDAO() } diff --git a/examples/gno.land/r/moonia/home/home.gno b/examples/gno.land/r/moonia/home/home.gno index 68d1cd3f617..55e8cee6aad 100644 --- a/examples/gno.land/r/moonia/home/home.gno +++ b/examples/gno.land/r/moonia/home/home.gno @@ -5,6 +5,8 @@ import ( "gno.land/p/moul/md" "gno.land/p/moonia/dao" "gno.land/p/moonia/utils" + "strconv" + "std" ) var ds DAOState @@ -54,7 +56,7 @@ func CreateProposalSample(daoID string) string { } func renderDAO(daoID string) string { - caller := utils.GetCaller() + caller := std.Address("g15dz69sch7fkhc9gk57hpe4qea77thmy20apu9x") out := "# " + ds.DAO.Name + "\n\n" out += "_" + ds.DAO.Description + "_\n\n" out += ds.DAO.ShowAdmin() + "\n\n" @@ -68,8 +70,11 @@ func renderDAO(daoID string) string { out += "- " + md.Link("View Stats", "/r/moonia/home?dao="+daoID+"&stats") + "\n" out += "- " + md.Link("Transfer Admin", txlink.Call("TransferAdmin", "args", "addr")) + "\n" out += ds.DAO.ShowWhitelist() + "\n" + out += "- Admin: `" + ds.DAO.Admin.String() + "`\n" + out += "- Caller: `" + caller.String() + "`\n" if ds.DAO.IsAdmin(caller) { out += "\n## 📨 Join Requests:\n" + out += "- Requests count: " + strconv.Itoa(len(ds.DAO.Requests)) + "\n" if len(ds.DAO.Requests) == 0 { out += "_No pending requests._\n" } else { @@ -79,12 +84,12 @@ func renderDAO(daoID string) string { md.Link("[Decline]", txlink.Call("DeclineRequest", "args", addr.String())) + "\n" } } - } + } out += ds.Proposals.ShowProposals() return out } -func Render(path string) string { +func Render(path string) string { if path == "/" || path == "" { return renderDashboard() } From 8cda97d5012ca4a87bde0cd12c9561d58e51efca Mon Sep 17 00:00:00 2001 From: moonia Date: Mon, 14 Apr 2025 12:52:18 +0200 Subject: [PATCH 16/20] fix: display request number --- examples/gno.land/r/moonia/home/home.gno | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/gno.land/r/moonia/home/home.gno b/examples/gno.land/r/moonia/home/home.gno index 55e8cee6aad..6d4f64e580b 100644 --- a/examples/gno.land/r/moonia/home/home.gno +++ b/examples/gno.land/r/moonia/home/home.gno @@ -73,8 +73,7 @@ func renderDAO(daoID string) string { out += "- Admin: `" + ds.DAO.Admin.String() + "`\n" out += "- Caller: `" + caller.String() + "`\n" if ds.DAO.IsAdmin(caller) { - out += "\n## 📨 Join Requests:\n" - out += "- Requests count: " + strconv.Itoa(len(ds.DAO.Requests)) + "\n" + out += "\n## 📨 Join Requests: " + strconv.Itoa(len(ds.DAO.Requests)) + "\n" if len(ds.DAO.Requests) == 0 { out += "_No pending requests._\n" } else { From 63eefae92e5dca46dae1b345b8db40a8a19f4ce2 Mon Sep 17 00:00:00 2001 From: moonia Date: Mon, 14 Apr 2025 13:02:45 +0200 Subject: [PATCH 17/20] fix: membership to a dao and display your DAOs --- examples/gno.land/r/moonia/home/dashboard.gno | 6 +++--- examples/gno.land/r/moonia/home/home.gno | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/examples/gno.land/r/moonia/home/dashboard.gno b/examples/gno.land/r/moonia/home/dashboard.gno index bf26809bab1..443b9c81ade 100644 --- a/examples/gno.land/r/moonia/home/dashboard.gno +++ b/examples/gno.land/r/moonia/home/dashboard.gno @@ -1,9 +1,9 @@ package home import ( - "gno.land/p/moonia/utils" "gno.land/p/moul/txlink" "gno.land/p/moul/md" + "std" ) func renderDashboardDesc() string { @@ -15,7 +15,8 @@ func renderDashboardDesc() string { } func renderDashboard() string { - caller := utils.GetCaller() + // TODO: fix empty string for caller, for the moment -> change it manually + caller := std.Address("g15dz69sch7fkhc9gk57hpe4qea77thmy20apu9x") out := "# Welcome to the DAO Hub\n\n" out += renderDashboardDesc() @@ -41,6 +42,5 @@ func renderDashboard() string { if !found { out += "_You are not a member of any DAO._\n" } - return out } diff --git a/examples/gno.land/r/moonia/home/home.gno b/examples/gno.land/r/moonia/home/home.gno index 6d4f64e580b..0fc1a0862a1 100644 --- a/examples/gno.land/r/moonia/home/home.gno +++ b/examples/gno.land/r/moonia/home/home.gno @@ -56,10 +56,13 @@ func CreateProposalSample(daoID string) string { } func renderDAO(daoID string) string { + // TODO: fix empty string for caller, for the moment -> change it manually caller := std.Address("g15dz69sch7fkhc9gk57hpe4qea77thmy20apu9x") + trueCaller := utils.GetCaller() // result: empty string out := "# " + ds.DAO.Name + "\n\n" out += "_" + ds.DAO.Description + "_\n\n" out += ds.DAO.ShowAdmin() + "\n\n" + out += "Caller (debug): `" + trueCaller.String() + "`\n" out += "## Actions:\n\n" if !ds.DAO.IsMember(caller) && ds.DAO.Admin != "" { @@ -70,8 +73,6 @@ func renderDAO(daoID string) string { out += "- " + md.Link("View Stats", "/r/moonia/home?dao="+daoID+"&stats") + "\n" out += "- " + md.Link("Transfer Admin", txlink.Call("TransferAdmin", "args", "addr")) + "\n" out += ds.DAO.ShowWhitelist() + "\n" - out += "- Admin: `" + ds.DAO.Admin.String() + "`\n" - out += "- Caller: `" + caller.String() + "`\n" if ds.DAO.IsAdmin(caller) { out += "\n## 📨 Join Requests: " + strconv.Itoa(len(ds.DAO.Requests)) + "\n" if len(ds.DAO.Requests) == 0 { From 14f2002da4329cd1c9497e95c157c1b972cfa891 Mon Sep 17 00:00:00 2001 From: moonia Date: Mon, 14 Apr 2025 13:18:43 +0200 Subject: [PATCH 18/20] refacto: rm non-used interfaces --- examples/gno.land/p/moonia/dao/interfaces.gno | 26 ------------------- 1 file changed, 26 deletions(-) delete mode 100644 examples/gno.land/p/moonia/dao/interfaces.gno diff --git a/examples/gno.land/p/moonia/dao/interfaces.gno b/examples/gno.land/p/moonia/dao/interfaces.gno deleted file mode 100644 index b86c850363f..00000000000 --- a/examples/gno.land/p/moonia/dao/interfaces.gno +++ /dev/null @@ -1,26 +0,0 @@ -package dao - -import "std" - -type IAdmin interface { - SetAdmin() string - TransferAdmin(addr string) string - KickMember(addr std.Address) string - AddMember(addr std.Address) string -} - -type IDao interface { - JoinDAO() string - LeaveDAO() string - ShowWhitelist() string - Stats(totalProposals, activeProposals int) string -} - -type IProposal interface { - CreateProposal(title, description string) string - CloseProposal(indexStr string) string - Vote(indexStr, voteYesStr string) string - EditProposal(indexStr, newTitle, newDescription string, newPeriod int64) string - GetProposal(indexStr string) Proposal - ShowProposals() string -} From b610fc69a819f39ef34d81a42c39af0c0247033a Mon Sep 17 00:00:00 2001 From: moonia Date: Mon, 14 Apr 2025 21:01:54 +0200 Subject: [PATCH 19/20] refacto: rm GetCaller useless encapsulation --- examples/gno.land/p/moonia/dao/admin.gno | 15 +++++++-------- examples/gno.land/p/moonia/dao/dao.gno | 5 ++--- examples/gno.land/p/moonia/dao/proposals.gno | 9 +++++---- examples/gno.land/p/moonia/utils/utils.gno | 4 ---- examples/gno.land/r/moonia/home/actions.gno | 5 +++-- examples/gno.land/r/moonia/home/dashboard.gno | 5 +++-- examples/gno.land/r/moonia/home/home.gno | 9 +++++---- 7 files changed, 25 insertions(+), 27 deletions(-) diff --git a/examples/gno.land/p/moonia/dao/admin.gno b/examples/gno.land/p/moonia/dao/admin.gno index 3da46fac94c..769ed71d218 100644 --- a/examples/gno.land/p/moonia/dao/admin.gno +++ b/examples/gno.land/p/moonia/dao/admin.gno @@ -1,20 +1,19 @@ package dao import ( - "gno.land/p/moonia/utils" "std" ) // Admin Methods // func (d *DAO) TransferAdmin(addr std.Address) string { - caller := utils.GetCaller() + caller := std.PreviousRealm().Address() if !d.IsAdmin(caller) { panic("Only the admin can transfer admin.") } if d.IsAdmin(addr) { - return "Address is already the admin." + panic("Address is already the admin.") } if !d.IsMember(addr) { panic("New admin must be a member.") @@ -27,7 +26,7 @@ func (d *DAO) SetAdmin() string { if d.Admin != "" { panic("Admin is already set.") } - caller := utils.GetCaller() + caller := std.PreviousRealm().Address() d.Admin = caller d.Whitelist[caller] = true return "Admin set to: " + caller.String() @@ -38,7 +37,7 @@ func (d *DAO) IsAdmin(addr std.Address) bool { } func (d *DAO) AcceptRequest(addr std.Address) string { - caller := utils.GetCaller() + caller := std.PreviousRealm().Address() if !d.IsAdmin(caller) { panic("Only admin can accept requests.") } @@ -50,7 +49,7 @@ func (d *DAO) AcceptRequest(addr std.Address) string { } func (d *DAO) DeclineRequest(addr std.Address) string { - caller := utils.GetCaller() + caller := std.PreviousRealm().Address() if !d.IsAdmin(caller) { panic("Only admin can decline requests.") } @@ -68,7 +67,7 @@ func (d *DAO) IsMember(addr std.Address) bool { } func (d *DAO) AddMember(addr std.Address) string { - caller := utils.GetCaller() + caller := std.PreviousRealm().Address() if !d.IsAdmin(caller) { panic("Only the admin can add members.") @@ -81,7 +80,7 @@ func (d *DAO) AddMember(addr std.Address) string { } func (d *DAO) KickMember(addr std.Address) string { - caller := utils.GetCaller() + caller := std.PreviousRealm().Address() if !d.IsAdmin(caller) { panic("Only the admin can kick members.") diff --git a/examples/gno.land/p/moonia/dao/dao.gno b/examples/gno.land/p/moonia/dao/dao.gno index 2b2578b8243..a5ac366df21 100644 --- a/examples/gno.land/p/moonia/dao/dao.gno +++ b/examples/gno.land/p/moonia/dao/dao.gno @@ -3,7 +3,6 @@ package dao import ( "std" "strconv" - "gno.land/p/moonia/utils" ) type DAO struct { @@ -36,7 +35,7 @@ func (d *DAO) ShowAdmin() string { } func (d *DAO) RequestDAO() string { - caller := utils.GetCaller() + caller := std.PreviousRealm().Address() if d.Whitelist[caller] { return "You are already a member." } @@ -48,7 +47,7 @@ func (d *DAO) RequestDAO() string { } func (d *DAO) LeaveDAO() string { - caller := utils.GetCaller() + caller := std.PreviousRealm().Address() if !d.Whitelist[caller] { panic("You are not a member of the DAO.") } diff --git a/examples/gno.land/p/moonia/dao/proposals.gno b/examples/gno.land/p/moonia/dao/proposals.gno index 2321b57085b..4d8aca8f373 100644 --- a/examples/gno.land/p/moonia/dao/proposals.gno +++ b/examples/gno.land/p/moonia/dao/proposals.gno @@ -3,6 +3,7 @@ package dao import ( "std" "strconv" + "gno.land/p/moonia/utils" "gno.land/p/moul/txlink" "gno.land/p/moul/md" @@ -34,7 +35,7 @@ func NewProposalStore(dao *DAO) *ProposalStore { } func (ps *ProposalStore) CreateProposal(title, description string, period int64) string { - caller := utils.GetCaller() + caller := std.PreviousRealm().Address() if !ps.DAO.Whitelist[caller] { panic("Only whitelisted members can create proposals.") } @@ -58,7 +59,7 @@ func (ps *ProposalStore) CreateProposal(title, description string, period int64) func (ps *ProposalStore) CloseProposal(indexStr string) string { index := utils.ParseIndex(indexStr, len(ps.Proposals)) p := &ps.Proposals[index] - if p.Creator != utils.GetCaller() { + if p.Creator != std.PreviousRealm().Address() { panic("Only the proposal creator can close it.") } if !p.Active { @@ -71,7 +72,7 @@ func (ps *ProposalStore) CloseProposal(indexStr string) string { func (ps *ProposalStore) Vote(indexStr, voteYesStr string) string { index := utils.ParseIndex(indexStr, len(ps.Proposals)) voteYes := voteYesStr == "true" - caller := utils.GetCaller() + caller := std.PreviousRealm().Address() if !ps.DAO.Whitelist[caller] { panic("Only whitelisted members can vote.") @@ -140,7 +141,7 @@ func (ps *ProposalStore) ShowProposals() string { func (ps *ProposalStore) EditProposal(indexStr, newTitle, newDescription string, newPeriod int64) string { index := utils.ParseIndex(indexStr, len(ps.Proposals)) p := &ps.Proposals[index] - if p.Creator != utils.GetCaller() { + if p.Creator != std.PreviousRealm().Address() { panic("Only the creator can edit the proposal.") } if !p.Active { diff --git a/examples/gno.land/p/moonia/utils/utils.gno b/examples/gno.land/p/moonia/utils/utils.gno index e65c023c43c..d6f497963b8 100644 --- a/examples/gno.land/p/moonia/utils/utils.gno +++ b/examples/gno.land/p/moonia/utils/utils.gno @@ -16,10 +16,6 @@ func FormatTimestamp(timestamp int64) string { return t.Format("02 Jan 2006, 15:04") } -func GetCaller() std.Address { - return std.PreviousRealm().Address() -} - func ParseIndex(indexStr string, max int) int { index, err := strconv.Atoi(indexStr) if err != nil { diff --git a/examples/gno.land/r/moonia/home/actions.gno b/examples/gno.land/r/moonia/home/actions.gno index 6e83640d70c..77d4a7c9cb8 100644 --- a/examples/gno.land/r/moonia/home/actions.gno +++ b/examples/gno.land/r/moonia/home/actions.gno @@ -1,8 +1,9 @@ package home import ( - "gno.land/p/moonia/dao" "std" + + "gno.land/p/moonia/dao" ) // Admin Methods // @@ -47,7 +48,7 @@ func ListRequests() map[std.Address]bool { func CreateDAO(id, name, desc string) string { if daoMap[id] != nil { - return "DAO with this ID already exists." + panic("DAO with this ID already exists.") } newDAO := dao.NewDAO(name, desc) daoMap[id] = newDAO diff --git a/examples/gno.land/r/moonia/home/dashboard.gno b/examples/gno.land/r/moonia/home/dashboard.gno index 443b9c81ade..8658a693bca 100644 --- a/examples/gno.land/r/moonia/home/dashboard.gno +++ b/examples/gno.land/r/moonia/home/dashboard.gno @@ -1,9 +1,10 @@ package home import ( - "gno.land/p/moul/txlink" - "gno.land/p/moul/md" "std" + + "gno.land/p/moul/md" + "gno.land/p/moul/txlink" ) func renderDashboardDesc() string { diff --git a/examples/gno.land/r/moonia/home/home.gno b/examples/gno.land/r/moonia/home/home.gno index 0fc1a0862a1..0dfde72290d 100644 --- a/examples/gno.land/r/moonia/home/home.gno +++ b/examples/gno.land/r/moonia/home/home.gno @@ -1,12 +1,13 @@ package home import ( + "std" + "strconv" + "gno.land/p/moul/txlink" "gno.land/p/moul/md" "gno.land/p/moonia/dao" "gno.land/p/moonia/utils" - "strconv" - "std" ) var ds DAOState @@ -58,7 +59,7 @@ func CreateProposalSample(daoID string) string { func renderDAO(daoID string) string { // TODO: fix empty string for caller, for the moment -> change it manually caller := std.Address("g15dz69sch7fkhc9gk57hpe4qea77thmy20apu9x") - trueCaller := utils.GetCaller() // result: empty string + trueCaller := std.PreviousRealm().Address() // result: empty string out := "# " + ds.DAO.Name + "\n\n" out += "_" + ds.DAO.Description + "_\n\n" out += ds.DAO.ShowAdmin() + "\n\n" @@ -90,7 +91,7 @@ func renderDAO(daoID string) string { } func Render(path string) string { - if path == "/" || path == "" { + if path == "" { return renderDashboard() } daoID, showStats := utils.ParseQuery(path) From c363dafba1237a2a6d9bae411fa242e7e91bb9bc Mon Sep 17 00:00:00 2001 From: moonia Date: Mon, 14 Apr 2025 22:59:30 +0200 Subject: [PATCH 20/20] fix: unused import of std --- examples/gno.land/p/moonia/utils/utils.gno | 1 - examples/gno.land/r/moonia/home/home.gno | 8 +++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/examples/gno.land/p/moonia/utils/utils.gno b/examples/gno.land/p/moonia/utils/utils.gno index d6f497963b8..9620d0f8d21 100644 --- a/examples/gno.land/p/moonia/utils/utils.gno +++ b/examples/gno.land/p/moonia/utils/utils.gno @@ -1,7 +1,6 @@ package utils import ( - "std" "strconv" "time" "strings" diff --git a/examples/gno.land/r/moonia/home/home.gno b/examples/gno.land/r/moonia/home/home.gno index 0dfde72290d..cac44c9a800 100644 --- a/examples/gno.land/r/moonia/home/home.gno +++ b/examples/gno.land/r/moonia/home/home.gno @@ -10,9 +10,11 @@ import ( "gno.land/p/moonia/utils" ) -var ds DAOState -var daoMap map[string]*dao.DAO -var proposalMap map[string]*dao.ProposalStore +var ( + ds DAOState + daoMap map[string]*dao.DAO + proposalMap map[string]*dao.ProposalStore +) type DAOState struct { DAO *dao.DAO