Search Apps Documentation Source Content File Folder Download Copy Actions Download

/p/samcrew/basedao

Directory · 18 Files
README.md Open

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.