MSG Chain DAO 治理实战手册
版本:v1.0 | 主网状态:No-Go | 兼容链版本:msg-chain-1
目录
- DAO 治理概述
- Cosmos SDK 治理模块
- dao_governance_v1 智能合约
- 提案生命周期
- 投票策略与权重计算
- foundation_treasury_v2 金库多签
- 治理参数管理
- 前端集成指南
- CLI 命令参考
- 最佳实践与安全建议
1. DAO 治理概述
1.1 MSG Chain 的双层治理架构
MSG Chain 采用链上治理 + 合约治理的双层架构设计,兼顾底层协议升级的稳定性与上层业务治理的灵活性。
┌──────────────────────────────────────────────────┐
│ 第一层:Cosmos SDK gov │
│ ───────────────────────────────────── │
│ 用途:链参数修改、软件升级、社区池支出 │
│ 投票权重:基于 Stake 权重(1 msg = 1 票) │
│ 治理代币:umsg(原生 Staking 代币) │
│ 法定人数:33.4% │
│ 通过阈值:50% │
│ veto 阈值:33.4% │
└──────────────────────┬───────────────────────────┘
│
┌──────────────────────▼───────────────────────────┐
│ 第二层:dao_governance_v1 │
│ ───────────────────────────────────── │
│ 用途:DAO 内部决策、合约调用、参数配置 │
│ 投票权重:可自定义(代币余额、Stake、NFT 等) │
│ 投票方式:Yes / No / Abstain / Veto │
│ 执行方式:时间锁定时后自动执行 │
│ 金库控制:foundation_treasury_v2 多签 │
└──────────────────────────────────────────────────┘
1.2 何时使用哪一层
| 决策类型 | 使用层级 | 示例 |
|---|---|---|
| 链参数修改 | Cosmos SDK gov | 修改区块 Gas 上限、Validator 佣金率 |
| 软件升级 | Cosmos SDK gov | 升级链版本 v1.0 → v1.1 |
| 社区池支出 | Cosmos SDK gov | 从社区池拨款给生态项目 |
| DAO 内部治理 | dao_governance_v1 | 修改合约参数、金库支出提案 |
| 金库资金管理 | foundation_treasury_v2 | 多签批准资金转账 |
| AI Agent 行为约束 | ai_agent_constitution_v1 | 更新 Agent 行为准则 |
1.3 参与角色
| 角色 | 权限 | 第一层 | 第二层 |
|---|---|---|---|
| 验证者 (Validator) | 提交、投票、执行 | ✅ | — |
| 委托者 (Delegator) | 投票 | ✅ | — |
| DAO 成员 | 提交提案、投票 | — | ✅ |
| 金库签名人 (Treasury Signer) | 批准支出 | — | ✅ |
| 任意地址 | 提交带保证金的提案 | 取决于参数 | 取决于参数 |
2. Cosmos SDK 治理模块
2.1 模块架构
Cosmos SDK 的 gov 模块是 MSG Chain 的第一层治理基础设施,负责处理链级别的参数更改、软件升级和社区池支出。
x/gov 模块
├── Proposal
│ ├── TextProposal (纯文本提案)
│ ├── ParamChangeProposal (参数修改提案)
│ ├── SoftwareUpgradeProposal(软件升级提案)
│ └── CommunityPoolSpendProposal (社区池支出提案)
├── Vote
│ ├── VoteOption::Yes
│ ├── VoteOption::No
│ ├── VoteOption::Abstain
│ └── VoteOption::NoWithVeto
├── Deposit
│ ├── MinDeposit (最小保证金)
│ ├── MaxDepositPeriod (最大保证金存入期)
│ └── DepositParams
├── Tally
│ ├── Quorum (法定人数)
│ ├── Threshold (通过阈值)
│ └── VetoThreshold (否决阈值)
└── Execution
├── Passed (通过后自动执行)
└── Failed / Rejected (失败或拒绝)
2.2 提案类型详解
2.2.1 TextProposal(纯文本提案)
无实际链上执行逻辑,用于社区信号投票或宣言发布。
CLI 提交:
msgd tx gov submit-proposal --type="text" \
--title="建议升级 MSG Chain 至 v1.1" \
--description="本次升级将引入 ABCI 2.0 支持..." \
--deposit="1000000umsg" \
--from=my_validator \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 \
--fees=2500umsg
REST API:
POST /cosmos/gov/v1beta1/proposals
{
"content": {
"@type": "/cosmos.gov.v1beta1.TextProposal",
"title": "建议升级 MSG Chain 至 v1.1",
"description": "本次升级将引入 ABCI 2.0 支持..."
},
"initial_deposit": [
{"denom": "umsg", "amount": "1000000"}
],
"proposer": "msg1qypqxpq9kcrn2c9afea5lq35ef37c5x7jqylz3"
}
2.2.2 ParamChangeProposal(参数修改提案)
修改链上模块参数。每个参数变更需指定 subspace(模块名)和 key(参数名)。
可用参数列表:
| 模块 (subspace) | 参数 (key) | 当前值 | 说明 |
|---|---|---|---|
| staking | MaxValidators | 100 | 最大验证者数量 |
| staking | UnbondingTime | 1814400s (21天) | 解委托期 |
| staking | MaxEntries | 7 | 最大解委托条目 |
| distribution | communitytax | 0.02 | 社区池税收比例 |
| distribution | baseproposerreward | 0.01 | 基础区块提议者奖励 |
| distribution | bonusproposerreward | 0.04 | 额外区块提议者奖励 |
| mint | InflationRateChange | 0.13 | 年通胀率变化率 |
| mint | InflationMax | 0.20 | 最大年通胀率 |
| mint | InflationMin | 0.07 | 最小年通胀率 |
| gov | min_deposit | 10000000umsg | 最小保证金 |
| gov | max_deposit_period | 172800s (2天) | 最大保证金存入期 |
| gov | voting_period | 604800s (7天) | 投票期 |
| gov | quorum | 0.334 | 法定人数比例 |
| gov | threshold | 0.500 | 通过阈值 |
| gov | veto_threshold | 0.334 | 否决阈值 |
| slashing | signed_blocks_window | 10000 | 签名窗口 |
| slashing | min_signed_per_window | 0.05 | 最低签名率 |
CLI 提交:
msgd tx gov submit-proposal param-change \/
--title="调整最小保证金至 500 MSG" \
--description="降低提案门槛以促进社区参与" \
--proposal="{\"title\":\"调整最小保证金至 500 MSG\",\"description\":\"降低提案门槛以促进社区参与\",\"changes\":[{\"subspace\":\"gov\",\"key\":\"min_deposit\",\"value\":\"\\\"500000000umsg\\\"\"}],\"deposit\":[{\"denom\":\"umsg\",\"amount\":\"1000000\"}]}" \
--deposit="1000000umsg" \
--from=my_validator \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5
注意: ParamChange 的 value 字段必须是 JSON 字符串,需要用双引号包裹数字或字符串值。
2.2.3 SoftwareUpgradeProposal(软件升级提案)
触发链上软件升级流程。升级将在指定的 height 处自动执行。
CLI 提交:
msgd tx gov submit-proposal software-upgrade v1.1.0 \
--title="升级至 MSG Chain v1.1.0" \
--description="本次升级内容:\n1. 引入 ABCI 2.0\n2. 优化 Dilithium-5 签名验证\n3. 修复 x/gov 模块投票计数问题" \
--upgrade-height=8500000 \
--upgrade-info="{\"binaries\":{\"linux/amd64\":\"https://github.com/msgchain/mainnet/releases/download/v1.1.0/msgd-linux-amd64.zip?checksum=sha256:...\"}}" \
--deposit="1000000umsg" \
--from=my_validator \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5
升级流程:
提交提案 → 投票期(7天) → 通过 → 存储升级信息 →
到达指定高度 → 链停止 → 替换二进制 → 重启 → 新版本运行
验证升级状态:
# 查看当前升级计划
msgd query upgrade plan --chain-id=msg-chain-1
# 查询是否已应用升级
msgd query upgrade applied v1.1.0 --chain-id=msg-chain-1
2.2.4 CommunityPoolSpendProposal(社区池支出提案)
从社区池中提取资金到指定地址。
CLI 提交:
msgd tx gov submit-proposal community-pool-spend \
--title="资助 MSG Chain 开发者社区" \
--description="资助开发者社区举办黑客松活动" \
--recipient="msg1qypqxpq9kcrn2c9afea5lq35ef37c5x7jqylz3" \
--amount="5000000000umsg" \
--deposit="1000000umsg" \
--from=my_validator \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5
检查社区池余额:
# 查询社区池余额
msgd query distribution community-pool --chain-id=msg-chain-1
# 输出示例:
# pool: 1250000000000umsg
2.3 提案状态机
┌──────────┐
│ Submitted │
└─────┬────┘
│ 保证金 >= MinDeposit
▼
┌────────────────┐
┌───────┤ DepositPeriod ├────────┐
│ └───────┬────────┘ │
│ 保证金不足 │ 保证金达标 │ 超时未达标
▼ ▼ ▼
┌──────────┐ ┌──────────────┐ ┌──────────┐
│ 继续存款 │ │ VotingPeriod │ │ Rejected │
└──────────┘ └──────┬───────┘ └──────────┘
│ 投票期结束
▼
┌─────────────────────┐
│ Tally (计票) │
└──────┬──────┬───────┘
│ │
通过条件 未通过条件
│ │
▼ ▼
┌────────┐ ┌──────────┐
│ Passed │ │ Rejected │
└───┬────┘ └──────────┘
│ 自动执行或时间锁
▼
┌──────────────┐
│ Executed / │
│ Implemented │
└──────────────┘
状态枚举(Proto 定义):
enum ProposalStatus {
PROPOSAL_STATUS_UNSPECIFIED = 0; // 未指定
PROPOSAL_STATUS_DEPOSIT_PERIOD = 1; // 保证金存入期
PROPOSAL_STATUS_VOTING_PERIOD = 2; // 投票期
PROPOSAL_STATUS_PASSED = 3; // 通过
PROPOSAL_STATUS_REJECTED = 4; // 拒绝
PROPOSAL_STATUS_FAILED = 5; // 失败(未达法定人数等)
}
2.4 投票权重计算
Cosmos SDK gov 模块的投票权重基于 Stake 数量:
投票权重 = 委托金额 + 自委托金额
权重计算规则:
- 1 umsg = 1 票
- 验证者的票 = 自委托 + 所有委托给该验证者的票(委托者未自行投票时)
- 委托者可以独立投票,覆盖验证者的投票
- 未投票的委托者:票权默认由验证者代投
查询验证者投票权重:
# 查询验证者详细信息(含委托量)
msgd query staking validator \
msgvaloper1qypqxpq9kcrn2c9afea5lq35ef37c5x7jqylz3 \
--chain-id=msg-chain-1
# 查询委托者投票权重
msgd query staking delegations-to \
msgvaloper1qypqxpq9kcrn2c9afea5lq35ef37c5x7jqylz3 \
--chain-id=msg-chain-1
2.5 计票逻辑
通过条件 (全部满足):
1. Quorum (法定人数): 投票总票数 >= 总 Stake × 33.4%
2. Threshold (通过阈值): Yes票数 / (Yes + No + Veto) >= 50%
3. Veto (否决保护): Veto票数 / 总投票数 <= 33.4%
计算公式:
- 参与投票率 = 总投票数 / 总 Stake 数
- Yes 比例 = Yes / (Yes + No + NoWithVeto)
- Veto 比例 = NoWithVeto / (Yes + No + NoWithVeto + Abstain)
(注意:Abstain 会计入法定人数但不会计入 Yes/No 比例分母)
计票示例:
总 Stake: 1,000,000 MSG
投票结果:
Yes: 400,000 MSG
No: 150,000 MSG
Veto: 50,000 MSG
Abstain: 80,000 MSG
未投票: 320,000 MSG
1. 法定人数: (400k + 150k + 50k + 80k) / 1,000k = 68% ✅ (>= 33.4%)
2. 通过阈值: 400k / (400k + 150k + 50k) = 66.7% ✅ (>= 50%)
3. 否决阈值: 50k / 680k = 7.4% ✅ (<= 33.4%)
结果: ✅ 通过
3. dao_governance_v1 智能合约
3.1 合约概述
dao_governance_v1 是 MSG Chain 第二层治理的核心合约,提供灵活的 DAO 治理功能。与 Cosmos SDK gov 模块的 Stake 权重不同,该合约支持多种投票权重策略。
合约信息:
- 合约名称:
dao_governance_v1 - Canonical Key:
dao_governance - 部署方式:Genesis 部署
- 合约地址:通过
genesis_registry_v1解析dao_governance获取
3.2 ExecuteMsg 接口
use cosmwasm_schema::cw_serde;
#[cw_serde]
pub enum ExecuteMsg {
/// 提交新提案
SubmitProposal {
/// 提案标题
title: String,
/// 提案描述
description: String,
/// 目标合约地址(可选,用于合约调用提案)
target_contract: Option<String>,
/// 要执行的 JSON 编码消息(可选)
execute_msg: Option<String>,
/// 保证金金额(可选,字符串格式)
deposit_amount: Option<String>,
},
/// 对提案投票
Vote {
/// 提案 ID
proposal_id: u64,
/// 投票选项
vote: VoteOption,
},
/// 执行已通过的提案
ExecuteProposal {
/// 提案 ID
proposal_id: u64,
},
/// 取消提案(仅提案人可操作)
CancelProposal {
/// 提案 ID
proposal_id: u64,
},
/// 更新治理参数(仅管理员/DAO 可操作)
UpdateConfig {
/// 法定人数比例(字符串,如 "0.334")
quorum: Option<String>,
/// 投票期(秒)
voting_period: Option<u64>,
/// 通过阈值(字符串,如 "0.5")
threshold: Option<String>,
},
}
#[cw_serde]
pub enum VoteOption {
Yes,
No,
Abstain,
Veto,
}
3.3 QueryMsg 接口
#[cw_serde]
pub enum QueryMsg {
/// 查询单个提案详情
GetProposal {
proposal_id: u64,
},
/// 列表查询提案(支持按状态过滤和分页)
ListProposals {
/// 过滤状态(可选):active / passed / rejected / executed / canceled
status: Option<String>,
/// 分页起始 ID(可选)
start_after: Option<u64>,
/// 返回数量限制(可选,默认 30)
limit: Option<u32>,
},
/// 查询某个投票
GetVote {
proposal_id: u64,
voter: String,
},
/// 查询合约配置参数
GetConfig {},
/// 查询提案计票结果
GetProposalTally {
proposal_id: u64,
},
}
3.4 响应数据结构
#[cw_serde]
pub struct ProposalResponse {
pub id: u64,
pub title: String,
pub description: String,
pub proposer: String,
pub status: String,
/// 创建时间戳(秒)
pub created_at: u64,
/// 投票开始时间戳(秒)
pub voting_start: Option<u64>,
/// 投票结束时间戳(秒)
pub voting_end: Option<u64>,
/// 目标合约地址
pub target_contract: Option<String>,
/// 要执行的合约消息
pub execute_msg: Option<String>,
/// 保证金金额
pub deposit_amount: String,
}
#[cw_serde]
pub struct TallyResponse {
pub proposal_id: u64,
pub yes: String,
pub no: String,
pub abstain: String,
pub veto: String,
pub total_power: String,
pub turnout: String,
pub status: String,
}
#[cw_serde]
pub struct ConfigResponse {
pub quorum: String,
pub voting_period: u64,
pub threshold: String,
pub owner: String,
pub proposal_count: u64,
pub timelock_duration: u64,
}
#[cw_serde]
pub struct VoteResponse {
pub proposal_id: u64,
pub voter: String,
pub vote: VoteOption,
pub weight: String,
pub voted_at: u64,
}
#[cw_serde]
pub struct ListProposalsResponse {
pub proposals: Vec<ProposalResponse>,
}
3.5 合约地址解析
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
const GENESIS_REGISTRY = "msg1...genesisRegistryAddress";
async function resolveDaoContract(
client: CosmWasmClient
): Promise<{ governance: string; treasury: string }> {
const governance = await client.queryContractSmart(
GENESIS_REGISTRY,
{ resolve_canonical: { key: "dao_governance" } }
);
const treasury = await client.queryContractSmart(
GENESIS_REGISTRY,
{ resolve_canonical: { key: "foundation_treasury" } }
);
return {
governance: governance.address,
treasury: treasury.address,
};
}
4. 提案生命周期
4.1 全生命周期流程图
┌─────────────────────────────────────────────────────────┐
│ 提案生命周期 │
├─────────────────────────────────────────────────────────┤
│ │
│ 1. 提交提案 (SubmitProposal) │
│ ├─ 提交者支付保证金 │
│ └─ 进入投票期或等待期 │
│ │
│ 2. 投票期 (Voting Period) │
│ ├─ DAO 成员投票 (Yes/No/Abstain/Veto) │
│ ├─ 投票权重根据策略计算 │
│ └─ 投票期内可更改投票 │
│ │
│ 3. 计票 (Tally) │
│ ├─ 检查法定人数 (Quorum) │
│ ├─ 检查通过阈值 (Threshold) │
│ └─ 检查否决阈值 (Veto Threshold) │
│ │
│ 4. 时间锁 (Timelock) [DAO 合约独有] │
│ ├─ 通过后进入时间锁等待期 │
│ └─ 期间可取消执行 │
│ │
│ 5. 执行 (Execute) │
│ ├─ 自动执行或手动执行 │
│ ├─ 执行目标合约消息 │
│ └─ 或更新合约参数 │
│ │
│ 6. 完成 (Completed) / 取消 (Canceled) │
│ │
└─────────────────────────────────────────────────────────┘
4.2 阶段一:提交提案
4.2.1 提交文本型提案
msgd tx wasm execute <dao_governance_address> \
'{"submit_proposal":{
"title":"增加 DAO 金库多元化策略",
"description":"建议将金库资产分散配置至稳定币池以降低波动风险。具体方案:1. 将 30% USDC 存入 AMM 流动性池 2. 保留 50% MSG 用于生态资助 3. 20% 转入跨链桥储备"
}}' \
--amount="1000000umsg" \
--from=dao_member \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 \
--fees=2500umsg
4.2.2 提交合约执行型提案
当提案通过后需要自动调用另一个合约时,使用 target_contract 和 execute_msg 字段。
msgd tx wasm execute <dao_governance_address> \
'{"submit_proposal":{
"title":"更新 DAO 治理参数",
"description":"将法定人数从 20% 降低至 15% 以提高治理效率",
"target_contract":"<dao_governance_address>",
"execute_msg":"{\"update_config\":{\"quorum\":\"0.15\"}}",
"deposit_amount":"5000000"
}}' \
--amount="5000000umsg" \
--from=dao_admin \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 \
--fees=2500umsg
前端实现:
interface SubmitProposalArgs {
title: string;
description: string;
targetContract?: string;
executeMsg?: string;
depositAmount?: string;
}
async function submitProposal(
client: SigningCosmWasmClient,
daoGovernanceAddress: string,
args: SubmitProposalArgs,
senderAddress: string
) {
const msg = {
submit_proposal: {
title: args.title,
description: args.description,
target_contract: args.targetContract || null,
execute_msg: args.executeMsg || null,
deposit_amount: args.depositAmount || null,
},
};
const funds = args.depositAmount
? [{ denom: "umsg", amount: args.depositAmount }]
: [];
const tx = await client.execute(
senderAddress,
daoGovernanceAddress,
msg,
"auto",
undefined,
funds
);
return tx.transactionHash;
}
4.3 阶段二:投票
4.3.1 CLI 投票
# 投赞成票
msgd tx wasm execute <dao_governance_address> \
'{"vote":{"proposal_id":1,"vote":"yes"}}' \
--from=dao_member \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 \
--fees=2500umsg
# 投反对票
msgd tx wasm execute <dao_governance_address> \
'{"vote":{"proposal_id":1,"vote":"no"}}' \
--from=dao_member \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 \
--fees=2500umsg
# 投弃权票
msgd tx wasm execute <dao_governance_address> \
'{"vote":{"proposal_id":1,"vote":"abstain"}}' \
--from=dao_member \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 \
--fees=2500umsg
# 投否决票
msgd tx wasm execute <dao_governance_address> \
'{"vote":{"proposal_id":1,"vote":"veto"}}' \
--from=dao_member \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 \
--fees=2500umsg
4.3.2 前端投票组件
interface VoteArgs {
proposalId: number;
vote: "yes" | "no" | "abstain" | "veto";
}
async function castVote(
client: SigningCosmWasmClient,
daoGovernanceAddress: string,
args: VoteArgs,
senderAddress: string
) {
const msg = {
vote: {
proposal_id: args.proposalId,
vote: args.vote,
},
};
const tx = await client.execute(
senderAddress,
daoGovernanceAddress,
msg,
"auto"
);
return tx.transactionHash;
}
4.4 阶段三:计票
计票在投票期结束后自动触发,任何人都可以调用 ExecuteProposal 来执行通过后的提案。
4.4.1 查询计票结果
msgd query wasm contract-state smart <dao_governance_address> \
'{"get_proposal_tally":{"proposal_id":1}}' \
--chain-id=msg-chain-1
响应示例:
{
"data": {
"yes": "450000000000",
"no": "120000000000",
"abstain": "30000000000",
"veto": "80000000000",
"total_power": "1000000000000",
"turnout": "68.0",
"status": "passed"
}
}
4.5 阶段四:执行提案
4.5.1 CLI 执行
msgd tx wasm execute <dao_governance_address> \
'{"execute_proposal":{"proposal_id":1}}' \
--from=any_address \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 \
--fees=2500umsg
4.5.2 自动执行脚本
#!/bin/bash
# auto_execute_proposals.sh
# 自动检查并执行已通过的 DAO 提案
DAO_GOVERNANCE="msg1...daoGovernanceAddress"
CHAIN_ID="msg-chain-1"
FROM="my_executor"
NODE="http://localhost:26657"
# 查询所有已通过的提案
PASSED_PROPOSALS=$(msgd query wasm contract-state smart "$DAO_GOVERNANCE" \
'{"list_proposals":{"status":"passed"}}' \
--chain-id="$CHAIN_ID" \
--node="$NODE" \
--output=json | jq -r '.data.proposals[].id')
for PROPOSAL_ID in $PASSED_PROPOSALS; do
echo "Executing proposal #$PROPOSAL_ID..."
msgd tx wasm execute "$DAO_GOVERNANCE" \
"{\"execute_proposal\":{\"proposal_id\":$PROPOSAL_ID}}" \
--from="$FROM" \
--chain-id="$CHAIN_ID" \
--node="$NODE" \
--gas=auto --gas-adjustment=1.5 \
--fees=2500umsg \
--yes
if [ $? -eq 0 ]; then
echo "✅ Proposal #$PROPOSAL_ID executed successfully"
else
echo "❌ Failed to execute proposal #$PROPOSAL_ID"
fi
sleep 2
done
4.6 阶段五:取消提案
仅提案人可以取消处于投票期内的提案。
msgd tx wasm execute <dao_governance_address> \
'{"cancel_proposal":{"proposal_id":1}}' \
--from=proposer_address \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 \
--fees=2500umsg
5. 投票策略与权重计算
5.1 投票权重策略概览
MSG Chain 的 dao_governance_v1 支持可配置的投票权重策略。默认基于代币余额(类似 Compound GovernorBravo 模型)。
| 策略 | 权重依据 | 适用场景 |
|---|---|---|
| TokenBalance | 钱包中 MSG 代币余额 | 标准的代币持有者治理 |
| StakedBalance | 委托给验证者的 Stake 数量 | 需要长期承诺的场景 |
| Quadratic | 余额的平方根 | 防止鲸鱼垄断(Vitalik 倡导) |
| OnePersonOneVote | 每个地址 1 票 | 社区平等治理 |
| WeightedMultiplier | 基础权重 × 倍数因子 | 基于信誉或参与度的治理 |
| NFTGated | 持有特定 NFT 的地址 | NFT 社区的专属治理 |
5.2 默认投票权重(TokenBalance)
投票权重 = 地址在快照时点的 MSG 余额
单位:1 umsg = 1 票
快照时间:提案提交时的区块高度
权重查询示例:
# 查询地址的当前 MSG 余额
msgd query bank balances \
msg1qypqxpq9kcrn2c9afea5lq35ef37c5x7jqylz3 \
--chain-id=msg-chain-1 \
--output=json | jq '.balances[] | select(.denom=="umsg")'
# 查询特定区块高度的余额(历史余额)
msgd query bank balances \
msg1qypqxpq9kcrn2c9afea5lq35ef37c5x7jqylz3 \
--height=1234567 \
--chain-id=msg-chain-1
5.3 二次方投票(Quadratic Voting)
如需引入二次方投票,可通过合约升级或治理提案修改投票逻辑——当前 dao_governance_v1 暂未内置二次方计算,可以在前端模拟显示或通过扩展合约实现。
二次方权重计算:
投票权重 = floor(sqrt(balance_in_umsg))
示例:
地址 A: 1,000,000 MSG → 权重 = sqrt(1,000,000) = 1,000
地址 B: 100,000 MSG → 权重 = sqrt(100,000) = 316
地址 C: 1,000 MSG → 权重 = sqrt(1,000) = 31
对比(线性):
地址 A: 1,000,000 票 vs 地址 C: 1,000 票 (差距 1000x)
对比(二次方):
地址 A: 1,000 票 vs 地址 C: 31 票 (差距 32x)
5.4 委托投票(Delegation Voting)
参考 Compound GovernorBravo 模型,DAO 可以引入委托投票机制:
委托投票流程:
1. 代币持有者 A 将投票权委托给地址 B
2. 地址 B 的投票权重 = B 自有权重 + Σ(委托给 B 的权重)
3. 委托可以随时取消或转委托
4. 每个地址最多只能被委托一次
委托合约接口示例(扩展实现):
#[cw_serde]
pub enum DelegateExecuteMsg {
/// 委托投票权
Delegate {
/// 被委托人地址
representative: String,
},
/// 取消委托
Undelegate {},
}
#[cw_serde]
pub enum DelegateQueryMsg {
/// 查询地址的当前委托信息
GetDelegation { address: String },
/// 查询地址的实际投票权重(含委托)
GetVotingPower { address: String, block_height: u64 },
}
5.5 法定人数与通过阈值计算
季度场景:DAO 有 1,000,000 MSG 的总投票权重
场景 A:高参与度
投票:600,000 MSG (60%)
Yes: 400,000 No: 150,000 Veto: 50,000
法定人数: 60% >= 33.4% ✅
通过率: 400k/(400k+150k+50k) = 66.7% >= 50.0% ✅
否决率: 50k/600k = 8.3% <= 33.4% ✅
结果: ✅ 通过
场景 B:低参与度
投票:250,000 MSG (25%)
Yes: 200,000 No: 30,000 Veto: 20,000
法定人数: 25% < 33.4% ❌
结果: ❌ 失败(未达法定人数)
场景 C:高否决率
投票:500,000 MSG (50%)
Yes: 250,000 No: 50,000 Veto: 200,000
法定人数: 50% >= 33.4% ✅
通过率: 250k/(250k+50k+200k) = 50.0% >= 50.0% ✅
否决率: 200k/500k = 40% > 33.4% ❌
结果: ❌ 否决
5.6 与 Compound GovernorBravo 对比
| 特性 | MSG DAO | Compound Governance | Cosmos Hub |
|---|---|---|---|
| 投票代币 | MSG (umsg) | COMP | ATOM |
| 权重依据 | 余额/配置策略 | 委托余额 | Stake 权重 |
| 提案门槛 | 保证金 | 100,000 COMP | 500 ATOM |
| 投票期 | 可配置 (默认 7天) | 3天 | 14天 |
| 时间锁 | 可配置 (默认 2天) | 2天 | — |
| 法定人数 | 可配置 (默认 33.4%) | 4% | 40% |
| 通过阈值 | 可配置 (默认 50%) | 50% | 50% |
| 否决阈值 | 可配置 (默认 33.4%) | — | 33.4% |
| 执行方式 | 任何人触发 | 时间锁自动 | 链自动执行 |
| 取消 | 仅提案人 | DAO 投票取消 | 无 |
6. foundation_treasury_v2 金库多签
6.1 合约概述
foundation_treasury_v2 是 MSG Chain 的金库多签合约,类似于 ERC-1155 多签或 Gnosis Safe,但完全在 CosmWasm 上原生实现。
合约信息:
- 合约名称:
foundation_treasury_v2 - Canonical Key:
foundation_treasury - 部署方式:Genesis 部署
- 签名人数量:至少 3 人(可配置)
- 通过阈值:签名人总数的 60%(可配置)
6.2 ExecuteMsg 接口
#[cw_serde]
pub enum ExecuteMsg {
/// 提交支出提案
ProposeSpend {
/// 收款地址
recipient: String,
/// 金额(最小单位)
amount: Uint128,
/// 代币类型
denom: String,
/// 支出用途说明
purpose: String,
},
/// 批准支出提案
ApproveSpend { proposal_id: u64 },
/// 执行已经批准的支出
ExecuteSpend { proposal_id: u64 },
/// 添加金库签名人
AddSigner { address: String },
/// 移除金库签名人
RemoveSigner { address: String },
}
6.3 QueryMsg 接口
#[cw_serde]
pub enum QueryMsg {
/// 查询金库余额
GetBalance {
/// 代币类型(可选,不传时返回所有余额)
denom: Option<String>,
},
/// 查询单个支出提案
GetProposal { proposal_id: u64 },
/// 列表查询支出提案
ListProposals {
status: Option<String>,
start_after: Option<u64>,
limit: Option<u32>,
},
/// 获取所有签名人列表
GetSigners {},
/// 获取合约配置
GetConfig {},
}
6.4 金库支出工作流
支出提案流程:
┌──────────────┐
│ ProposeSpend │ ← 金库签名人 A 提交支出提案
└──────┬───────┘
│
┌──────▼───────┐
│ ApproveSpend │ ← 金库签名人 B 批准(需要 ≥ 60% 签名人批准)
│ ApproveSpend │ ← 金库签名人 C 批准
└──────┬───────┘
│ 达到阈值
┌──────▼───────┐
│ ExecuteSpend │ ← 任何签名人执行转账
└──────┬───────┘
│
┌──────▼───────┐
│ Transfer │ ← 资金从金库转移到 recipient
└──────────────┘
6.5 CLI 操作示例
6.5.1 提交支出提案
msgd tx wasm execute <treasury_address> \
'{"propose_spend":{
"recipient":"msg1qypqxpq9kcrn2c9afea5lq35ef37c5x7jqylz3",
"amount":"5000000000",
"denom":"umsg",
"purpose":"Q3 开发团队赏金支出"
}}' \
--from=signer_a \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 \
--fees=2500umsg
6.5.2 批准支出提案
# 签名人 B 批准
msgd tx wasm execute <treasury_address> \
'{"approve_spend":{"proposal_id":1}}' \
--from=signer_b \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 \
--fees=2500umsg
# 签名人 C 批准
msgd tx wasm execute <treasury_address> \
'{"approve_spend":{"proposal_id":1}}' \
--from=signer_c \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 \
--fees=2500umsg
6.5.3 执行支出提案
msgd tx wasm execute <treasury_address> \
'{"execute_spend":{"proposal_id":1}}' \
--from=signer_a \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 \
--fees=2500umsg
6.6 金库管理
6.6.1 查询金库余额
msgd query wasm contract-state smart <treasury_address> \
'{"get_balance":{"denom":"umsg"}}' \
--chain-id=msg-chain-1
响应示例:
{
"data": {
"denom": "umsg",
"balance": "50000000000000"
}
}
6.6.2 查询签名人列表
msgd query wasm contract-state smart <treasury_address> \
'{"get_signers":{}}' \
--chain-id=msg-chain-1
响应示例:
{
"data": {
"signers": [
"msg1qypqxpq9kcrn2c9afea5lq35ef37c5x7jqylz3",
"msg1x2y3z4...",
"msg1a2b3c4..."
],
"threshold": "2",
"total_signers": 3
}
}
6.6.3 添加/移除签名人
# 添加签名人(需要现有签名人执行)
msgd tx wasm execute <treasury_address> \
'{"add_signer":{"address":"msg1new_signer_address"}}' \
--from=signer_a \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 \
--fees=2500umsg
# 移除签名人(需要现有签名人执行)
msgd tx wasm execute <treasury_address> \
'{"remove_signer":{"address":"msg1old_signer_address"}}' \
--from=signer_a \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 \
--fees=2500umsg
6.7 前端集成
interface ProposeSpendArgs {
recipient: string;
amount: string;
denom: string;
purpose: string;
}
interface TreasuryProposal {
id: number;
recipient: string;
amount: string;
denom: string;
purpose: string;
status: string;
approvers: string[];
created_at: number;
}
const TREASURY_QUERY_MSG = {
get_balance: { denom: "umsg" },
get_signers: {},
get_config: {},
get_proposal: { proposal_id: 1 },
list_proposals: { status: "pending", limit: 20 },
};
async function fetchTreasuryBalance(
client: CosmWasmClient,
treasuryAddress: string
): Promise<string> {
const result: any = await client.queryContractSmart(
treasuryAddress,
{ get_balance: { denom: "umsg" } }
);
return result.balance;
}
async function proposeSpend(
client: SigningCosmWasmClient,
treasuryAddress: string,
args: ProposeSpendArgs,
senderAddress: string
) {
const msg = {
propose_spend: {
recipient: args.recipient,
amount: args.amount,
denom: args.denom,
purpose: args.purpose,
},
};
const tx = await client.execute(
senderAddress,
treasuryAddress,
msg,
"auto"
);
return tx.transactionHash;
}
async function approveSpend(
client: SigningCosmWasmClient,
treasuryAddress: string,
proposalId: number,
senderAddress: string
) {
const msg = {
approve_spend: { proposal_id: proposalId },
};
const tx = await client.execute(
senderAddress,
treasuryAddress,
msg,
"auto"
);
return tx.transactionHash;
}
async function executeSpend(
client: SigningCosmWasmClient,
treasuryAddress: string,
proposalId: number,
senderAddress: string
) {
const msg = {
execute_spend: { proposal_id: proposalId },
};
const tx = await client.execute(
senderAddress,
treasuryAddress,
msg,
"auto"
);
return tx.transactionHash;
}
7. 治理参数管理
7.1 参数总览
7.1.1 Cosmos SDK gov 模块参数
# 查询当前治理参数
msgd query gov params --chain-id=msg-chain-1
# 查询特定参数子集
msgd query gov deposit-params --chain-id=msg-chain-1
msgd query gov voting-params --chain-id=msg-chain-1
msgd query gov tally-params --chain-id=msg-chain-1
输出示例:
{
"deposit_params": {
"min_deposit": [
{"denom": "umsg", "amount": "10000000"}
],
"max_deposit_period": "172800s"
},
"voting_params": {
"voting_period": "604800s"
},
"tally_params": {
"quorum": "0.334000000000000000",
"threshold": "0.500000000000000000",
"veto_threshold": "0.334000000000000000"
}
}
7.1.2 dao_governance_v1 合约参数
msgd query wasm contract-state smart <dao_governance_address> \
'{"get_config":{}}' \
--chain-id=msg-chain-1
输出示例:
{
"data": {
"owner": "msg1admin_address",
"quorum": "0.334",
"voting_period": 604800,
"threshold": "0.5",
"proposal_count": 42,
"timelock_duration": 172800
}
}
7.2 参数调整方法
方法一:通过 DAO 提案修改合约参数
# 提交修改合约参数的提案
msgd tx wasm execute <dao_governance_address> \
'{"submit_proposal":{
"title":"调整 DAO 投票期为 3 天",
"description":"为了提高治理效率,建议将投票期从 7 天缩短至 3 天",
"target_contract":"<dao_governance_address>",
"execute_msg":"{\"update_config\":{\"voting_period\":\"259200\"}}",
"deposit_amount":"5000000"
}}' \
--amount="5000000umsg" \
--from=dao_member \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 \
--fees=2500umsg
方法二:使用脚本批量查询参数
#!/bin/bash
# check_dao_params.sh - 检查 DAO 治理参数
DAO_GOVERNANCE="msg1...daoGovernanceAddress"
TREASURY="msg1...treasuryAddress"
CHAIN_ID="msg-chain-1"
NODE="http://localhost:26657"
echo "═══════════════════════════════════════════"
echo " MSG Chain DAO 治理参数检查"
echo "═══════════════════════════════════════════"
# 1. Cosmos SDK Gov 模块参数
echo ""
echo "── Cosmos SDK Gov 模块参数 ──"
msgd query gov params --chain-id="$CHAIN_ID" --node="$NODE" --output=json | jq '{
min_deposit: .deposit_params.min_deposit,
max_deposit_period: .deposit_params.max_deposit_period,
voting_period: .voting_params.voting_period,
quorum: .tally_params.quorum,
threshold: .tally_params.threshold,
veto_threshold: .tally_params.veto_threshold
}'
# 2. DAO 治理合约参数
echo ""
echo "── DAO 治理合约参数 ──"
msgd query wasm contract-state smart "$DAO_GOVERNANCE" \
'{"get_config":{}}' \
--chain-id="$CHAIN_ID" --node="$NODE" --output=json
# 3. 金库合约参数与余额
echo ""
echo "── 金库合约参数 ──"
msgd query wasm contract-state smart "$TREASURY" \
'{"get_config":{}}' \
--chain-id="$CHAIN_ID" --node="$NODE" --output=json
echo ""
echo "── 金库余额 ──"
msgd query wasm contract-state smart "$TREASURY" \
'{"get_balance":{"denom":"umsg"}}' \
--chain-id="$CHAIN_ID" --node="$NODE" --output=json
echo ""
echo "── 金库签名人列表 ──"
msgd query wasm contract-state smart "$TREASURY" \
'{"get_signers":{}}' \
--chain-id="$CHAIN_ID" --node="$NODE" --output=json
echo ""
echo "═══════════════════════════════════════════"
7.3 参数优化建议
| 场景 | Quorum | Threshold | Voting Period | Timelock |
|---|---|---|---|---|
| 启动阶段 | 15% | 50% | 3天 | 1天 |
| 成熟 DAO | 20% | 50% | 5天 | 2天 |
| 紧急治理 | 30% | 60% | 2天 | 6小时 |
| 保守治理 | 40% | 66.7% | 7天 | 3天 |
| Compound 风格 | 4% | 50% | 3天 | 2天 |
8. 前端集成指南
8.1 环境与依赖
# 安装依赖
npm install @cosmjs/cosmwasm-stargate @cosmjs/proto-signing @cosmjs/stargate @cosmjs/amino
npm install @keplr-wallet/types # Keplr 钱包类型
8.2 DAO 治理 Provider
// dao/daoProvider.tsx
import React, { createContext, useContext, useState, useEffect, useCallback } from "react";
import { SigningCosmWasmClient, CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { OfflineSigner } from "@cosmjs/proto-signing";
interface DaoConfig {
quorum: string;
votingPeriod: number;
threshold: string;
owner: string;
proposalCount: number;
timelockDuration: number;
}
interface DaoContextType {
client: CosmWasmClient | null;
signingClient: SigningCosmWasmClient | null;
daoAddress: string | null;
treasuryAddress: string | null;
config: DaoConfig | null;
address: string | null;
isConnected: boolean;
connect: () => Promise<void>;
disconnect: () => void;
}
const DaoContext = createContext<DaoContextType>({
client: null,
signingClient: null,
daoAddress: null,
treasuryAddress: null,
config: null,
address: null as string | null,
isConnected: false,
connect: async () => {},
disconnect: () => {},
});
const CHAIN_CONFIG = {
chainId: "msg-chain-1",
rpcEndpoint: "https://rpc.msgchain.org",
prefix: "msg",
denom: "umsg",
gasPrice: "1000000000attoMSG",
};
const GENESIS_REGISTRY = "msg1...genesisRegistryAddress";
export function DaoProvider({ children }: { children: React.ReactNode }) {
const [client, setClient] = useState<CosmWasmClient | null>(null);
const [signingClient, setSigningClient] = useState<SigningCosmWasmClient | null>(null);
const [daoAddress, setDaoAddress] = useState<string | null>(null);
const [treasuryAddress, setTreasuryAddress] = useState<string | null>(null);
const [config, setConfig] = useState<DaoConfig | null>(null);
const [address, setAddress] = useState<string | null>(null);
const [isConnected, setIsConnected] = useState(false);
const connect = useCallback(async () => {
try {
if (!window.keplr) {
throw new Error("请安装 Keplr 钱包");
}
await window.keplr.enable(CHAIN_CONFIG.chainId);
const offlineSigner = window.keplr.getOfflineSigner(CHAIN_CONFIG.chainId);
const accounts = await offlineSigner.getAccounts();
const userAddress = accounts[0].address;
const cosmClient = await CosmWasmClient.connect(CHAIN_CONFIG.rpcEndpoint);
const signClient = await SigningCosmWasmClient.connectWithSigner(
CHAIN_CONFIG.rpcEndpoint,
offlineSigner,
{ gasPrice: { denom: CHAIN_CONFIG.denom, amount: "1000000000" } }
);
// 解析合约地址
const govResult: any = await cosmClient.queryContractSmart(
GENESIS_REGISTRY,
{ resolve_canonical: { key: "dao_governance" } }
);
const treasuryResult: any = await cosmClient.queryContractSmart(
GENESIS_REGISTRY,
{ resolve_canonical: { key: "foundation_treasury" } }
);
// 获取 DAO 配置
const daoConfig: any = await cosmClient.queryContractSmart(
govResult.address,
{ get_config: {} }
);
setClient(cosmClient);
setSigningClient(signClient);
setDaoAddress(govResult.address);
setTreasuryAddress(treasuryResult.address);
setConfig(daoConfig);
setAddress(userAddress);
setIsConnected(true);
} catch (error) {
console.error("连接失败:", error);
throw error;
}
}, []);
const disconnect = useCallback(() => {
setClient(null);
setSigningClient(null);
setDaoAddress(null);
setTreasuryAddress(null);
setConfig(null);
setAddress(null);
setIsConnected(false);
}, []);
return (
<DaoContext.Provider
value={{
client,
signingClient,
daoAddress,
treasuryAddress,
config,
address,
isConnected,
connect,
disconnect,
}}
>
{children}
</DaoContext.Provider>
);
}
export const useDao = () => useContext(DaoContext);
8.3 提案浏览组件
// dao/ProposalList.tsx
import React, { useState, useEffect } from "react";
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { useDao } from "./daoProvider";
interface Proposal {
id: number;
title: string;
description: string;
proposer: string;
status: string;
created_at: number;
voting_end: number | null;
deposit_amount: string;
target_contract: string | null;
}
interface Tally {
yes: string;
no: string;
abstain: string;
veto: string;
total_power: string;
turnout: string;
}
const STATUS_LABELS: Record<string, string> = {
active: "投票中",
passed: "已通过",
rejected: "已拒绝",
executed: "已执行",
canceled: "已取消",
};
const STATUS_COLORS: Record<string, string> = {
active: "#2196F3",
passed: "#4CAF50",
rejected: "#F44336",
executed: "#9E9E9E",
canceled: "#FF9800",
};
export function ProposalList() {
const { client, daoAddress } = useDao();
const [proposals, setProposals] = useState<Proposal[]>([]);
const [tallies, setTallies] = useState<Record<number, Tally>>({});
const [loading, setLoading] = useState(true);
const [filter, setFilter] = useState<string>("");
useEffect(() => {
if (!client || !daoAddress) return;
fetchProposals();
}, [client, daoAddress, filter]);
async function fetchProposals() {
if (!client || !daoAddress) return;
setLoading(true);
try {
const queryMsg: any = {
list_proposals: {},
};
if (filter) {
queryMsg.list_proposals.status = filter;
}
const result: any = await client.queryContractSmart(
daoAddress,
queryMsg
);
setProposals(result.proposals);
// 并行查询计票结果
const tallyPromises = result.proposals.map((p: Proposal) =>
client.queryContractSmart(daoAddress, {
get_proposal_tally: { proposal_id: p.id },
}).then((t: any) => ({ id: p.id, tally: t }))
.catch(() => null)
);
const tallyResults = await Promise.all(tallyPromises);
const tallyMap: Record<number, Tally> = {};
tallyResults.forEach((r: any) => {
if (r) tallyMap[r.id] = r.tally;
});
setTallies(tallyMap);
} catch (error) {
console.error("获取提案列表失败:", error);
} finally {
setLoading(false);
}
}
function formatMsg(amount: string): string {
return (Number(amount) / 1_000_000).toLocaleString() + " MSG";
}
if (loading) return <div>加载中...</div>;
return (
<div className="proposal-list">
<div className="filters">
<button onClick={() => setFilter("")}>全部</button>
<button onClick={() => setFilter("active")}>投票中</button>
<button onClick={() => setFilter("passed")}>已通过</button>
<button onClick={() => setFilter("executed")}>已执行</button>
</div>
{proposals.length === 0 ? (
<p className="empty">暂无提案</p>
) : (
proposals.map((proposal) => (
<div key={proposal.id} className="proposal-card">
<div className="proposal-header">
<span className="proposal-id">#{proposal.id}</span>
<span
className="status-badge"
style={{ backgroundColor: STATUS_COLORS[proposal.status] }}
>
{STATUS_LABELS[proposal.status] || proposal.status}
</span>
</div>
<h3 className="proposal-title">{proposal.title}</h3>
<p className="proposal-desc">{proposal.description}</p>
<div className="proposal-meta">
<span>提案人: {proposal.proposer.slice(0, 12)}...</span>
<span>保证金: {formatMsg(proposal.deposit_amount)}</span>
</div>
{tallies[proposal.id] && (
<div className="tally-bar">
<div className="tally-row">
<span>赞成</span>
<div className="bar-bg">
<div
className="bar-fill yes"
style={{
width: `${
(Number(tallies[proposal.id].yes) /
Number(tallies[proposal.id].total_power)) *
100
}%`,
}}
/>
</div>
<span>{formatMsg(tallies[proposal.id].yes)}</span>
</div>
<div className="tally-row">
<span>反对</span>
<div className="bar-bg">
<div
className="bar-fill no"
style={{
width: `${
(Number(tallies[proposal.id].no) /
Number(tallies[proposal.id].total_power)) *
100
}%`,
}}
/>
</div>
<span>{formatMsg(tallies[proposal.id].no)}</span>
</div>
<div className="tally-row">
<span>否决</span>
<div className="bar-bg">
<div
className="bar-fill veto"
style={{
width: `${
(Number(tallies[proposal.id].veto) /
Number(tallies[proposal.id].total_power)) *
100
}%`,
}}
/>
</div>
<span>{formatMsg(tallies[proposal.id].veto)}</span>
</div>
<div className="tally-info">
参与率: {tallies[proposal.id].turnout}%
</div>
</div>
)}
</div>
))
)}
</div>
);
}
8.4 投票组件
// dao/VotePanel.tsx
import React, { useState } from "react";
import { useDao } from "./daoProvider";
interface VotePanelProps {
proposalId: number;
onVoteComplete?: (txHash: string) => void;
}
export function VotePanel({ proposalId, onVoteComplete }: VotePanelProps) {
const { signingClient, daoAddress, address, isConnected, connect } = useDao();
const [selectedVote, setSelectedVote] = useState<string>("");
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string>("");
const VOTE_OPTIONS = [
{ value: "yes", label: "赞成", color: "#4CAF50", icon: "👍" },
{ value: "no", label: "反对", color: "#F44336", icon: "👎" },
{ value: "abstain", label: "弃权", color: "#9E9E9E", icon: "🤝" },
{ value: "veto", label: "否决", color: "#FF9800", icon: "🚫" },
];
async function handleVote() {
if (!selectedVote) {
setError("请选择一个投票选项");
return;
}
if (!isConnected) {
await connect();
}
if (!signingClient || !daoAddress || !address) {
setError("钱包未连接");
return;
}
setSubmitting(true);
setError("");
try {
const tx = await signingClient.execute(
address,
daoAddress,
{
vote: {
proposal_id: proposalId,
vote: selectedVote,
},
},
"auto"
);
onVoteComplete?.(tx.transactionHash);
} catch (err: any) {
setError(err.message || "投票失败,请重试");
} finally {
setSubmitting(false);
}
}
return (
<div className="vote-panel">
<h3>投票</h3>
<div className="vote-options">
{VOTE_OPTIONS.map((opt) => (
<button
key={opt.value}
className={`vote-btn ${selectedVote === opt.value ? "selected" : ""}`}
style={{
borderColor: selectedVote === opt.value ? opt.color : "#ddd",
backgroundColor: selectedVote === opt.value ? opt.color + "20" : "white",
}}
onClick={() => setSelectedVote(opt.value)}
disabled={submitting}
>
<span className="vote-icon">{opt.icon}</span>
<span className="vote-label">{opt.label}</span>
</button>
))}
</div>
{error && <p className="error-msg">{error}</p>}
<button
className="submit-vote-btn"
onClick={handleVote}
disabled={!selectedVote || submitting}
>
{submitting ? "提交中..." : "提交投票"}
</button>
</div>
);
}
8.5 创建提案表单
// dao/CreateProposal.tsx
import React, { useState } from "react";
import { useDao } from "./daoProvider";
interface CreateProposalProps {
onCreated?: (txHash: string) => void;
}
export function CreateProposal({ onCreated }: CreateProposalProps) {
const { signingClient, daoAddress, address, isConnected, connect } = useDao();
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [targetContract, setTargetContract] = useState("");
const [executeMsg, setExecuteMsg] = useState("");
const [depositAmount, setDepositAmount] = useState("1000000");
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState("");
const [isContractProposal, setIsContractProposal] = useState(false);
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
if (!title.trim()) {
setError("请输入提案标题");
return;
}
if (!description.trim()) {
setError("请输入提案描述");
return;
}
if (!isConnected) {
await connect();
}
if (!signingClient || !daoAddress || !address) {
setError("钱包未连接");
return;
}
setSubmitting(true);
setError("");
try {
const msg: any = {
submit_proposal: {
title: title.trim(),
description: description.trim(),
target_contract: isContractProposal && targetContract ? targetContract : null,
execute_msg: isContractProposal && executeMsg ? executeMsg : null,
deposit_amount: depositAmount,
},
};
const funds = [{ denom: "umsg", amount: depositAmount }];
const tx = await signingClient.execute(
address,
daoAddress,
msg,
"auto",
undefined,
funds
);
onCreated?.(tx.transactionHash);
setTitle("");
setDescription("");
setTargetContract("");
setExecuteMsg("");
} catch (err: any) {
setError(err.message || "提交提案失败");
} finally {
setSubmitting(false);
}
}
return (
<form className="create-proposal" onSubmit={handleSubmit}>
<h2>创建新提案</h2>
<div className="form-group">
<label>提案标题</label>
<input
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="输入提案标题"
maxLength={200}
required
/>
</div>
<div className="form-group">
<label>提案描述</label>
<textarea
value={description}
onChange={(e) => setDescription(e.target.value)}
placeholder="详细描述你的提案..."
rows={5}
required
/>
</div>
<div className="form-group">
<label>
<input
type="checkbox"
checked={isContractProposal}
onChange={(e) => setIsContractProposal(e.target.checked)}
/>
合约执行提案(通过后自动调用合约)
</label>
</div>
{isContractProposal && (
<>
<div className="form-group">
<label>目标合约地址</label>
<input
type="text"
value={targetContract}
onChange={(e) => setTargetContract(e.target.value)}
placeholder="msg1..."
/>
</div>
<div className="form-group">
<label>执行消息 (JSON)</label>
<textarea
value={executeMsg}
onChange={(e) => setExecuteMsg(e.target.value)}
placeholder='{"method":{"param":"value"}}'
rows={3}
/>
</div>
</>
)}
<div className="form-group">
<label>保证金 (umsg)</label>
<input
type="number"
value={depositAmount}
onChange={(e) => setDepositAmount(e.target.value)}
min="1000000"
/>
<small>最小保证金: 1,000,000 umsg (1 MSG)</small>
</div>
{error && <p className="error-msg">{error}</p>}
<button type="submit" disabled={submitting}>
{submitting ? "提交中..." : "提交提案"}
</button>
</form>
);
}
8.6 金库管理页面
// dao/TreasuryDashboard.tsx
import React, { useState, useEffect } from "react";
import { CosmWasmClient, SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { useDao } from "./daoProvider";
interface TreasuryBalance {
denom: string;
balance: string;
}
interface SignerInfo {
signers: string[];
threshold: string;
total_signers: number;
}
interface TreasuryProposal {
id: number;
recipient: string;
amount: string;
denom: string;
purpose: string;
status: string;
approvers: string[];
created_at: number;
}
export function TreasuryDashboard() {
const {
client,
signingClient,
treasuryAddress,
address,
isConnected,
connect,
} = useDao();
const [balance, setBalance] = useState<TreasuryBalance | null>(null);
const [signers, setSigners] = useState<SignerInfo | null>(null);
const [proposals, setProposals] = useState<TreasuryProposal[]>([]);
const [newRecipient, setNewRecipient] = useState("");
const [newAmount, setNewAmount] = useState("");
const [newPurpose, setNewPurpose] = useState("");
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
useEffect(() => {
if (!client || !treasuryAddress) return;
fetchTreasuryData();
}, [client, treasuryAddress]);
async function fetchTreasuryData() {
if (!client || !treasuryAddress) return;
setLoading(true);
try {
const [balanceData, signersData, proposalsData] = await Promise.all([
client.queryContractSmart(treasuryAddress, {
get_balance: { denom: "umsg" },
}),
client.queryContractSmart(treasuryAddress, {
get_signers: {},
}),
client.queryContractSmart(treasuryAddress, {
list_proposals: { limit: 20 },
}),
]);
setBalance(balanceData);
setSigners(signersData);
setProposals(proposalsData.proposals || []);
} catch (err: any) {
console.error("获取金库数据失败:", err);
setError(err.message);
} finally {
setLoading(false);
}
}
function formatBalance(amount: string): string {
return (Number(amount) / 1_000_000).toLocaleString("zh-CN", {
minimumFractionDigits: 2,
maximumFractionDigits: 6,
});
}
async function handleProposeSpend() {
if (!isConnected) await connect();
if (!signingClient || !treasuryAddress || !address) return;
try {
const tx = await signingClient.execute(
address,
treasuryAddress,
{
propose_spend: {
recipient: newRecipient,
amount: newAmount,
denom: "umsg",
purpose: newPurpose,
},
},
"auto"
);
setNewRecipient("");
setNewAmount("");
setNewPurpose("");
await fetchTreasuryData();
} catch (err: any) {
setError(err.message);
}
}
async function handleApprove(proposalId: number) {
if (!isConnected) await connect();
if (!signingClient || !treasuryAddress || !address) return;
try {
await signingClient.execute(
address,
treasuryAddress,
{ approve_spend: { proposal_id: proposalId } },
"auto"
);
await fetchTreasuryData();
} catch (err: any) {
setError(err.message);
}
}
async function handleExecute(proposalId: number) {
if (!isConnected) await connect();
if (!signingClient || !treasuryAddress || !address) return;
try {
await signingClient.execute(
address,
treasuryAddress,
{ execute_spend: { proposal_id: proposalId } },
"auto"
);
await fetchTreasuryData();
} catch (err: any) {
setError(err.message);
}
}
if (loading) return <div>加载金库数据...</div>;
return (
<div className="treasury-dashboard">
<h2>DAO 金库仪表盘</h2>
{/* 余额展示 */}
<div className="balance-card">
<h3>金库余额</h3>
<div className="balance-amount">
{balance ? formatBalance(balance.balance) : "—"} MSG
</div>
</div>
{/* 签名人信息 */}
{signers && (
<div className="signers-card">
<h3>金库签名人</h3>
<p>总共 {signers.total_signers} 人,需要 {signers.threshold} 人批准</p>
<ul>
{signers.signers.map((s: string, i: number) => (
<li key={i} title={s}>
{s.slice(0, 12)}...{s.slice(-6)}
</li>
))}
</ul>
</div>
)}
{/* 新增支出提案 */}
<div className="spend-form">
<h3>发起支出提案</h3>
<input
type="text"
placeholder="收款地址 (msg1...)"
value={newRecipient}
onChange={(e) => setNewRecipient(e.target.value)}
/>
<input
type="number"
placeholder="金额 (umsg)"
value={newAmount}
onChange={(e) => setNewAmount(e.target.value)}
/>
<input
type="text"
placeholder="支出用途"
value={newPurpose}
onChange={(e) => setNewPurpose(e.target.value)}
/>
<button onClick={handleProposeSpend}>提交支出提案</button>
</div>
{/* 支出提案列表 */}
<div className="treasury-proposals">
<h3>支出提案</h3>
{proposals.length === 0 ? (
<p>暂无支出提案</p>
) : (
proposals.map((p) => (
<div key={p.id} className="treasury-proposal-card">
<div className="prop-header">
<span className="prop-id">#{p.id}</span>
<span className={`prop-status ${p.status}`}>
{p.status === "pending" && "待批准"}
{p.status === "approved" && "已批准"}
{p.status === "executed" && "已执行"}
{p.status === "rejected" && "已拒绝"}
</span>
</div>
<p>
收款: {p.recipient.slice(0, 12)}...
<br />
金额: {formatBalance(p.amount)} {p.denom}
<br />
用途: {p.purpose}
</p>
<div className="approvers">
已批准: {p.approvers?.length || 0}/{signers?.threshold || "?"}
</div>
<div className="actions">
{p.status === "pending" && (
<button onClick={() => handleApprove(p.id)}>批准</button>
)}
{p.status === "approved" && (
<button onClick={() => handleExecute(p.id)}>执行</button>
)}
</div>
</div>
))
)}
</div>
{error && <div className="error-toast">{error}</div>}
</div>
);
}
8.7 链上治理查询工具
// dao/governanceQueries.ts
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
interface CosmosGovProposal {
proposal_id: string;
content: {
"@type": string;
title: string;
description: string;
};
status: string;
final_tally_result: {
yes: string;
abstain: string;
no: string;
no_with_veto: string;
};
submit_time: string;
deposit_end_time: string;
voting_start_time: string;
voting_end_time: string;
}
export class GovernanceQueries {
private client: CosmWasmClient;
private restEndpoint: string;
private daoAddress: string;
private treasuryAddress: string;
constructor(
client: CosmWasmClient,
restEndpoint: string,
daoAddress: string,
treasuryAddress: string
) {
this.client = client;
this.restEndpoint = restEndpoint;
this.daoAddress = daoAddress;
this.treasuryAddress = treasuryAddress;
}
// 查询 Cosmos SDK 提案
async getCosmosProposals(
status: string = ""
): Promise<CosmosGovProposal[]> {
let url = `${this.restEndpoint}/cosmos/gov/v1beta1/proposals`;
if (status) {
url += `?status=${status}`;
}
const response = await fetch(url);
const data = await response.json();
return data.proposals || [];
}
// 查询 DAO 合约提案
async getDaoProposals(limit: number = 30) {
return this.client.queryContractSmart(this.daoAddress, {
list_proposals: { limit },
});
}
// 查询金库支出提案
async getTreasuryProposals(limit: number = 30) {
return this.client.queryContractSmart(this.treasuryAddress, {
list_proposals: { limit },
});
}
// 查询地址是否为金库签名人
async isTreasurySigner(address: string): Promise<boolean> {
const result: any = await this.client.queryContractSmart(
this.treasuryAddress,
{ get_signers: {} }
);
return result.signers.includes(address);
}
// 查询地址的投票权重
async getVotingPower(address: string): Promise<string> {
const balance: any = await this.client.getBalance(
address,
"umsg"
);
return balance.amount;
}
}
9. CLI 命令参考
9.1 Cosmos SDK Gov 模块命令
提案管理
# 列出所有提案
msgd query gov proposals --chain-id=msg-chain-1
# 按状态过滤提案
msgd query gov proposals --status=VOTING_PERIOD --chain-id=msg-chain-1
msgd query gov proposals --status=PASSED --chain-id=msg-chain-1
msgd query gov proposals --status=REJECTED --chain-id=msg-chain-1
# 查询单个提案详情
msgd query gov proposal 1 --chain-id=msg-chain-1
# 查询提案投票结果
msgd query gov tally 1 --chain-id=msg-chain-1
# 查询提案投票详情
msgd query gov votes 1 --chain-id=msg-chain-1
# 查询提案押金
msgd query gov deposits 1 --chain-id=msg-chain-1
投票管理
# 投赞成票
msgd tx gov vote 1 Yes --from=my_validator --chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 --fees=2500umsg
# 投反对票
msgd tx gov vote 1 No --from=my_validator --chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 --fees=2500umsg
# 投弃权票
msgd tx gov vote 1 Abstain --from=my_validator --chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 --fees=2500umsg
# 投否决票
msgd tx gov vote 1 NoWithVeto --from=my_validator --chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 --fees=2500umsg
保证金管理
# 为提案存入保证金
msgd tx gov deposit 1 1000000umsg --from=my_validator \
--chain-id=msg-chain-1 --gas=auto --gas-adjustment=1.5 --fees=2500umsg
9.2 CosmWasm 合约交互命令
DAO 治理合约
# 设置合约地址变量
DAO_GOV="<dao_governance_address>"
# 提交提案
msgd tx wasm execute $DAO_GOV \
'{"submit_proposal":{"title":"测试提案","description":"这是一个测试提案","deposit_amount":"1000000"}}' \
--amount="1000000umsg" \
--from=dao_member \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 --fees=2500umsg
# 投票
msgd tx wasm execute $DAO_GOV \
'{"vote":{"proposal_id":1,"vote":"yes"}}' \
--from=dao_member \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 --fees=2500umsg
# 执行提案
msgd tx wasm execute $DAO_GOV \
'{"execute_proposal":{"proposal_id":1}}' \
--from=anyone \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 --fees=2500umsg
# 取消提案
msgd tx wasm execute $DAO_GOV \
'{"cancel_proposal":{"proposal_id":1}}' \
--from=proposer \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 --fees=2500umsg
# 更新配置
msgd tx wasm execute $DAO_GOV \
'{"update_config":{"quorum":"0.2","voting_period":432000,"threshold":"0.5"}}' \
--from=admin \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 --fees=2500umsg
DAO 治理查询
# 查询配置
msgd query wasm contract-state smart $DAO_GOV \
'{"get_config":{}}' \
--chain-id=msg-chain-1
# 查询提案
msgd query wasm contract-state smart $DAO_GOV \
'{"get_proposal":{"proposal_id":1}}' \
--chain-id=msg-chain-1
# 列出提案(按状态过滤)
msgd query wasm contract-state smart $DAO_GOV \
'{"list_proposals":{"status":"active","limit":10}}' \
--chain-id=msg-chain-1
# 查询投票
msgd query wasm contract-state smart $DAO_GOV \
'{"get_vote":{"proposal_id":1,"voter":"msg1..."}}' \
--chain-id=msg-chain-1
# 查询计票结果
msgd query wasm contract-state smart $DAO_GOV \
'{"get_proposal_tally":{"proposal_id":1}}' \
--chain-id=msg-chain-1
金库合约
# 设置合约地址变量
TREASURY="<treasury_address>"
# 提交支出提案
msgd tx wasm execute $TREASURY \
'{"propose_spend":{"recipient":"msg1...","amount":"5000000000","denom":"umsg","purpose":"生态资助"}}' \
--from=signer_a \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 --fees=2500umsg
# 批准支出
msgd tx wasm execute $TREASURY \
'{"approve_spend":{"proposal_id":1}}' \
--from=signer_b \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 --fees=2500umsg
# 执行支出
msgd tx wasm execute $TREASURY \
'{"execute_spend":{"proposal_id":1}}' \
--from=signer_a \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 --fees=2500umsg
# 添加签名人
msgd tx wasm execute $TREASURY \
'{"add_signer":{"address":"msg1..."}}' \
--from=signer_a \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 --fees=2500umsg
# 移除签名人
msgd tx wasm execute $TREASURY \
'{"remove_signer":{"address":"msg1..."}}' \
--from=signer_a \
--chain-id=msg-chain-1 \
--gas=auto --gas-adjustment=1.5 --fees=2500umsg
金库查询
# 查询余额
msgd query wasm contract-state smart $TREASURY \
'{"get_balance":{"denom":"umsg"}}' \
--chain-id=msg-chain-1
# 列出支出提案
msgd query wasm contract-state smart $TREASURY \
'{"list_proposals":{"status":"pending","limit":20}}' \
--chain-id=msg-chain-1
# 查询签名人
msgd query wasm contract-state smart $TREASURY \
'{"get_signers":{}}' \
--chain-id=msg-chain-1
# 查询配置
msgd query wasm contract-state smart $TREASURY \
'{"get_config":{}}' \
--chain-id=msg-chain-1
9.3 合约地址解析命令
# 通过 genesis_registry_v1 解析 DAO 治理合约地址
GENESIS_REGISTRY="<genesis_registry_address>"
msgd query wasm contract-state smart $GENESIS_REGISTRY \
'{"resolve_canonical":{"key":"dao_governance"}}' \
--chain-id=msg-chain-1
# 解析金库合约地址
msgd query wasm contract-state smart $GENESIS_REGISTRY \
'{"resolve_canonical":{"key":"foundation_treasury"}}' \
--chain-id=msg-chain-1
# 列出所有已注册合约
msgd query wasm contract-state smart $GENESIS_REGISTRY \
'{"list_contracts":{"limit":30}}' \
--chain-id=msg-chain-1
9.4 通用命令
# 查询交易详情
msgd query tx <tx_hash> --chain-id=msg-chain-1
# 查询账户余额
msgd query bank balances msg1... --chain-id=msg-chain-1
# 查询区块信息
msgd query block --height=1234567 --chain-id=msg-chain-1
# 查询链上参数
msgd query params --chain-id=msg-chain-1
# 查询当前验证者集
msgd query staking validators --chain-id=msg-chain-1
# 查询委托信息
msgd query staking delegations msg1... --chain-id=msg-chain-1
10. 最佳实践与安全建议
10.1 治理安全清单
提案安全
- [ ] 提案描述清晰完整,包含动机、方案、影响分析
- [ ] 合约执行提案的
execute_msg需经过多地址验证 - [ ] 保证金金额需防止垃圾提案(建议 ≥ 100 MSG)
- [ ] 合约执行提案需在测试网先验证
- [ ] 敏感操作(如修改治理参数)需设置更高门槛
投票安全
- [ ] 投票期不宜过短(建议 ≥ 3 天)
- [ ] 确保足够的法定人数防止少数控制
- [ ] 启用否决权(Veto)防止恶意提案
- [ ] 验证者应积极投票,避免被委托人惩罚
- [ ] 大额投票建议拆分为多笔交易避免滑点
金库安全
- [ ] 金库签名人至少 3-5 人,避免单点故障
- [ ] 签名人阈值设置为总人数的 60-70%
- [ ] 大额支出拆分多笔提案
- [ ] 定期轮换签名人
- [ ] 所有支出必须在链上留下审计轨迹
10.2 常见风险与防护
| 风险 | 描述 | 防护措施 |
|---|---|---|
| 女巫攻击 | 创建大量地址操纵投票 | 设置保证金、使用声誉权重 |
| 闪电贷攻击 | 借入代币后投票再归还 | 使用历史余额快照而非当前余额 |
| 投票贿赂 | 链下贿赂投票者 | 使用隐私投票、Commit-Reveal 方案 |
| 治理攻击 | 恶意提案通过后提取资金 | 时间锁、多签、否决权 |
| 低参与度 | 法定人数不足导致治理停滞 | 设置合理法定人数阈值 |
| 提案堆积 | 大量垃圾提案阻塞治理 | 保证金门槛、提案费用 |
| 签名人泄露 | 金库签名人私钥泄露 | 硬件钱包、定期轮换、阈值签名 |
10.3 实战建议
10.3.1 启动建议
- 从小范围开始:从 3-5 个核心贡献者开始治理
- 渐进式去中心化:逐步转移权限给 DAO 合约
- 测试网先行:所有参数修改先在测试网验证
- 文档优先:制定清晰的治理流程文档
- 社区教育:培训社区成员了解投票机制
10.3.2 运营建议
- 保持投票率:定期提醒社区投票,设置投票提醒
- 定期报告:定期发布治理统计报告
- 治理论坛:建立链下讨论机制(如 Commonwealth)
- 提案模板:提供标准化提案模板
- 快速响应:建立紧急提案通道
10.3.3 技术建议
- 自动执行脚本:部署自动执行提案的 Cron Job
- 监控告警:监控关键参数变化和异常投票
- 多签备份:DAO 管理员私钥使用多签或 MPC
- 合约升级:使用代理模式支持合约升级
- 审计追溯:保留完整的治理操作日志
10.4 治理故障预案
级别 1:参数异常
- 症状:投票率异常高/低
- 响应:暂停治理,调查原因
- 恢复:调整参数后重启
级别 2:恶意提案
- 症状:提案试图提取金库资金
- 响应:金库签名人拒绝批准,提案人取消提案
- 恢复:社区投票修改治理规则
级别 3:合约漏洞
- 症状:治理合约被攻击
- 响应:暂停合约,冻结金库
- 恢复:部署修复版合约,迁移数据
级别 4:签名人失联
- 症状:金库签名人长时间不签名
- 响应:通过 DAO 投票移除失联签名人
- 恢复:添加新签名人,恢复签名阈值
10.5 参考框架
MSG Chain 的治理设计参考了以下主流 DAO 框架:
| 框架 | 借鉴点 | MSG Chain 实现 |
|---|---|---|
| Compound GovernorBravo | 委托投票、提案门槛、时间锁 | dao_governance_v1 的 token 权重策略 |
| Cosmos Hub Governance | 双阈值(Quorum + Threshold)、Veto | Cosmos SDK gov 模块完全兼容 |
| Aragon OSx | 模块化治理、插件系统 | dao_governance_v1 的可配置参数 |
| MakerDAO | 执行投票、治理安全模块 | foundation_treasury_v2 多签 |
| Uniswap | 委托-代理投票、温度检查 | 可通过扩展合约实现温度检查环节 |
附录 A:快速参考卡片
A.1 常用命令速查
# ── 第一层治理 (Cosmos SDK Gov) ──
msgd query gov proposals --status=VOTING_PERIOD # 查看投票中提案
msgd query gov tally <id> # 查看计票结果
msgd tx gov vote <id> Yes --from=<key> --chain-id=msg-chain-1 # 投赞成票
msgd tx gov submit-proposal param-change <proposal.json> --from=<k> # 参数修改提案
# ── 第二层治理 (dao_governance_v1) ──
msgd query wasm contract-state smart $DAO '{"get_config":{}}' # 查看配置
msgd query wasm contract-state smart $DAO '{"list_proposals":{}}' # 查看所有提案
msgd tx wasm execute $DAO '{"submit_proposal":{...}}' --from=<key> # 提交提案
msgd tx wasm execute $DAO '{"vote":{"proposal_id":1,"vote":"yes"}}' # 投票
# ── 金库 (foundation_treasury_v2) ──
msgd query wasm contract-state smart $TREASURY '{"get_balance":{}}' # 查看余额
msgd query wasm contract-state smart $TREASURY '{"get_signers":{}}' # 查看签名人
msgd tx wasm execute $TREASURY '{"propose_spend":{...}}' --from=<k> # 支出提案
msgd tx wasm execute $TREASURY '{"approve_spend":{"proposal_id":1}}' # 批准
# ── 通用 ──
msgd query wasm contract-state smart $REGISTRY \
'{"resolve_canonical":{"key":"dao_governance"}}' # 解析合约地址
msgd query bank balances msg1... # 查询余额
A.2 提案状态转换
submit → [voting_period 秒后] → tally → { passed | rejected }
passed → [timelock_duration 秒后] → executed
submit → [任何人调用 cancel] → canceled
A.3 Gas 费用参考
| 操作 | 预计 Gas | 费用 (umsg) |
|---|---|---|
| 查询合约状态 | 100k-300k | 2,500-7,500 |
| 提交提案 | 200k-500k | 5,000-12,500 |
| 投票 | 100k-200k | 2,500-5,000 |
| 执行提案 | 150k-400k | 3,750-10,000 |
| 金库支出提案 | 200k-400k | 5,000-10,000 |
| 批准支出 | 100k-150k | 2,500-3,750 |
| 执行支出 | 150k-300k | 3,750-7,500 |
附录 B:完整提案模板
B.1 DAO 治理提案模板
# 提案 [编号]:[标题]
## 摘要
简短的提案概述(2-3 句话)
## 动机
为什么要提出这个提案?(当前存在的什么问题或机会)
## 规格
具体要做什么:
1. 步骤一
2. 步骤二
3. 步骤三
## 理由
为什么选择这个方案而不是其他方案?
## 影响分析
- 对 DAO 成员的影響
- 对金库的影响
- 技术影响(如有)
## 时间线
- 提案提交:[日期]
- 投票开始:[日期]
- 投票结束:[日期]
- 执行时间:[日期](如通过)
## 参考资料
- 相关链接
- 技术文档
- 社区讨论
## 投票选项
- ✅ 赞成:同意执行提案
- ❌ 反对:不同意执行提案
- ⏸ 弃权:中立态度
- 🚫 否决:强烈反对(触发否决阈值)
B.2 金库支出提案模板
# 金库支出提案 [编号]:[用途]
## 基本信息
- 收款地址:msg1...
- 金额:[数字] MSG
- 代币:umsg
## 用途说明
详细描述这笔资金的用途
## 预期成果
使用这笔资金后预期达成的成果
## 预算明细
| 项目 | 金额 (MSG) | 说明 |
|-----|-----------|------|
| 项目 A | 1000 | 说明 |
| 项目 B | 2000 | 说明 |
| 总计 | 3000 | |
## 支付方式
- 一次性支付 / 分期支付
- 支付条件(如有)
## 批准要求
需要 [数字]/[总数] 签名人批准
附录 C:与主流生态系统对比
C.1 提案流程对比
Ethereum (Compound): MSG Chain (DAO):
COMP 持有者 MSG 地址
│ │
▼ ▼
提交提案 提交提案
(需要 100k COMP) (需要保证金)
│ │
▼ ▼
投票期 (3天) 投票期 (可配置)
│ │
▼ ▼
计票 计票
│ │
▼ ▼
时间锁 (2天) 时间锁 (可配置)
│ │
▼ ▼
执行 执行
C.2 投票权重对比
Framework 权重公式 抗鲸鱼 Gas 成本
──────────────────────────────────────────────────────────
Cosmos SDK Gov Stake 线性 ❌ 低
Compound COMP 委托余额 ❌ 中
MSG DAO 可配置策略 ✅ 低-中
MakerDAO MKR 线性 ❌ 中
Gitcoin Quadratic Voting ✅ 高
本文档基于 MSG Chain 代码库核实的技术事实。
白皮书系统: https://msgchain.org/whitepaper/
