/p/samcrew/daokit
daokit
1. Introduction
A Decentralized Autonomous Organization (DAO) is a self-governing entity that operates through smart contracts, enabling transparent decision-making without centralized control.
daokit
is a gnolang package for creating complex DAO models. It introduces a new framework based on conditions, composed of :
daokit
: Core package for building DAOs, proposals, and actionsbasedao
: Extension with membership and role managementdaocond
: Stateless condition engine for evaluating proposals
2. What is daokit
?
daokit
provides a powerful condition and role-based system to build flexible and programmable DAOs.
Key Features:
- Create proposals that include complex execution logic
- Attach rules (conditions) to each resource
- Assign roles to users to structure permissions and governance
2.1 Key Concepts
- Proposal: A request to execute a resource. Proposals are voted on and executed only if predefined conditions are met.
- Resource: An executable action within the DAO. Each resource is governed by a condition.
- Condition: A set of rules that determine whether a proposal can be executed.
- Role: Labels that assign governance power or permissions to DAO members.
Example Use Case: A DAO wants to create a proposal to spend money from its treasury.
Rules:
SpendMoney
is a resource with a condition requiring:- 50% approval from the administration board
- Approval from the CFO
Outcome:
- Any user can propose to spend money
- Only board and CFO votes are considered
- The proposal executes only if the condition is satisfied
3. Architecture
DAOkit framework is composed of three packages:
3.1 daocond
daocond
provides a stateless condition engine used to evaluate if a proposal should be executed.
3.1.1 Interface
1type Condition interface {
2 // Eval checks if the condition is satisfied based on current votes.
3 Eval(ballot Ballot) bool
4 // Signal returns a value from 0.0 to 1.0 to indicate how close the condition is to being met.
5 Signal(ballot Ballot) float64
6
7 // Render returns a static human-readable representation of the condition.
8 Render() string
9 // RenderWithVotes returns a dynamic representation with vote context included.
10 RenderWithVotes(ballot Ballot) string
11}
12
13type Ballot interface {
14 // Vote allows a user to vote on a proposal.
15 Vote(voter string, vote Vote)
16 // Get returns the vote of a user.
17 Get(voter string) Vote
18 // Total returns the total number of votes.
19 Total() int
20 // Iterate iterates over all votes, similar as avl.Tree.Iterate.
21 Iterate(fn func(voter string, vote Vote) bool)
22}
3.1.2 Built-in Conditions
daocond
provides several built-in conditions to cover common governance scenarios.
1// MembersThreshold requires that a specified fraction of all DAO members approve the proposal.
2func MembersThreshold(threshold float64, isMemberFn func(memberId string) bool, membersCountFn func() uint64) Condition
3
4// RoleThreshold requires that a certain percentage of members holding a specific role approve.
5func RoleThreshold(threshold float64, role string, hasRoleFn func(memberId string, role string) bool, usersRoleCountFn func(role string) uint32) Condition
6
7// RoleCount requires a fixed minimum number of members holding a specific role to approve.
8func RoleCount(count uint64, role string, hasRoleFn func(memberId string, role string) bool) Condition
3.1.3 Logical Composition
You can combine multiple conditions to create complex governance rules using logical operators:
1// And returns a condition that is satisfied only if *all* provided conditions are met.
2func And(conditions ...Condition) Condition
3// Or returns a condition that is satisfied if *any* one of the provided conditions is met.
4func Or(conditions ...Condition) Condition
Example:
1// Require both admin approval and at least one CFO
2cond := daocond.And(
3 daocond.RoleThreshold(0.5, "admin", hasRole, roleCount),
4 daocond.RoleCount(1, "CFO", hasRole),
5)
Conditions are stateless for flexibility and scalability.
3.2 daokit
daokit
provides the core mechanics:
3.2.1 Core Structure:
It's the central component of a DAO, responsible for managing both available resources that can be executed and the proposals.
1type Core struct {
2 Resources *ResourcesStore
3 Proposals *ProposalsStore
4}
3.2.2 DAO Interface:
The interface defines the external functions that users or other modules interact with. It abstracts the core governance flow: proposing, voting, and executing.
1type DAO interface {
2 Propose(req ProposalRequest) uint64
3 Vote(id uint64, vote daocond.Vote)
4 Execute(id uint64)
5}
3.2.3 Proposal Lifecycle
Each proposal goes through the following states:
- Open:
- Initial state after proposal creation.
- Accepts votes from eligible participants.
- Passed
- Proposal has gathered enough valid votes to meet the condition.
- Voting is closed and cannot be modified.
- The proposal is now eligible for execution.
- Executed
- Proposal action has been successfully carried out.
- Final state β proposal can no longer be voted on or modified.
3.3 basedao
basedao
extends daokit
to handle members and roles management.
It handles who can participate in a DAO and what permissions they have.
3.3.1 Core Types
1type MembersStore struct {
2 Roles *avl.Tree
3 Members *avl.Tree
4}
3.3.2 Initialize the DAO
Create a MembersStore
structure to initialize the DAO with predefined roles and members.
1roles := []basedao.RoleInfo{
2 {Name: "admin", Description: "Administrators"},
3 {Name: "finance", Description: "Handles treasury"},
4}
5
6members := []basedao.Member{
7 {Address: "g1abc...", Roles: []string{"admin"}},
8 {Address: "g1xyz...", Roles: []string{"finance"}},
9}
10
11store := basedao.NewMembersStore(roles, members)
3.3.3 Example Usage
1store := basedao.NewMembersStore(nil, nil)
2
3// Add a role and assign it
4store.AddRole(basedao.RoleInfo{Name: "moderator", Description: "Can moderate posts"})
5store.AddMember("g1alice...", []string{"moderator"})
6
7// Update role assignment
8store.AddRoleToMember("g1alice...", "editor")
9store.RemoveRoleFromMember("g1alice...", "moderator")
10
11// Inspect the state
12isMember := store.IsMember("g1alice...") // "Is Alice a member?"
13hasRole := store.HasRole("g1alice...", "editor") // "Is Alice an editor?"
14members := store.GetMembersJSON() // "All Members (JSON):"
3.3.4 Creating a DAO:
1func New(conf *Config) (daokit.DAO, *DAOPrivate)
3.3.4.1 Key Structures:
DAOPrivate
: Full access to internal DAO statedaokit.DAO
: External interface for DAO interaction
3.3.5 Configuration:
1type Config struct {
2 Name string
3 Description string
4 ImageURI string
5 // Use `basedao.NewMembersStore(...)` to create members and roles.
6 Members *MembersStore
7 // Set to `true` to disable built-in actions like add/remove member.
8 NoDefaultHandlers bool
9 // Default rule applied to all built-in DAO actions.
10 InitialCondition daocond.Condition
11 // Optional helpers to store profile data (e.g., from `/r/demo/profile`).
12 SetProfileString ProfileStringSetter
13 GetProfileString ProfileStringGetter
14 // Set to `true` if you donβt want a "DAO Created" event to be emitted.
15 NoCreationEvent bool
16}
4. Code Example of a Basic DAO
1package daokit_demo
2
3import (
4 "gno.land/p/samcrew/basedao"
5 "gno.land/p/samcrew/daocond"
6 "gno.land/p/samcrew/daokit"
7 "gno.land/r/demo/profile"
8)
9
10var (
11 DAO daokit.DAO // External interface for DAO interaction
12 daoPrivate *basedao.DAOPrivate // Full access to internal DAO state
13)
14
15func init() {
16 initialRoles := []basedao.RoleInfo{
17 {Name: "admin", Description: "Admin is the superuser"},
18 {Name: "public-relationships", Description: "Responsible of communication with the public"},
19 {Name: "finance-officer", Description: "Responsible of funds management"},
20 }
21
22 initialMembers := []basedao.Member{
23 {Address: "g126...zlg", Roles: []string{"admin", "public-relationships"}},
24 {Address: "g1ld6...3jv", Roles: []string{"public-relationships"}},
25 {Address: "g1r69...0tth", Roles: []string{"finance-officer"}},
26 {Address: "g16jv...6e0r", Roles: []string{}},
27 }
28
29 memberStore := basedao.NewMembersStore(initialRoles, initialMembers)
30
31 membersMajority := daocond.MembersThreshold(0.6, memberStore.IsMember, memberStore.MembersCount)
32 publicRelationships := daocond.RoleCount(1, "public-relationships", memberStore.HasRole)
33 financeOfficer := daocond.RoleCount(1, "finance-officer", memberStore.HasRole)
34
35 // `and` and `or` use va_args so you can pass as many conditions as needed
36 adminCond := daocond.And(membersMajority, publicRelationships, financeOfficer)
37
38 DAO, daoPrivate = basedao.New(&basedao.Config{
39 Name: "Demo DAOKIT DAO",
40 Description: "This is a demo DAO built with DAOKIT",
41 Members: memberStore,
42 InitialCondition: adminCond,
43 GetProfileString: profile.GetStringField,
44 SetProfileString: profile.SetStringField,
45 })
46}
47
48func Vote(proposalID uint64, vote daocond.Vote) {
49 DAO.Vote(proposalID, vote)
50}
51
52func Execute(proposalID uint64) {
53 DAO.Execute(proposalID)
54}
55
56func Render(path string) string {
57 return daoPrivate.Render(path)
58}
5. Create Custom Resources
To add new behavior to your DAO β or to enable others to integrate your package into their own DAOs β define custom resources by implementing:
1type Action interface {
2 Type() string // return the type of the action. e.g.: "gno.land/p/samcrew/blog.NewPost"
3 String() string // return stringify content of the action
4}
5
6type ActionHandler interface {
7 Type() string // return the type of the action. e.g.: "gno.land/p/samcrew/blog"
8 Execute(action Action) // executes logic associated with the action
9}
This allows DAOs to execute arbitrary logic or interact with Gno packages through governance-approved decisions.
Steps to Add a Custom Resource:
- Define the path of the action, it should be unique
1// XXX: pkg "/p/samcrew/blog" - does not exist, it's just an example
2const ActionNewPostKind = "gno.land/p/samcrew/blog.NewPost"
- Create the structure type of the payload
1type ActionNewPost struct {
2 Title string
3 Content string
4}
- Implement the action and handler
1func NewPostAction(title, content string) daokit.Action {
2 // def: daoKit.NewAction(kind: String, payload: interface{})
3 return daokit.NewAction(ActionNewPostKind, &ActionNewPost{
4 Title: title,
5 Content: content,
6 })
7}
8
9func NewPostHandler(blog *Blog) daokit.ActionHandler {
10 // def: daoKit.NewActionHandler(kind: String, payload: func(interface{}))
11 return daokit.NewActionHandler(ActionNewPostKind, func(payload interface{}) {
12 action, ok := payload.(*ActionNewPost)
13 if !ok {
14 panic(errors.New("invalid action type"))
15 }
16 blog.NewPost(action.Title, action.Content)
17 })
18}
- Register the resource
1resource := daokit.Resource{
2 Condition: daocond.NewRoleCount(1, "CEO", daoPrivate.Members.HasRole),
3 Handler: blog.NewPostHandler(blog),
4}
5daoPrivate.Core.Resources.Set(&resource)