README.md
basedao: Membership and Role Management for DAOs
basedao is a gnolang package that extends the DAOkit framework with comprehensive membership and role management capabilities. It serves as the foundation for most DAO implementation.
1. Core Components
Provides three main types for managing DAO membership and roles:
1// DAO member with assigned roles
2type Member struct {
3 Address string
4 Roles []string
5}
6
7// Contains metadata about DAO roles
8type RoleInfo struct {
9 Name string
10 Description string
11 Color string
12}
13
14// Central component for managing members and roles
15type MembersStore struct {
16 Roles *avl.Tree
17 Members *avl.Tree
18}
MembersStore Usage
Creating and initializing:
1// Create with initial data
2store := basedao.NewMembersStore(
3 []basedao.RoleInfo{
4 {Name: "admin", Description: "Administrators", Color: "#329175"},
5 {Name: "treasurer", Description: "Treasury management", Color: "#F3D3BC"},
6 },
7 []basedao.Member{
8 {Address: "g1admin...", Roles: []string{"admin"}},
9 {Address: "g1user...", Roles: []string{}},
10 },
11)
12
13// Create empty store for dynamic management
14store := basedao.NewMembersStore(nil, nil)
Common operations:
1// Member operations
2store.AddMember("g1new...", []string{"moderator"})
3store.RemoveMember("g1former...")
4isMember := store.IsMember("g1user...")
5count := store.MembersCount()
6
7// Role operations
8store.AddRole(basedao.RoleInfo{Name: "secretary", Description: "Records keeper", Color: "#4A90E2"})
9store.RemoveRole("obsolete-role")
10store.AddRoleToMember("g1user...", "secretary")
11store.RemoveRoleFromMember("g1user...", "moderator")
12
13// Query operations
14hasRole := store.HasRole("g1user...", "admin")
15memberRoles := store.GetMemberRoles("g1user...")
16roleMembers := store.GetMembersWithRole("admin")
17adminCount := store.CountMembersWithRole("admin")
18membersWithoutRoles := store.GetMembersWithoutRole()
19roleInfo := store.RoleInfo("admin") // Get role metadata
20membersJSON := store.GetMembersJSON() // Export as JSON
2. Built-in Actions
Provides built-in actions for common DAO operations. Each action has a unique type identifier:
Action Type Constants
1const ActionAddMemberKind = "gno.land/p/samcrew/basedao.AddMember"
2const ActionRemoveMemberKind = "gno.land/p/samcrew/basedao.RemoveMember"
3const ActionAssignRoleKind = "gno.land/p/samcrew/basedao.AssignRole"
4const ActionUnassignRoleKind = "gno.land/p/samcrew/basedao.UnassignRole"
5const ActionEditProfileKind = "gno.land/p/samcrew/basedao.EditProfile"
6const ActionChangeDAOImplementationKind = "gno.land/p/samcrew/basedao.ChangeDAOImplementation"
Creating Actions
1// Add a member with roles
2action := basedao.NewAddMemberAction(&basedao.ActionAddMember{
3 Address: address("g1newmember..."),
4 Roles: []string{"moderator", "treasurer"},
5})
6
7// Remove member
8action := basedao.NewRemoveMemberAction(address("g1member..."))
9
10// Assign role to member
11action := basedao.NewAssignRoleAction(&basedao.ActionAssignRole{
12 Address: address("g1member..."),
13 Role: "admin",
14})
15
16// Remove role from member
17action := basedao.NewUnassignRoleAction(&basedao.ActionUnassignRole{
18 Address: address("g1member..."),
19 Role: "moderator",
20})
21
22// Edit DAO profile
23action := basedao.NewEditProfileAction(
24 [2]string{"DisplayName", "My Updated DAO Name"},
25 [2]string{"Bio", "An improved description of our DAO"},
26 [2]string{"Avatar", "https://example.com/new-logo.png"},
27)
3. Core Governance Interface
Allow DAO's members to interact through proposals and voting.
Creating Proposals
1// Create a new proposal for members to vote on
2// Returns its proposal ID
3func Propose(req daokit.ProposalRequest) uint64 {...}
4
5type ProposalRequest struct {
6 Title string
7 Description string
8 Action Action
9}
Example - Adding a new member:
1addMemberAction := basedao.NewAddMemberAction(&basedao.ActionAddMember{
2 Address: "g1alice...",
3 Roles: []string{"treasurer"},
4})
5
6proposal := daokit.ProposalRequest{
7 Title: "Add new treasurer",
8 Description: "Proposal to add Alice as treasurer for better fund management",
9 Action: addMemberAction,
10}
11proposalID := Propose(proposal)
Voting on Proposals
1// Cast your vote on an active proposal
2// Available vote: VoteYes, VoteNo, VoteAbstain
3func Vote(proposalID uint64, vote daocond.Vote) {...}
4
5Vote(1, daocond.VoteYes) // Vote yes on proposal #1
6Vote(2, daocond.VoteNo) // Vote no on proposal #2
7Vote(3, daocond.VoteAbstain) // Vote abstain on proposal #3
Executing Proposals
1// Execute a proposal that has passed its voting requirements.
2// cur is threaded down to the action handler so it can perform cross-realm calls.
3func Execute(cur realm, proposalID uint64) {...}
4
5Execute(cross(cur), 1) // Execute proposal #1 -- only works if it has enough votes
Instant Execution
Skip the voting process and execute a proposal immediately if you have the required permissions:
1// This performs: Propose() -> Vote(VoteYes) -> Execute()
2proposalID := daokit.InstantExecute(DAO, proposal, cur)
Useful for admin actions, migrations, and emergency procedures.
Rendering DAO Information
Built-in render paths:
/- Main DAO overview with basic info/members- Member list with roles and permissions/proposals- Proposal list with their status and voting progress/proposals/{id}- Detailed view of a proposal with vote breakdown/config- DAO configuration and governance rules/roles- Available roles and their descriptions
You can add or overwrite renders by providing a custom RenderFn in the DAO configuration.
4. Creating a DAO with basedao
4.1 Basic DAO Setup
1package my_dao
2
3import (
4 "gno.land/p/samcrew/basedao"
5 "gno.land/p/samcrew/daocond"
6 "gno.land/p/samcrew/daokit"
7)
8
9var (
10 localDAO daokit.DAO // Local interface for DAO interaction
11 daoPrivate *basedao.DAOPrivate // Full access to internal DAO state
12)
13
14func init(cur realm) {
15 // Set up roles
16 roles := []basedao.RoleInfo{
17 {Name: "admin", Description: "Administrators", Color: "#329175"},
18 {Name: "member", Description: "Regular members", Color: "#21577A"},
19 }
20
21 // Add initial members
22 members := []basedao.Member{
23 {Address: "g1admin...", Roles: []string{"admin"}},
24 {Address: "g1user1...", Roles: []string{"member"}},
25 {Address: "g1user2...", Roles: []string{"member"}},
26 }
27
28 store := basedao.NewMembersStore(roles, members)
29
30 // Require 60% of members to approve proposals
31 condition := daocond.MembersThreshold(0.6, store.IsMember, store.MembersCount)
32
33 // Create the DAO (cur is threaded so the DAO can perform cross-realm calls)
34 localDAO, daoPrivate = basedao.New(&basedao.Config{
35 Name: "My DAO",
36 Description: "A simple DAO example",
37 Members: store,
38 InitialCondition: condition,
39 }, cur)
40}
41
42// Create a new Proposal to be voted on
43// To execute this function, you must use a MsgRun (maketx run)
44// See why it is necessary in Gno Documentation: https://docs.gno.land/users/interact-with-gnokey#run
45func Propose(cur realm, req daokit.ProposalRequest) {
46 localDAO.Propose(req)
47}
48
49// Allows DAO members to cast their vote on a specific proposal
50func Vote(cur realm, proposalID uint64, vote daocond.Vote) {
51 localDAO.Vote(proposalID, vote)
52}
53
54// Triggers the implementation of a proposal's actions
55func Execute(cur realm, proposalID uint64) {
56 localDAO.Execute(proposalID, cur)
57}
58
59// Render generates a UI representation of the DAO's state
60func Render(path string) string {
61 return localDAO.Render(path)
62}
4.2 Configuration Options
1type Config struct {
2 // Basic DAO information
3 Name string
4 Description string
5 ImageURI string
6
7 // Core components
8 Members *MembersStore
9
10 // Feature toggles
11 NoDefaultHandlers bool // Skips registration of default management actions (add/remove members, etc.)
12 NoDefaultRendering bool // Skips setup of default web UI rendering routes
13 NoCreationEvent bool // Skips emitting the DAO creation event
14
15 // Governance configuration
16 InitialCondition daocond.Condition // Default condition for all built-in actions, defaults to 60% member majority
17
18 // Profile integration (optional)
19 SetProfileString ProfileStringSetter // Function to update profile fields (DisplayName, Bio, Avatar)
20 GetProfileString ProfileStringGetter // Function to retrieve profile fields for members
21
22 // Advanced customization hooks
23 SetImplemFn SetImplemRaw // Function called when DAO implementation changes via governance
24 MigrationParamsFn MigrationParamsFn // Function providing parameters for DAO upgrades
25 RenderFn RenderFn // Rendering function for Gnoweb
26 CallerID CallerIDFn // Custom function to identify the current caller, defaults to realmid.Previous
27
28 // Internal configuration
29 PrivateVarName string // Name of the private DAO variable for member querying extensions
30}
5. DAO Upgrades and Migration
Supports upgrading DAO implementations through governance proposals, allowing DAOs to evolve over time.
5.1 Configuration for Upgrades
1DAO, daoPrivate = basedao.New(&basedao.Config{
2 // ... other config
3 MigrationParamsFn: func() []any { return nil }, // Parameters passed to migration function
4
5 SetImplemFn: setImplem,
6}, cur)
7
8// Update DAO variable after migration
9func setImplem(newDAO daokit.DAO) {
10 DAO = newDAO
11}
5.2 Migration Process
1// Migration function signature (rlm is threaded so the migration can create a new DAO)
2type MigrateFn = func(prev *DAOPrivate, params []any, rlm realm) daokit.DAO
3
4// Parameters function signature
5type MigrationParamsFn = func() []any
6
7// 1. Define migration function
8// params contains data from MigrationParamsFn - use for config, settings, etc.
9func migrateTo2_0(prev *basedao.DAOPrivate, params []any, rlm realm) daokit.DAO {
10 // Preserve existing member store
11 memberStore := prev.Members
12
13 // Add new roles for v2.0
14 memberStore.AddRole(basedao.RoleInfo{
15 Name: "auditor",
16 Description: "Financial oversight",
17 })
18
19 // Create new DAO with enhanced features
20 newLocalDAO, newPrivate := basedao.New(&basedao.Config{
21 Name: prev.InitialConfig.Name + " v2.0",
22 Description: "Upgraded DAO with audit capabilities",
23 Members: memberStore,
24 InitialCondition: prev.InitialConfig.InitialCondition,
25 // ... other configuration
26 }, rlm)
27
28 return newLocalDAO
29}
30
31// 2. Create and submit upgrade proposal
32action := basedao.NewChangeDAOImplementationAction(migrateTo2_0)
33proposal := daokit.ProposalRequest{
34 Title: "Upgrade to DAO v2.0",
35 Description: "Adds auditor role and enhanced governance",
36 Action: action,
37}
38proposalID := DAO.Propose(proposal)
39
40// 3. Execute Migration
41DAO.Execute(proposalID, cur)
42
43// Alternatively, you can use InstantExecute to skip the voting process
44// if you have sufficient permissions to execute the action directly
45daokit.InstantExecute(DAO, proposal, cur)
6. Event System
Events are emitted when actions happen in your DAO. This helps track activities.
1// DAO creation (only if NoCreationEvent is false)
2chain.Emit("BaseDAOCreated")
3
4// Member management
5chain.Emit("BaseDAOAddMember", "address", memberAddr)
6chain.Emit("BaseDAORemoveMember", "address", memberAddr)
7. Membership Extension
Allows other packages and realms to check if an address is a member of your DAO.
Usage
1import "gno.land/p/samcrew/basedao"
2
3// Check if someone is a DAO member
4ext := basedao.MustGetMembersViewExtension(localDAO)
5if ext.IsMember("g1user...") {
6 // User is a member
7}
Example: Member-Only action
The DAO value is kept unexported in the DAO realm, so expose a read-only membership check for other realms:
1package my_dao
2
3// IsMember reports whether addr is a member of this DAO.
4func IsMember(addr string) bool {
5 return basedao.MustGetMembersViewExtension(localDAO).IsMember(addr)
6}
Other realms can then gate actions on membership:
1package my_content
2
3import (
4 "chain/runtime/unsafe"
5
6 "gno.land/r/some/dao"
7)
8
9func Post(title, content string) {
10 caller := unsafe.PreviousRealm().Address()
11
12 if !dao.IsMember(caller.String()) {
13 panic("Only DAO members can post")
14 }
15
16 createPost(title, content)
17}
The extension is automatically registered when you create a DAO with basedao.New().
Part of the daokit framework for building decentralized autonomous organizations in gnolang.