dApp Docs/DAO治理实战手册
Development reference. Not independently verified for production.

MSG Chain DAO 治理实战手册

版本:v1.0 | 主网状态:No-Go | 兼容链版本:msg-chain-1


目录

  1. DAO 治理概述
  2. Cosmos SDK 治理模块
  3. dao_governance_v1 智能合约
  4. 提案生命周期
  5. 投票策略与权重计算
  6. foundation_treasury_v2 金库多签
  7. 治理参数管理
  8. 前端集成指南
  9. CLI 命令参考
  10. 最佳实践与安全建议

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 权重不同,该合约支持多种投票权重策略。

合约信息:

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 上原生实现。

合约信息:

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 治理安全清单

提案安全

投票安全

金库安全

10.2 常见风险与防护

风险 描述 防护措施
女巫攻击 创建大量地址操纵投票 设置保证金、使用声誉权重
闪电贷攻击 借入代币后投票再归还 使用历史余额快照而非当前余额
投票贿赂 链下贿赂投票者 使用隐私投票、Commit-Reveal 方案
治理攻击 恶意提案通过后提取资金 时间锁、多签、否决权
低参与度 法定人数不足导致治理停滞 设置合理法定人数阈值
提案堆积 大量垃圾提案阻塞治理 保证金门槛、提案费用
签名人泄露 金库签名人私钥泄露 硬件钱包、定期轮换、阈值签名

10.3 实战建议

10.3.1 启动建议

  1. 从小范围开始:从 3-5 个核心贡献者开始治理
  2. 渐进式去中心化:逐步转移权限给 DAO 合约
  3. 测试网先行:所有参数修改先在测试网验证
  4. 文档优先:制定清晰的治理流程文档
  5. 社区教育:培训社区成员了解投票机制

10.3.2 运营建议

  1. 保持投票率:定期提醒社区投票,设置投票提醒
  2. 定期报告:定期发布治理统计报告
  3. 治理论坛:建立链下讨论机制(如 Commonwealth)
  4. 提案模板:提供标准化提案模板
  5. 快速响应:建立紧急提案通道

10.3.3 技术建议

  1. 自动执行脚本:部署自动执行提案的 Cron Job
  2. 监控告警:监控关键参数变化和异常投票
  3. 多签备份:DAO 管理员私钥使用多签或 MPC
  4. 合约升级:使用代理模式支持合约升级
  5. 审计追溯:保留完整的治理操作日志

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/