In this guide, we'll go over how to set up and configure a Decentralized Autonomous Organization (DAO) using the Gno DAO framework. We'll cover the core components that make up a DAO, walk you through the process of creating your first DAO, and provide code examples to help you get started.
The IDAOCore
interface ties all the other components together and offers the main entry points for interacting with the DAO.
Interface Definition:
type IDAOCore interface {
Render(path string) string
VotingModule() IVotingModule
ProposalModules() []ActivableProposalModule
ActiveProposalModuleCount() int
Registry() *MessagesRegistry
UpdateVotingModule(newVotingModule IVotingModule)
UpdateProposalModules(toAdd []IProposalModule, toDisable []int)
}
A default implementation is provided in the package gno.land/p/demo/teritori/dao_core
, and custom implementations are generally not required.
The gno.land/p/demo/teritori/dao_interfaces.IVotingModule
interface defines how voting power is allocated to addresses within the DAO.
Interface Definition:
type IVotingModule interface {
Info() ModuleInfo
ConfigJSON() string
Render(path string) string
VotingPowerAtHeight(address std.Address, height int64) (power uint64)
TotalPowerAtHeight(height int64) uint64
}
There is only one implementation currently, gno.land/p/demo/teritori/dao_voting_group
, it's a wrapper around a gno group, providing a membership-based voting power definition
A proposal module (gno.land/p/demo/teritori/dao_interfaces.IProposalModule
) is responsible for:
- Receiving proposals, the proposal type is defined by the module
- Managing the proposals lifecycle
- Tallying votes, the vote type is defined by the module and the associated voting power is queried from the voting module
- Executing proposals once they are passed
Interface Definition:
type IProposalModule interface {
Core() IDAOCore
Info() ModuleInfo
ConfigJSON() string
Render(path string) string
Execute(proposalID int)
VoteJSON(proposalID int, voteJSON string)
ProposeJSON(proposalJSON string) int
ProposalsJSON(limit int, startAfter string, reverse bool) string
ProposalJSON(proposalID int) string
}
There is only one implementation currently, gno.land/p/demo/teritori/dao_proposal_single
, providing a yes/no/abstain vote model with quorum and threshold
Proposals actions are encoded as objects implementing gno.land/p/demo/teritori/dao_interfaces.ExecutableMessage
type ExecutableMessage interface {
ujson.JSONAble
ujson.FromJSONAble
String() string
Type() string
}
They are deserialized and executed by message handlers implementing gno.land/p/demo/teritori/dao_interfaces.MessageHandler
type MessageHandler interface {
Execute(message ExecutableMessage)
MessageFromJSON(ast *ujson.JSONASTNode) ExecutableMessage
Type() string
}
Message handlers are registered at core creation and new message handlers can be registered via proposals to extend the DAO capabilities
Sooo, let's create a new realm
git clone https://github.com/TERITORI/gno.git gno-dao-tutorial
cd gno-dao-tutorial
git checkout teritori-unified
mkdir examples/gno.land/r/demo/my_dao
We will start by instantiating a voting module
- Initialize the Factory
Modules instantiation uses the factory pattern in case the module needs to access the core
examples/gno.land/r/demo/my_dao/my_dao.gno
package my_dao
import (
"gno.land/p/demo/teritori/dao_interfaces"
)
func init() {
votingModuleFactory := func(core dao_interfaces.IDAOCore) {
}
}
- Create the Group
First we need to create a group that will be queried by the module. We use the teritori fork of the groups realm since the upstream groups can't be created by a non-EOA
examples/gno.land/r/demo/my_dao/my_dao.gno
package my_dao
import (
"gno.land/p/demo/teritori/dao_interfaces"
"gno.land/p/demo/teritori/groups" // <- new
)
func init() {
var groupID groups.GroupID
votingModuleFactory := func(core dao_interfaces.IDAOCore) {
groupID = groups.CreateGroup("my_dao_voting_group") // <- new
}
}
We need to keep a reference to the group ID to instantiate it's message handlers later
- Add Initial Members
examples/gno.land/r/demo/my_dao/my_dao.gno
func init() {
votingModuleFactory := func(core dao_interfaces.IDAOCore) {
groupID = groups.CreateGroup("my_dao_voting_group")
groups.AddMember(groupID, "your-address", 1, "") // <- new
// repeat for any other initial members you want in the DAO
}
}
- Instantiate the voting module
examples/gno.land/r/demo/my_dao/my_dao.gno
package my_dao
import (
"gno.land/p/demo/teritori/dao_interfaces"
"gno.land/p/demo/teritori/groups"
"gno.land/p/demo/teritori/dao_voting_group" // <- new
)
func init() {
votingModuleFactory := func(core dao_interfaces.IDAOCore) dao_interfaces.IVotingModule {
groupID = groups.CreateGroup("my_dao_voting_group")
groups.AddMember(groupID, "your-address", 1, "")
return dao_voting_group.NewVotingGroup(groupID) // <- new
}
}
Now let's create a proposal module
- Initialize the Factory
examples/gno.land/r/demo/my_dao/my_dao.gno
func init() {
votingModuleFactory := func(core dao_interfaces.IDAOCore) {
// ...
}
proposalModuleFactories := []dao_interfaces.ProposalModuleFactory{
func(core dao_interfaces.IDAOCore) dao_interfaces.IProposalModule {
},
}
}
- Configure and instantiate the Proposal Module
examples/gno.land/r/demo/my_dao/my_dao.gno
package my_dao
import (
"gno.land/p/demo/teritori/dao_interfaces"
"gno.land/p/demo/teritori/groups"
"gno.land/p/demo/teritori/dao_voting_group"
"gno.land/p/demo/teritori/dao_proposal_single" // <- new
)
func init() {
// ...
var proposalModule *dao_proposal_single.DAOProposalSingle
proposalModuleFactories := []dao_interfaces.ProposalModuleFactory{
func(core dao_interfaces.IDAOCore) dao_interfaces.IProposalModule {
tt := dao_proposal_single.PercentageThresholdPercent(100) // 1% threshold
tq := dao_proposal_single.PercentageThresholdPercent(100) // 1% quorum
proposalModule = dao_proposal_single.NewDAOProposalSingle(core, &dao_proposal_single.DAOProposalSingleOpts{
MaxVotingPeriod: time.Hour * 24 * 42,
Threshold: &dao_proposal_single.ThresholdThresholdQuorum{
Threshold: &tt,
Quorum: &tq,
},
})
return proposalModule
},
}
}
We need to keep a reference to the group ID to instantiate it's message handlers later
Add message handlers to allow your DAO to perform specific actions when proposals are executed.
examples/gno.land/r/demo/my_dao/my_dao.gno
package my_dao
import (
"gno.land/p/demo/teritori/dao_interfaces"
"gno.land/p/demo/teritori/groups"
"gno.land/p/demo/teritori/dao_voting_group"
"gno.land/p/demo/teritori/dao_proposal_single"
)
func init() {
// ...
messageHandlersFactories := []dao_interfaces.MessageHandlerFactory{
// Allow to manage the voting group
func(core dao_interfaces.IDAOCore) dao_interfaces.MessageHandler {
return groups.NewAddMemberHandler(groupID)
},
func(core dao_interfaces.IDAOCore) dao_interfaces.MessageHandler {
return groups.NewDeleteMemberHandler(groupID)
},
// Allow to update the proposal module settings
func(core dao_interfaces.IDAOCore) dao_interfaces.MessageHandler {
return dao_proposal_single.NewUpdateSettingsHandler(proposalModule)
},
}
}
Now we can create the actual DAO
package my_dao
import (
"gno.land/p/demo/teritori/dao_interfaces"
"gno.land/p/demo/teritori/groups"
"gno.land/p/demo/teritori/dao_voting_group"
"gno.land/p/demo/teritori/dao_proposal_single"
"gno.land/p/demo/teritori/dao_core" // <- new
)
var (
daoCore dao_interfaces.IDAOCore // <- new
)
func init() {
// ...
messageHandlersFactories := []dao_interfaces.MessageHandlerFactory{
// ...
}
daoCore = dao_core.NewDAOCore(votingModuleFactory, proposalModuleFactories, messageHandlersFactories) // <- new
}
We also need to expose the DAO methods in the realm
func init() {
// ...
}
func Render(path string) string {
return daoCore.Render(path)
}
func VoteJSON(moduleIndex int, proposalID int, voteJSON string) {
module := dao_core.GetProposalModule(daoCore, moduleIndex)
if !module.Enabled {
panic("proposal module is not enabled")
}
module.Module.VoteJSON(proposalID, voteJSON)
}
func Execute(moduleIndex int, proposalID int) {
module := dao_core.GetProposalModule(daoCore, moduleIndex)
if !module.Enabled {
panic("proposal module is not enabled")
}
module.Module.Execute(proposalID)
}
func ProposeJSON(moduleIndex int, proposalJSON string) int {
module := dao_core.GetProposalModule(daoCore, moduleIndex)
if !module.Enabled {
panic("proposal module is not enabled")
}
return module.Module.ProposeJSON(proposalJSON)
}
func getProposalsJSON(moduleIndex int, limit int, startAfter string, reverse bool) string {
module := dao_core.GetProposalModule(daoCore, moduleIndex)
return module.Module.ProposalsJSON(limit, startAfter, reverse)
}
func getProposalJSON(moduleIndex int, proposalIndex int) string {
module := dao_core.GetProposalModule(daoCore, moduleIndex)
return module.Module.ProposalJSON(proposalIndex)
}
TODO: Add instructions for deploying and interacting with the DAO.
That's it! You've successfully created your first DAO using the Gno DAO framework. To expand its capabilities, you can register additional message handlers or even create new modules if you feel bold