How to create an autofill proposal link
Proposals and the actions they contain can be generated programmatically and passed in via URLβthis enables a more user-friendly experience for users of a specific app or service that rely on DAO DAO for their governance needs.
Similar to the bulk import actions feature, you must
find the key and the shape of the data object for each action. However, unlike
bulk import, you must also find the proposal module adapter ID, and the action
key in the object must be actionKey
instead of key
.
Link formatβ
The link is simply the proposal creation page with a prefill
parameter added,
like this:
https://daodao.zone/dao/<DAO_ADDRESS>/proposals/create?prefill=<PREFILL_DATA>
The prefill
parameter can either be:
- a stringified JSON object
- a base64-encoded JSON object
β οΈ Remember to URL-escape the
prefill
parameter, whichever format it's in. For example, base64 strings sometimes contain+
and=
, which must be escaped as%2B
and%3D
respectively, or else they'll be decoded incorrectly and fail to autofill the proposal.
The format of the prefill
object is always:
{
"id": "<PROPOSAL MODULE ADAPTER ID>",
"data": {
// PROPOSAL MODULE FORM DATA
}
}
You can use base64encode.org to encode the JSON object to a base64 string and urlencoder.org to URL-escape the base64 string.
IPFSβ
The prefill object can get quite large which becomes impractical to use in a
URL, so instead you can upload a JSON blob to IPFS and replace the prefill
parameter with pi
, pointing to the IPFS hash containing the object:
https://daodao.zone/dao/<DAO_ADDRESS>/proposals/create?pi=<IPFS_HASH>
Proposal module adapter IDβ
The proposal module adapter IDs can be found in
@dao-dao/utils/constants/adapters.ts
.
For dao-proposal-single
(the most commonly used proposal module that supports
proposals with Yes/No/Abstain votes), the ID is DaoProposalSingle
. For
dao-proposal-multiple
(which supports multiple choice proposals), the ID is
DaoProposalMultiple
.
Proposal module form dataβ
The form data structure can be found in
@dao-dao/types/proposal-module-adapter.ts
.
The single choice proposal form data is:
{
"title": "<PROPOSAL TITLE>",
"description": "<PROPOSAL DESCRIPTION>",
"actionData": [
{
"actionKey": "<ACTION KEY>",
"data": {
// ACTION DATA
}
},
...
]
}
The multiple choice proposal form data is:
{
"title": "<PROPOSAL TITLE>",
"description": "<PROPOSAL DESCRIPTION>",
"choices": [
{
"title": "<OPTION TITLE>",
"description": "<OPTION DESCRIPTION>",
"actionData": [
{
"actionKey": "<ACTION KEY>",
"data": {
// ACTION DATA
}
},
...
]
},
...
]
}
See the bottom of this document for a complete example.
Action keysβ
The action keys can be found in
@dao-dao/types/actions.ts
in the ActionKey
enum. For example:
spend
execute
mintNft
mint
The key and data format for an action are defined in its README.md
, and the
actions can be found in the following places:
@dao-dao/stateful/actions/core/actions
β common actions to all DAOs.@dao-dao/stateful/voting-module-adapter/adapters/*/actions
β each voting module adapter may contain specific actions (for example: member-based DAOs include a Manage Members action).@dao-dao/stateful/proposal-module-adapter/adapters/*/common/actions
β each proposal module adapter may contain specific actions (for example: single choice proposals have a different Update Proposal Submission Config action from multiple choice proposals).@dao-dao/stateful/modules/modules/*/actions
β each module may contain specific actions (for example: the vesting payments module adds an action to manage vesting payments).
Here are some common ones:
spend
β
For sending money from the treasury.
{
"fromChainId": "<CHAIN ID>",
"toChainId": "<CHAIN ID>",
"from": "<FROM ADDRESS>",
"to": "<RECIPIENT ADDRESS>",
"amount": "<AMOUNT>",
"denom": "<DENOM>"
}
execute
β
For executing a smart contract.
{
"chainId": "<CHAIN ID>",
"sender": "<EXECUTOR ADDRESS>",
"address": "<SMART CONTRACT ADDRESS>",
"message": "<SMART CONTRACT MESSAGE>",
"funds": [
{
"denom": "<DENOM>",
"amount": "<AMOUNT>",
"decimals": "<DECIMALS>"
}
]
}
mintNft
β
For minting an NFT in a collection the DAO controls.
{
"contractChosen": true,
"collectionAddress": "<NFT COLLECTION ADDRESS>",
"mintMsg": {
"owner": "<RECIPIENT ADDRESS>",
"token_id": "<UNIQUE TOKEN ID>",
"token_uri": "<JSON METADATA URL>"
}
}
mint
β
For minting governance tokens in a token-based DAO.
{
"recipient": "<RECIPIENT ADDRESS>",
"amount": "<TOKEN AMOUNT>"
}
Exampleβ
Here's an example of a proposal link that creates a single choice proposal with a spend action and an execute action in the DAO DAO DAO.
{
"id": "DaoProposalSingle",
"data": {
"title": "Example Proposal",
"description": "This proposal will send 100 JUNO to our community fund and update our voting module's unstaking duration.",
"actionData": [
{
"actionKey": "spend",
"data": {
"fromChainId": "juno-1",
"toChainId": "juno-1",
"from": "juno10h0hc64jv006rr8qy0zhlu4jsxct8qwa0vtaleayh0ujz0zynf2s2r7v8q",
"to": "juno1community...fund456",
"amount": "100",
"denom": "ujuno"
}
},
{
"actionKey": "execute",
"data": {
"chainId": "juno-1",
"sender": "juno10h0hc64jv006rr8qy0zhlu4jsxct8qwa0vtaleayh0ujz0zynf2s2r7v8q",
"address": "juno1voting...module789",
"message": "{\"update_config\":{\"duration\":86400}}",
"funds": []
}
}
]
}
}
Base64-encoding the entire object:
ewogICJpZCI6ICJEYW9Qcm9wb3NhbFNpbmdsZSIsCiAgImRhdGEiOiB7CiAgICAidGl0bGUiOiAiRXhhbXBsZSBQcm9wb3NhbCIsCiAgICAiZGVzY3JpcHRpb24iOiAiVGhpcyBwcm9wb3NhbCB3aWxsIHNlbmQgMTAwIEpVTk8gdG8gb3VyIGNvbW11bml0eSBmdW5kIGFuZCB1cGRhdGUgb3VyIHZvdGluZyBtb2R1bGUncyB1bnN0YWtpbmcgZHVyYXRpb24uIiwKICAgICJhY3Rpb25EYXRhIjogWwogICAgICB7CiAgICAgICAgImFjdGlvbktleSI6ICJzcGVuZCIsCiAgICAgICAgImRhdGEiOiB7CiAgICAgICAgICAiZnJvbUNoYWluSWQiOiAianVuby0xIiwKICAgICAgICAgICJ0b0NoYWluSWQiOiAianVuby0xIiwKICAgICAgICAgICJmcm9tIjogImp1bm8xMGgwaGM2NGp2MDA2cnI4cXkwemhsdTRqc3hjdDhxd2EwdnRhbGVheWgwdWp6MHp5bmYyczJyN3Y4cSIsCiAgICAgICAgICAidG8iOiAianVubzFjb21tdW5pdHkuLi5mdW5kNDU2IiwKICAgICAgICAgICJhbW91bnQiOiAiMTAwIiwKICAgICAgICAgICJkZW5vbSI6ICJ1anVubyIKICAgICAgICB9CiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiYWN0aW9uS2V5IjogImV4ZWN1dGUiLAogICAgICAgICJkYXRhIjogewogICAgICAgICAgImNoYWluSWQiOiAianVuby0xIiwKICAgICAgICAgICJzZW5kZXIiOiAianVubzEwaDBoYzY0anYwMDZycjhxeTB6aGx1NGpzeGN0OHF3YTB2dGFsZWF5aDB1anowenluZjJzMnI3djhxIiwKICAgICAgICAgICJhZGRyZXNzIjogImp1bm8xdm90aW5nLi4ubW9kdWxlNzg5IiwKICAgICAgICAgICJtZXNzYWdlIjogIntcInVwZGF0ZV9jb25maWdcIjp7XCJkdXJhdGlvblwiOjg2NDAwfX0iLAogICAgICAgICAgImZ1bmRzIjogW10KICAgICAgICB9CiAgICAgIH0KICAgIF0KICB9Cn0=
And then URL-escaping the base64 string simply
replaces the =
at the end with %3D
:
ewogICJpZCI6ICJEYW9Qcm9wb3NhbFNpbmdsZSIsCiAgImRhdGEiOiB7CiAgICAidGl0bGUiOiAiRXhhbXBsZSBQcm9wb3NhbCIsCiAgICAiZGVzY3JpcHRpb24iOiAiVGhpcyBwcm9wb3NhbCB3aWxsIHNlbmQgMTAwIEpVTk8gdG8gb3VyIGNvbW11bml0eSBmdW5kIGFuZCB1cGRhdGUgb3VyIHZvdGluZyBtb2R1bGUncyB1bnN0YWtpbmcgZHVyYXRpb24uIiwKICAgICJhY3Rpb25EYXRhIjogWwogICAgICB7CiAgICAgICAgImFjdGlvbktleSI6ICJzcGVuZCIsCiAgICAgICAgImRhdGEiOiB7CiAgICAgICAgICAiZnJvbUNoYWluSWQiOiAianVuby0xIiwKICAgICAgICAgICJ0b0NoYWluSWQiOiAianVuby0xIiwKICAgICAgICAgICJmcm9tIjogImp1bm8xMGgwaGM2NGp2MDA2cnI4cXkwemhsdTRqc3hjdDhxd2EwdnRhbGVheWgwdWp6MHp5bmYyczJyN3Y4cSIsCiAgICAgICAgICAidG8iOiAianVubzFjb21tdW5pdHkuLi5mdW5kNDU2IiwKICAgICAgICAgICJhbW91bnQiOiAiMTAwIiwKICAgICAgICAgICJkZW5vbSI6ICJ1anVubyIKICAgICAgICB9CiAgICAgIH0sCiAgICAgIHsKICAgICAgICAiYWN0aW9uS2V5IjogImV4ZWN1dGUiLAogICAgICAgICJkYXRhIjogewogICAgICAgICAgImNoYWluSWQiOiAianVuby0xIiwKICAgICAgICAgICJzZW5kZXIiOiAianVubzEwaDBoYzY0anYwMDZycjhxeTB6aGx1NGpzeGN0OHF3YTB2dGFsZWF5aDB1anowenluZjJzMnI3djhxIiwKICAgICAgICAgICJhZGRyZXNzIjogImp1bm8xdm90aW5nLi4ubW9kdWxlNzg5IiwKICAgICAgICAgICJtZXNzYWdlIjogIntcInVwZGF0ZV9jb25maWdcIjp7XCJkdXJhdGlvblwiOjg2NDAwfX0iLAogICAgICAgICAgImZ1bmRzIjogW10KICAgICAgICB9CiAgICAgIH0KICAgIF0KICB9Cn0%3D
Using the DAO DAO
DAO
address and then adding the prefill
parameter to the URL gives us the
proposal creation page with the proposal prefilled: