# DAO DAO on Juno DAO DAO is the dominant on-chain DAO platform on Juno. This reference covers three layers: 1. **§Interact** — work with an existing DAO (query state, propose, vote, execute). 2. **§Create** — instantiate a new DAO from scratch (the three-contract dance). 3. **§Member** — operate as a DAO member, including agent-mandate patterns (sub-DAOs, VetoConfig, cw-filter). Read top-to-bottom on your first DAO DAO task; grep on subsequent ones. ## The module model Every DAO DAO DAO is composed of multiple contracts: ``` DaoDaoCore (the "DAO" — treasury, identity, gateway) │ ├── one Voting Module (computes voting power per address) ├── one+ Proposal Modules (accept proposals, tally votes, execute) └── 0/1 Pre-Propose Module per proposal module (gates submission, may charge a deposit) ``` | Module type | Examples | |---|---| | Voting | `dao-voting-cw4` (group-based), `dao-voting-cw20-staked`, `dao-voting-cw721-staked` (NFT), `dao-voting-token-staked` (native + tokenfactory), `dao-voting-juno-staked` (Juno chain staking, v30+) | | Proposal | `dao-proposal-single` (yes/no/abstain + veto-config), `dao-proposal-multiple` (multi-choice), `dao-proposal-condorcet` (ranked), `dao-proposal-wavs` (WAVS-attested execution, in development) | | Pre-Propose | `dao-pre-propose-single`, `dao-pre-propose-multiple`, `dao-pre-propose-approval-single` (explicit approval gate) | The **core** holds the treasury (any denom — native, IBC, cw20, NFT). The voting module computes who has voting power. Proposal modules receive proposals and tally votes. When a proposal passes (and the optional veto window expires), the proposal module calls back into the core to execute the messages — which can transfer treasury, call other contracts, do anything the DAO is authorized for. ## Mainnet code IDs (juno-1, dao-contracts v2.7.0) From `dao-dao-ui/packages/utils/constants/codeIds.json`: | Contract | Code ID | What it is | |---|---|---| | DaoDaoCore | 4862 | The core / treasury contract | | DaoProposalSingle | 4869 | yes/no/abstain proposals with veto config | | DaoProposalMultiple | 4870 | multi-choice proposals | | DaoVotingCw4 | 4872 | group-based voting (cw4-group) | | DaoVotingCw20Staked | 4873 | cw20 staking voting | | DaoVotingTokenStaked | 4874 | native / tokenfactory staking voting | | Cw4Group | 1992 | reference group contract | | DaoPreProposeSingle | 4867 | basic pre-propose gate (optional deposit) | | DaoPreProposeMultiple | 4868 | same for multi-choice | | DaoPreProposeApprovalSingle | 4864 | explicit-approval gate | `uni-7` testnet has different code IDs — check the same JSON in `dao-dao-ui` under the testnet block, or upload your own. ## §Interact — work with an existing DAO You need three addresses to interact: - **Core address** — `juno1...` of the DAO itself. Often what UIs show as "the DAO." - **Proposal module address** — proposals are submitted to and votes cast on this contract. - **Voting module address** — voting power and members live here. Discover them from the core: ```bash DAO_CORE=juno1 # Voting module (singular) junod query wasm contract-state smart $DAO_CORE \ '{"voting_module":{}}' --node $RPC # Proposal modules (list — DAOs can have multiple) junod query wasm contract-state smart $DAO_CORE \ '{"proposal_modules":{}}' --node $RPC # Or active-only: junod query wasm contract-state smart $DAO_CORE \ '{"active_proposal_modules":{}}' --node $RPC ``` ### Query DAO state ```bash # Top-level info junod query wasm contract-state smart $DAO_CORE '{"info":{}}' --node $RPC junod query wasm contract-state smart $DAO_CORE '{"config":{}}' --node $RPC junod query wasm contract-state smart $DAO_CORE '{"admin":{}}' --node $RPC junod query wasm contract-state smart $DAO_CORE '{"dump_state":{}}' --node $RPC | jq # Treasury (the core's bank balances; treasury cw20s queried separately) junod query bank balances $DAO_CORE --node $RPC junod query wasm contract-state smart $DAO_CORE '{"cw20_balances":{"limit":20}}' --node $RPC # Proposals on a specific proposal module PROP_MOD=juno1 junod query wasm contract-state smart $PROP_MOD \ '{"list_proposals":{"limit":10}}' --node $RPC | jq junod query wasm contract-state smart $PROP_MOD \ '{"reverse_proposals":{"limit":5}}' --node $RPC | jq # Single proposal junod query wasm contract-state smart $PROP_MOD \ '{"proposal":{"proposal_id":42}}' --node $RPC | jq # Vote tally junod query wasm contract-state smart $PROP_MOD \ '{"get_vote":{"proposal_id":42,"voter":"juno1..."}}' --node $RPC # Your voting power at the DAO's current height junod query wasm contract-state smart $VOTING_MOD \ '{"voting_power_at_height":{"address":"juno1..."}}' --node $RPC ``` ### Propose The shape of a proposal: a title + description + a list of `CosmosMsg` to execute if it passes. ```bash PROP_MOD=juno1 # Build the proposal JSON cat > /tmp/propose.json <<'EOF' { "propose": { "title": "Send 100 JUNO to vendor for invoice #42", "description": "Pays the open invoice from acme-corp dated 2026-05-12.", "msgs": [ { "bank": { "send": { "to_address": "juno1vendor...", "amount": [{"denom": "ujuno", "amount": "100000000"}] } } } ] } } EOF junod tx wasm execute "$PROP_MOD" "$(cat /tmp/propose.json)" \ --from \ --chain-id juno-1 --node $RPC \ --gas auto --gas-adjustment 1.4 --gas-prices 0.075ujuno \ --keyring-backend test --keyring-dir \ --yes ``` **If the DAO has a pre-propose module with a deposit**, you need to either: - Send the deposit alongside (`--amount 1000000ujuno` for native, or a cw20 `Send` with the propose-msg as the inner msg) - Be on the allowlist for free proposing (some configs allow it) Query the pre-propose for its config: ```bash PREPROPOSE=$(junod query wasm contract-state smart $PROP_MOD \ '{"proposal_creation_policy":{}}' --node $RPC -o json | jq -r '.module.addr // empty') junod query wasm contract-state smart "$PREPROPOSE" '{"config":{}}' --node $RPC ``` Capture the proposal_id from the tx events: ```bash TXHASH= sleep 6 junod query tx "$TXHASH" --node $RPC -o json \ | jq -r '.events[] | select(.type == "wasm") | .attributes[] | select(.key == "proposal_id") | .value' ``` ### Vote ```bash junod tx wasm execute "$PROP_MOD" \ '{"vote":{"proposal_id":42,"vote":"yes"}}' \ --from --chain-id juno-1 --node $RPC \ --gas auto --gas-adjustment 1.4 --gas-prices 0.075ujuno \ --keyring-backend test --keyring-dir \ --yes ``` Vote options: `"yes"`, `"no"`, `"abstain"` (and `"none"` is the absent-vote state, not something you cast). ### Execute Once a proposal passes (and any veto timelock expires), anyone can call execute: ```bash junod tx wasm execute "$PROP_MOD" \ '{"execute":{"proposal_id":42}}' \ --from --chain-id juno-1 --node $RPC \ --gas auto --gas-adjustment 1.5 --gas-prices 0.075ujuno \ --keyring-backend test --keyring-dir \ --yes ``` Note `--gas-adjustment 1.5` — the executed messages run inside this tx, and their gas isn't visible to the simulator. ### Close If a proposal is rejected or expired and has a refundable deposit: ```bash junod tx wasm execute "$PROP_MOD" \ '{"close":{"proposal_id":42}}' \ --from ... ``` This releases the deposit back to the proposer (per the pre-propose-module's refund_policy). ### Veto (only if VetoConfig is set) Only the configured `vetoer` address can veto, only during the timelock window after a proposal passes: ```bash junod tx wasm execute "$PROP_MOD" \ '{"veto":{"proposal_id":42}}' \ --from ... ``` ## §Create — instantiate a new DAO The three-contract dance: DaoDaoCore takes nested base64-encoded sub-msgs for the voting + proposal modules. The clean approach is JSON files + jq + base64. ```bash # Set chain + endpoint CHAIN=juno-1 RPC=https://juno-rpc.publicnode.com:443 # Pick code IDs (mainnet juno-1, v2.7.0 — verify before use) CORE_CODE=4862 VOTING_CW4_CODE=4872 CW4_GROUP_CODE=1992 PROPOSAL_SINGLE_CODE=4869 # --- Voting module config: cw4-group with single member (1-of-1) --- cat > /tmp/voting_msg.json < /tmp/proposal_msg.json <<'EOF' { "threshold": { "absolute_count": { "threshold": "1" } }, "max_voting_period": { "time": 86400 }, "min_voting_period": null, "only_members_execute": false, "allow_revoting": false, "pre_propose_info": { "anyone_may_propose": {} }, "close_proposal_on_execution_failure": true, "veto": { "timelock_duration": { "time": 172800 }, "vetoer": "juno1vetoer...", "early_execute": false, "veto_before_passed": false }, "delegation_module": null } EOF VOTING_B64=$(jq -c . /tmp/voting_msg.json | base64 -w 0) PROPOSAL_B64=$(jq -c . /tmp/proposal_msg.json | base64 -w 0) # --- DaoDaoCore InstantiateMsg --- cat > /tmp/dao_msg.json < \ --chain-id "$CHAIN" --node "$RPC" \ --label "My DAO" \ --no-admin \ --gas auto --gas-adjustment 1.5 \ --gas-prices 0.075ujuno \ --keyring-backend test --keyring-dir \ --yes ``` **Capture the DAO core address from the tx events:** ```bash TXHASH= sleep 6 junod query tx "$TXHASH" --node $RPC -o json \ | jq -r '.events[] | select(.type == "instantiate") | .attributes[] | select(.key == "_contract_address") | .value' \ | head -1 # The first _contract_address in the event list is the DAO core. # Subsequent ones are the voting module, cw4-group, proposal module, ... ``` For more deterministic addressing, use `instantiate2` with a salt — see [`cosmwasm.md`](cosmwasm.md). For DAO DAO specifically, `dao-dao-ui` uses `instantiate2` extensively for the sub-contract addresses. ### Variants to consider - **cw20-staked DAO** — replace `dao-voting-cw4` with `dao-voting-cw20-staked`. The voting module deploys (or wraps) a cw20 token; staking the token grants voting power. - **NFT-staked DAO** — `dao-voting-cw721-staked`. Stake NFTs to vote (one NFT = one vote, configurable). - **Token-staked DAO (tokenfactory)** — `dao-voting-token-staked`. Native or tokenfactory denom staking. - **Multi-proposal-module DAO** — the `proposal_modules_instantiate_info` array can hold many entries. A DAO can have a `proposal-single` for normal ops AND a `proposal-condorcet` for ranked choices. - **With pre-propose** — set `pre_propose_info` to `{"module_may_propose":{"info":{}}}` to spawn a pre-propose module. The schema for each of these lives in the contract's `schema/` directory in `dao-contracts`. UI code in `dao-dao-ui` is the working reference for which fields are required. ## §Member — operating as a DAO member Once you're a member of a DAO (either by being in a cw4-group, staking tokens, or holding qualifying NFTs), you can propose, vote, and execute. This section covers patterns specific to operating *as* the DAO, including the agent-mandate stack. ### Routine member ops Identical to §Interact: - Propose treasury moves - Vote on submitted proposals - Execute passed proposals (anyone can after timelock) ### Sub-DAOs A sub-DAO is just another DAO that's owned by (or accountable to) a parent DAO. Common architectures: - **Parent admin pattern** — the parent DAO is set as `admin` of the sub-DAO core. Parent can migrate the sub-DAO via passed proposal. - **Vetoer pattern** — the parent (or its multisig) is the `vetoer` in the sub-DAO's proposal-module VetoConfig. Sub-DAO can act autonomously; parent has a veto window. - **Member pattern** — the parent's address is a member of the sub-DAO's voting module. Parent participates in sub-DAO votes alongside other members. For an AI-agent sub-DAO, the canonical pattern is: ``` Parent DAO (stewards / steering) │ (vetoer in sub-DAO's VetoConfig) ▼ Sub-DAO core (treasury, holds funds) ├── voting: dao-voting-cw4 (1-of-1, agent address is the sole member) └── proposal: dao-proposal-single with VetoConfig ├── timelock_duration: 24-48h ├── vetoer: parent multisig address └── early_execute: false (must wait for timelock) ``` The agent proposes; the timelock + vetoer gives stewards a window to react; if no veto, the proposal executes. Blast radius from a key compromise is bounded to *what the sub-DAO can propose*, with humans in the loop. ### VetoConfig in detail The `veto: Option` field on `dao-proposal-single::InstantiateMsg`: ```json { "veto": { "timelock_duration": { "height": 33000 }, "vetoer": "juno1...", "early_execute": false, "veto_before_passed": false } } ``` | Field | Meaning | |---|---| | `timelock_duration` | After a proposal passes, this delay must elapse before execute is allowed. **Must use the same unit as `max_voting_period`** — both `{"time": secs}` or both `{"height": N}`. Mixing units triggers `TimelockDurationUnitMismatch` at instantiate. 48h ≈ `{"time": 172800}` or `{"height": 33000}` (at ~5.2s blocks). | | `vetoer` | The address authorized to veto during the timelock. Can be a multisig, another DAO, or a single key. | | `early_execute` | If `true`, the vetoer can also execute immediately, bypassing the timelock. Useful for "approval"-style flows. | | `veto_before_passed` | If `true`, the vetoer can veto a proposal even before voting concludes (preemptive veto). Rare. | To veto: the vetoer calls `{"veto":{"proposal_id":42}}` on the proposal module during the timelock window. Once timelock expires, veto is impossible — anyone can execute. **Why VetoConfig matters for agent-mandates:** the timelock + human-veto pattern is exactly the "agent autonomy + steward override" architecture, and it ships out of the box with `dao-proposal-single`. No custom contract work needed. ### cw-filter — machine-readable mandates `cw-filter` is a stateless contract that validates whether a `CosmosMsg` matches a JSON policy filter. Source: `dao-contracts/contracts/external/cw-filter`. Grammar: `dao-contracts/packages/cw-jsonfilter/docs/filter.md` (the authoritative spec — read it before writing any non-trivial filter). ```bash junod query wasm contract-state smart $CW_FILTER_ADDR \ '{"filter":{"filter":,"msg":}}' \ --node $RPC ``` Returns: - `{"pass":{}}` — message is allowed - `{"fail":{"reason":"..."}}` — recoverable rejection - `{"fatal":{"reason":"..."}}` — fatal rejection (don't retry) **The filter mirrors the shape of the `CosmosMsg` JSON.** Adjacent keys are implicit `$and`. Predicates use `$` prefixes (`$eq`, `$or`, `$and`, `$not`, `$gt`, `$lt`, `$any`, `$all`, `$contains`, `$startsWith`, etc.). Value transformations use `#` prefixes (`#to_number`, `#len`, `#lower`, `#base64`, `#proto`, `#stargate`). A bare value is an implicit `$eq`. A CosmosMsg looks like `{"bank": {"send": {"to_address": "...", "amount": [...]}}}`; a filter looks like the same shape, with predicates in place of leaf values. **Example: bank sends only, to one of two recipients, denom `ujuno` only, ≤ 10 JUNO per tx.** ```json { "$or": [ { "bank": { "send": { "to_address": "juno1allowed1...", "amount": { "$all": { "denom": "ujuno", "amount": { "#to_number": { "$lte": 10000000 } } } } } } }, { "bank": { "send": { "to_address": "juno1allowed2...", "amount": { "$all": { "denom": "ujuno", "amount": { "#to_number": { "$lte": 10000000 } } } } } } } ] } ``` Notes on this example: - Top-level `$or` enumerates allowed message shapes. Anything outside the enumeration fails closed — non-`bank.send` messages, other denoms, other recipients all return `Fail`. - `$all` over `amount` requires every coin in the multi-coin amount array to match the inner filter (denom + amount cap). One bad coin → reject. - Coin amounts are `Uint128` strings on the wire; `#to_number` converts before applying `$lte`. Without the conversion the comparison happens on the string lexically — `"9"` would (correctly) compare greater than `"10000000"`. - For staking, gov, or other modules, add additional `$or` branches with the matching `CosmosMsg` shape, e.g. `{"staking": {"delegate": {}}}` (empty inner = "any delegate body passes") or `{"gov": {"vote": {"vote": {"$or": [{"$eq": "yes"}, {"$eq": "no"}]}}}}`. - cw-filter is **stateless**: it sees one message at a time. Rate limits and daily aggregates live in the signer service layer, not in this JSON. For agent-mandate stacks, the filter encodes the charter machine-readably: allowed message types, recipients, amount caps, denoms. The filter is loaded into the agent's signer service (off-chain check before signing) and into the proposal-module's execution path (on-chain check before dispatching) once `dao-proposal-wavs` lands. ### dao-proposal-wavs — WAVS-attested proposals `dao-proposal-wavs` (in development, see DA0-DA0/dao-contracts#922) is a proposal module that accepts WAVS-attested execution envelopes signed by a configured WAVS service-manager. Use cases: - Agent operating under a verifiable off-chain compute mandate - Cross-chain DAO governance with cryptographic attestation - Any case where you want "this action was approved by an off-chain ZK/TEE-verified process" The contract verifies the WAVS service-manager's signature on an envelope, optionally runs the envelope's payload through cw-filter, and dispatches the message via the DAO core. Wire format is canonical Solidity ABI (matches cw-middleware v0.3.0). For most DAO operators, this is forward-looking; standard `dao-proposal-single` + VetoConfig + advisory cw-filter is the production pattern today. ### End-to-end runbook: agent sub-DAO on mainnet (zero → first executed proposal) The most common real-world flow on Juno: an agent address gets funded; you stand up a sub-DAO; you submit the first proposal; the vetoer either lets it pass or vetoes; you execute. This is the canonical archetype the bundled scripts (`scripts/`) automate. Assumptions: agent address funded with ~5 JUNO (instantiate + 2–3 follow-up txs comfortably fit). Keyring at `/workspace/.my-keyring` with key `my-key`. Vetoer (a trusted human, multisig, or stewards DAO) at `juno1vetoer...`. RPC `https://juno-rpc.publicnode.com:443`. **Step 1 — Pre-flight.** ```bash RPC=https://juno-rpc.publicnode.com:443 junod version # expect v29.x curl -sS "$RPC/status" | jq -r '.result.node_info.network' # expect "juno-1" ADDR=$(junod keys show my-key -a --keyring-backend test --keyring-dir /workspace/.my-keyring) junod query bank balances "$ADDR" --node "$RPC" # expect ≥ 5_000_000 ujuno ``` **Step 2 — Instantiate the sub-DAO** using the bundled script: ```bash scripts/instantiate-subdao.sh \ --name "Agent X DAO" \ --description "1-of-1 sub-DAO for agent X; stewards hold a 48h veto window" \ --member "$ADDR" \ --vetoer juno1vetoer... \ --key my-key \ --keyring-dir /workspace/.my-keyring \ --voting-period 86400 \ --timelock 172800 ``` The script assembles the nested base64-encoded sub-msgs, enforces the timelock/voting-period unit-match constraint, and broadcasts the instantiate. It prints the broadcast txhash on success. **Step 3 — Capture the four contract addresses** (DAO core, voting module, cw4-group, proposal module): ```bash scripts/verify-tx.sh --txhash --rpc "$RPC" # the first _contract_address in instantiate-event order is the DAO core; # subsequent ones are voting module, cw4-group, proposal module (in that order) ``` Save them — every subsequent propose / vote / execute / veto needs `(core, proposal_module)`. The cw4-group address is rarely needed directly (the voting module wraps it). **Step 4 — Fund the DAO core treasury** (only the DAO core can spend on behalf of the DAO): ```bash junod tx bank send "$ADDR" $DAO_CORE 4000000ujuno \ --from my-key --chain-id juno-1 --node "$RPC" \ --keyring-backend test --keyring-dir /workspace/.my-keyring \ --gas auto --gas-adjustment 1.4 --gas-prices 0.075ujuno \ --note 'Agent X DAO: initial treasury seed' \ --yes ``` Keep ~0.5 JUNO at the agent address as a gas buffer; the rest seeds the DAO. **Step 5 — Submit the first proposal** (bank.send from the DAO treasury): ```bash scripts/propose-bank-send.sh \ --proposal-module "$PROP_MOD" \ --title "Seed payment to vendor" \ --description "First proposal: pay 1 JUNO to vendor X per agreement of 2026-05-15." \ --recipient juno1vendor... \ --amount 1000000 \ --key my-key \ --keyring-dir /workspace/.my-keyring ``` **Step 6 — Vote yes** (1-of-1, the agent's vote passes the proposal): ```bash PROPOSAL_ID=$(scripts/verify-tx.sh --txhash --rpc "$RPC" | awk '/proposal_id/{print $NF}' | head -1) junod tx wasm execute "$PROP_MOD" \ "{\"vote\":{\"proposal_id\":$PROPOSAL_ID,\"vote\":\"yes\"}}" \ --from my-key --chain-id juno-1 --node "$RPC" \ --keyring-backend test --keyring-dir /workspace/.my-keyring \ --gas auto --gas-adjustment 1.4 --gas-prices 0.075ujuno \ --yes ``` **Step 7 — Wait the timelock window.** The proposal status is now `passed` but execute is gated until `timelock_duration` elapses. During this window, `juno1vetoer...` can call `{"veto":{"proposal_id":N}}` and kill it. Check status: ```bash junod query wasm contract-state smart "$PROP_MOD" \ "{\"proposal\":{\"proposal_id\":$PROPOSAL_ID}}" --node "$RPC" -o json | jq '.data.proposal.status' # expect "passed" → after timelock expiry, anyone can execute ``` **Step 8 — Execute** (after the timelock expires, anyone — including the agent — can call execute): ```bash junod tx wasm execute "$PROP_MOD" \ "{\"execute\":{\"proposal_id\":$PROPOSAL_ID}}" \ --from my-key --chain-id juno-1 --node "$RPC" \ --keyring-backend test --keyring-dir /workspace/.my-keyring \ --gas auto --gas-adjustment 1.5 --gas-prices 0.075ujuno \ --yes scripts/verify-tx.sh --txhash --rpc "$RPC" # should show a transfer event: $DAO_CORE → juno1vendor... 1000000ujuno ``` The DAO has now made its first treasury disbursement under steward-veto governance. Subsequent proposals follow the same propose / vote / wait / execute loop. **Append-only audit log.** Record each broadcast (instantiate, fund, propose, vote, execute) to an `onchain-log.md` with timestamp, chain, txhash, recipient, amount, and intent. The chain is canonical for state; the log captures the *why*. **What you skipped that's worth coming back to:** - **cw-filter as a machine-readable mandate.** The DAO above accepts *any* proposal the agent can write. To bound what the agent is allowed to propose in the first place, instantiate cw-filter with a JSON policy (§cw-filter above) and have the agent's signer query it before signing each propose tx. That's the off-chain enforcement leg of the agent-mandate architecture; once `dao-proposal-wavs` lands, the same cw-filter check moves on-chain. - **Parent admin.** The sub-DAO above has `admin: null` — it cannot be migrated. For an operational sub-DAO, transfer admin to a parent stewards multisig (`junod tx wasm set-contract-admin $DAO_CORE `) so module upgrades can happen via governance. - **Migration upgrade path.** Once admin is set, the parent can migrate the proposal module to a future version (e.g., `dao-proposal-wavs` when it ships) without re-instantiating the DAO. ### dao-pre-propose-approval-single — explicit approval gate If you want stewards to *explicitly approve every proposal* (not just have a veto window), use this pre-propose module. Inversion of VetoConfig polarity: - **VetoConfig:** "passes by default, can be vetoed." - **ApprovalSingle:** "rejected by default, must be approved." When to use which: - **VetoConfig** — autonomous agent with steward fallback. Most agent-mandate scenarios. Lower friction. - **ApprovalSingle** — high-trust gating, every action sees a human. Higher friction, lower autonomy. **Critical caveat (per DAO DAO wiki):** if any pre-propose-module hook fails (panic, error), the proposal module *evicts* the pre-propose module and allows *anyone* to create proposals. For a 1-of-1 sub-DAO, this is dangerous — eviction means any address could spam proposals that auto-pass. The defense: don't use a pre-propose module unless you genuinely need the gate, and never on a 1-of-1 sub-DAO. VetoConfig achieves "humans in the loop" without this fail-open risk. ## Common foot-guns 1. **The `msg` field in nested proposals is base64-encoded JSON, not raw JSON.** When proposing a `wasm.execute` against another contract, the inner contract's ExecuteMsg goes through `jq -c . | base64 -w 0`. Forgetting this produces "InvalidMsg" errors that look mysterious. 2. **`delegation_module: null` is required on recent dao-proposal-single versions.** Older docs may omit it; new instantiate fails without it. 3. **`initial_actions: null` vs `[]`.** Both work; `null` is cleaner. The field exists for "messages to run at DAO genesis" — usually unused. 4. **Cw4 vs voting-cw4.** `cw4-group` is the raw group contract. `dao-voting-cw4` wraps it and exposes the DAO DAO voting interface. Instantiate `dao-voting-cw4`, which spawns (or accepts) a cw4-group inside. 5. **Multi-proposal-modules race.** If a DAO has multiple proposal modules, each has its own proposal_id sequence. A "proposal 5" in one module is unrelated to "proposal 5" in another. Always pair (module_addr, proposal_id). 6. **Voting power snapshots at proposal creation.** Once a proposal is open, your voting power is frozen at the proposal's `start_height`. Staking more tokens *after* the proposal is created doesn't increase your weight on it. ## Going further - DAO DAO UI source: https://github.com/DA0-DA0/dao-dao-ui — TypeScript clients for every contract, useful for learning message shapes. - DAO DAO contracts source: https://github.com/DA0-DA0/dao-contracts — Rust source + JSON schemas in `schema/` subdirs. - DAO DAO indexer: https://indexer.daodao.zone — for high-throughput historical queries (your RPC isn't built for it). - Polytone: https://github.com/DA0-DA0/polytone — cross-chain DAO DAO governance over IBC.