Skip to main content

How to add a proposal module

The proposal module system enables DAOs to support any type of execution flow that can be coded in a smart contract.

DAO DAO provides two modules:

  • single choice (e.g. Yes/No/Abstain)
  • multiple choice (e.g. A/B/C/None of the Above)

You can always add new modules or disable existing ones, though there must always be at least one module enabled (or else the DAO will not function). The most likely reason one would want to add a new module is to use a new version with new features. Because the proposal module interface is standardized and unchanging, newer versions of proposal modules should be backwards compatible.

Adding and disabling proposal modules is not common, and can be dangerous, and thus is not easily supported by the UI, but it is not hard to do with a little bit of time.

⚠️ Be careful to verify all proposal modules function correctly before disabling one you no longer need, because if the only enabled proposal modules are broken or the addresses are incorrect, the DAO will be bricked and all assets will be locked.

Guide

First you need the code IDs of the proposal module and its corresponding pre-propose module. You can find all uploaded DAO DAO code IDs here: https://github.com/DA0-DA0/dao-dao-ui/blob/development/packages/utils/constants/codeIds.json

The following guide will add the v2.6.0 single choice proposal module to a DAO on Osmosis mainnet (osmosis-1), so we need the dao-proposal-single and dao-pre-propose-single code IDs.

A few steps involve base64 encoding, which base64encode.org can help with.

Here is the configuration we will use:

  • threshold: majority
  • quorum: 20%
  • max voting period: 1 week
  • only members can execute: true
  • close proposal on execution failure: true
  • allow revoting: false
  • submission policy: DAO members only
  • no proposal deposit

You can look at the JSON schemas' instantiate definitions for the proposal and pre-propose modules to see the configuration fields you need to set, as well as the dao-dao-core contract's JSON schema to see the update_proposal_modules execution message that performs the actual update:

You can also look at the Rust source code, which contains the corresponding InstantiateMsg and ExecuteMsg structs, though you will need to know how serde serializes various types and enums which can be tricky. The JSON schemas above contain all the information you need, but here is the source code for reference:

Putting that all together, the full message we need to execute on the DAO via a proposal, with everything decoded for readability, follows:

{
"update_proposal_modules": {
"to_add": [{
"admin": { "core_module": {} },
"code_id": 1252,
"label": "dao-proposal-single-v2.6.0",
"msg": {
"threshold": {
"threshold_quorum": {
"threshold": { "majority": {} },
"quorum": { "percent": "0.2" }
}
},
"max_voting_period": { "time": 604800 },
"only_members_execute": true,
"close_proposal_on_execution_failure": true,
"allow_revoting": false,
"pre_propose_info": {
"module_may_propose": {
"info": {
"code_id": 1250,
"msg": {
"submission_policy": {
"specific": {
"dao_members": true,
"allowlist": [],
"denylist": []
}
},
"extension": {}
},
"admin": { "core_module": {} },
"funds": [],
"label": "dao-pre-propose-single-v2.6.0"
}
}
}
},
"funds": []
}],
"to_disable": []
}
}

Now we have to recursively encode the msg fields with base64, so starting from the innermost msg field for the pre-propose module instantiation message:

{
"submission_policy": {
"specific": {
"dao_members": true,
"allowlist": [],
"denylist": []
}
},
"extension": {}
}

This gets base64-encoded to:

ewogICJzdWJtaXNzaW9uX3BvbGljeSI6IHsKICAgICJzcGVjaWZpYyI6IHsKICAgICAgImRhb19tZW1iZXJzIjogdHJ1ZSwKICAgICAgImFsbG93bGlzdCI6IFtdLAogICAgICAiZGVueWxpc3QiOiBbXQogICAgfQogIH0sCiAgImV4dGVuc2lvbiI6IHt9Cn0=

Now isolate the proposal module instantiation msg, and replace the pre-propose module instantiation msg field with the base64-encoded pre-propose module instantiation message from above:

{
"threshold": {
"threshold_quorum": {
"threshold": { "majority": {} },
"quorum": { "percent": "0.2" }
}
},
"max_voting_period": { "time": 604800 },
"only_members_execute": true,
"close_proposal_on_execution_failure": true,
"allow_revoting": false,
"pre_propose_info": {
"module_may_propose": {
"info": {
"code_id": 1250,
"msg": "ewogICJzdWJtaXNzaW9uX3BvbGljeSI6IHsKICAgICJzcGVjaWZpYyI6IHsKICAgICAgImRhb19tZW1iZXJzIjogdHJ1ZSwKICAgICAgImFsbG93bGlzdCI6IFtdLAogICAgICAiZGVueWxpc3QiOiBbXQogICAgfQogIH0sCiAgImV4dGVuc2lvbiI6IHt9Cn0=",
"admin": { "core_module": {} },
"funds": [],
"label": "dao-pre-propose-single-v2.6.0"
}
}
}
}

This gets base64-encoded to:

ewogICJ0aHJlc2hvbGQiOiB7CiAgICAidGhyZXNob2xkX3F1b3J1bSI6IHsKICAgICAgInRocmVzaG9sZCI6IHsgIm1ham9yaXR5Ijoge30gfSwKICAgICAgInF1b3J1bSI6IHsgInBlcmNlbnQiOiAiMC4yIiB9CiAgICB9CiAgfSwKICAibWF4X3ZvdGluZ19wZXJpb2QiOiB7ICJ0aW1lIjogNjA0ODAwIH0sCiAgIm9ubHlfbWVtYmVyc19leGVjdXRlIjogdHJ1ZSwKICAiY2xvc2VfcHJvcG9zYWxfb25fZXhlY3V0aW9uX2ZhaWx1cmUiOiB0cnVlLAogICJhbGxvd19yZXZvdGluZyI6IGZhbHNlLAogICJwcmVfcHJvcG9zZV9pbmZvIjogewogICAgIm1vZHVsZV9tYXlfcHJvcG9zZSI6IHsKICAgICAgImluZm8iOiB7CiAgICAgICAgImNvZGVfaWQiOiAxMjUwLAogICAgICAgICJtc2ciOiAiZXdvZ0lDSnpkV0p0YVhOemFXOXVYM0J2YkdsamVTSTZJSHNLSUNBZ0lDSnpjR1ZqYVdacFl5STZJSHNLSUNBZ0lDQWdJbVJoYjE5dFpXMWlaWEp6SWpvZ2RISjFaU3dLSUNBZ0lDQWdJbUZzYkc5M2JHbHpkQ0k2SUZ0ZExBb2dJQ0FnSUNBaVpHVnVlV3hwYzNRaU9pQmJYUW9nSUNBZ2ZRb2dJSDBzQ2lBZ0ltVjRkR1Z1YzJsdmJpSTZJSHQ5Q24wPSIsCiAgICAgICAgImFkbWluIjogeyAiY29yZV9tb2R1bGUiOiB7fSB9LAogICAgICAgICJmdW5kcyI6IFtdLAogICAgICAgICJsYWJlbCI6ICJkYW8tcHJlLXByb3Bvc2Utc2luZ2xlLXYyLjYuMCIKICAgICAgfQogICAgfQogIH0KfQ==

Now replace the proposal module instantiation msg field in the update_proposal_modules execution message with the base64-encoded proposal module instantiation message from above:

{
"update_proposal_modules": {
"to_add": [{
"admin": { "core_module": {} },
"code_id": 1252,
"label": "dao-proposal-single-v2.6.0",
"msg": "ewogICJ0aHJlc2hvbGQiOiB7CiAgICAidGhyZXNob2xkX3F1b3J1bSI6IHsKICAgICAgInRocmVzaG9sZCI6IHsgIm1ham9yaXR5Ijoge30gfSwKICAgICAgInF1b3J1bSI6IHsgInBlcmNlbnQiOiAiMC4yIiB9CiAgICB9CiAgfSwKICAibWF4X3ZvdGluZ19wZXJpb2QiOiB7ICJ0aW1lIjogNjA0ODAwIH0sCiAgIm9ubHlfbWVtYmVyc19leGVjdXRlIjogdHJ1ZSwKICAiY2xvc2VfcHJvcG9zYWxfb25fZXhlY3V0aW9uX2ZhaWx1cmUiOiB0cnVlLAogICJhbGxvd19yZXZvdGluZyI6IGZhbHNlLAogICJwcmVfcHJvcG9zZV9pbmZvIjogewogICAgIm1vZHVsZV9tYXlfcHJvcG9zZSI6IHsKICAgICAgImluZm8iOiB7CiAgICAgICAgImNvZGVfaWQiOiAxMjUwLAogICAgICAgICJtc2ciOiAiZXdvZ0lDSnpkV0p0YVhOemFXOXVYM0J2YkdsamVTSTZJSHNLSUNBZ0lDSnpjR1ZqYVdacFl5STZJSHNLSUNBZ0lDQWdJbVJoYjE5dFpXMWlaWEp6SWpvZ2RISjFaU3dLSUNBZ0lDQWdJbUZzYkc5M2JHbHpkQ0k2SUZ0ZExBb2dJQ0FnSUNBaVpHVnVlV3hwYzNRaU9pQmJYUW9nSUNBZ2ZRb2dJSDBzQ2lBZ0ltVjRkR1Z1YzJsdmJpSTZJSHQ5Q24wPSIsCiAgICAgICAgImFkbWluIjogeyAiY29yZV9tb2R1bGUiOiB7fSB9LAogICAgICAgICJmdW5kcyI6IFtdLAogICAgICAgICJsYWJlbCI6ICJkYW8tcHJlLXByb3Bvc2Utc2luZ2xlLXYyLjYuMCIKICAgICAgfQogICAgfQogIH0KfQ==",
"funds": []
}],
"to_disable": []
}
}

And you have the full message to execute on the DAO via a proposal. You can now use the DAO DAO UI to create a proposal with the Execute Smart Contract action, with the contract address being the DAO's dao-dao-core contract, and the message field containing the above text.

Once you execute this, test the new proposal module by creating a proposal, voting on it, and executing it. If everything seems to work, you can now disable the old single choice proposal module by executing the following message in another proposal:

{
"update_proposal_modules": {
"to_add": [],
"to_disable": ["EXISTING PROPOSAL MODULE ADDRESS"]
}
}

Ideally, wait a while to see the new proposal module in action before disabling the old one.