@msg-chain/devkit CLI 工具完整指南
数据来源:MSG Chain 代码库核实
主网状态: No-Go — 当前 MSGChain 主网裁决为 No-Go,以下内容反映代码实际状态,不代表生产可用。
目录
1. 概述
1.1 什么是 @msg-chain/devkit?
@msg-chain/devkit 是 MSG Chain 官方推出的全栈开发工具包,定位为 "MSG Chain 的 Hardhat"。它是一个基于 Node.js/TypeScript 的命令行工具,为开发者提供从合约编写、编译、测试到部署、验证、上线的完整工作流。
1.2 为什么需要 devkit?
在 MSG Chain 上开发智能合约和去中心化应用,开发者面临以下挑战:
| 挑战 | devkit 解决方案 |
|---|---|
| CosmWasm 合约编译命令冗长 | msg-devkit compile — 一键编译所有合约 |
| cargo test 输出缺乏可读性 | msg-devkit test — 格式化测试报告 |
| 部署流程需要多步操作 | msg-devkit deploy — 自动完成 store + instantiate |
| 本地开发环境搭建复杂 | msg-devkit node — 一键启动本地节点 |
| 生产发布缺乏安全机制 | msg-devkit publish --guard — 审批门强制检查 |
| 缺少项目脚手架 | msg-devkit init — 完整项目模板生成 |
1.3 与其他工具的对比
| 特性 | @msg-chain/devkit | Hardhat | Truffle | CosmJS CLI |
|---|---|---|---|---|
| 链适配 | MSG Chain 专用 | EVM 通用 | EVM 通用 | Cosmos 通用 |
| 合约语言 | CosmWasm (Rust) | Solidity | Solidity | CosmWasm (Rust) |
| 任务系统 | 内置任务运行器 | Hardhat Tasks | 无 | 无 |
| 插件系统 | 内置插件机制 | Hardhat Plugins | Truffle Boxes | 无 |
| 类型安全 | TypeScript 优先 | JavaScript/TS | JavaScript | TypeScript |
| Guard 模式 | 审批门 | 无 | 无 | 无 |
| 合约验证 | 内置 | Etherscan | 插件 | 无 |
| 交互控制台 | REPL + CosmJS | Hardhat Console | Truffle Console | 无 |
@msg-chain/devkit 借鉴了 Hardhat 的任务运行器设计理念和 Truffle 的合约管理思想,但同时针对 MSG Chain 的 CosmWasm 生态和 Guard 安全模型做了深度定制。
1.4 核心特性
- 一键编译 — 基于实际
cargo build --target wasm32-unknown-unknown --release的封装 - 智能测试 — 自动发现并执行 46+ 合约包的 cargo test
- 安全部署 — Guard 模式集成 4 道审批门:scope_lock, secret_injection, production_release, governance_or_treasury
- 完整脚手架 — 合约项目、dApp 前端、测试框架一键生成
- 网络管理 — 预设 local/devnet/testnet/mainnet 四套网络配置
- CI/CD 集成 — 生成 GitHub Actions 工作流模板
- 节点管理 — Docker Compose 封装本地节点启动
1.5 MSG Chain 技术参数
| 参数 | 值 |
|---|---|
| Chain ID | msg-chain-1 |
| Bech32 前缀 | msg / msgpub / msgvaloper / msgvaloperpub / msgvalcons / msgvalconspub |
| CoinType | 118 |
| 小数位 | 18 |
| Gas 价格(低/中/高) | 1,000,000,000 / 1,000,000,000 / 1,000,000,000 attoMSG |
| 出块时间 | 5s |
| 共识机制 | Round-Robin + DAR |
| 构建产物 | bin/genesis_node_linux, bin/quantum_node_linux |
2. 安装
2.1 前提条件
| 依赖 | 版本要求 | 用途 |
|---|---|---|
| Node.js | >= 18.0.0 | 运行时 |
| npm / yarn / pnpm | 最新 | 包管理 |
| Rust | >= 1.75.0 | 合约编译 |
| Go | >= 1.24.6 | MSG Chain 节点编译 |
| Docker + Docker Compose | 最新 | 本地节点 |
| wasm32-unknown-unknown target | rustup target add | WASM 编译 |
| wasm-opt (binaryen) | 最新 | WASM 优化 |
# 安装 Rust 和 WASM 目标
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-unknown-unknown
# 安装 binaryen(wasm-opt)
# Ubuntu/Debian
sudo apt install binaryen
# macOS
brew install binaryen
# 安装 Go
wget https://go.dev/dl/go1.24.6.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go1.24.6.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
2.2 全局安装
# npm 全局安装
npm install -g @msg-chain/devkit
# 验证安装
msg-devkit --version
# => 1.0.0
msg-devkit --help
# => @msg-chain/devkit CLI — MSG Chain 智能合约开发工具链
2.3 使用 npx(推荐)
# 无需全局安装,直接使用
npx @msg-chain/devkit init my-project
npx @msg-chain/devkit compile
npx @msg-chain/devkit test
2.4 项目本地安装
# 在已有项目中安装
npm install --save-dev @msg-chain/devkit
# 在 package.json 中添加脚本
# "scripts": {
# "compile": "msg-devkit compile",
# "test": "msg-devkit test",
# "deploy": "msg-devkit deploy",
# "node": "msg-devkit node"
# }
2.5 环境变量
@msg-chain/devkit 支持以下环境变量配置:
# 节点 RPC 端点
MSG_RPC_URL=http://localhost:26657
# 节点 REST API 端点
MSG_REST_URL=http://localhost:1317
# 部署账户助记词(仅在本地开发使用)
MSG_MNEMONIC="..."
# 签名账户名称
MSG_SIGNER=my-validator-key
# 默认网络
MSG_NETWORK=local
# 日志级别
MSG_LOG_LEVEL=info
3. 命令参考
3.1 命令树概览
msg-devkit
├── init [project-name] # 脚手架 — 创建新项目
├── compile # 编译 — 编译所有合约
├── test # 测试 — 运行合约测试
├── deploy # 部署 — 部署合约到链上
├── verify # 验证 — 验证合约源码
├── console # 控制台 — 交互式合约交互
├── node # 节点 — 管理本地 MSG Chain 节点
│ ├── start
│ ├── stop
│ └── status
├── faucet # 水龙头 — 申请测试代币
├── schema # 模式 — 生成 JSON Schema
├── network # 网络 — 管理网络配置
│ ├── list
│ ├── add
│ └── use
└── publish # 发布 — 受保护的生产部署
3.2 msg-devkit init [project-name]
创建新的 MSG Chain 项目脚手架。
说明: 类似 Hardhat 的 npx hardhat init,但专门为 MSG Chain 的 CosmWasm 合约和 dApp 定制。根据选择的模板生成完整的目录结构、配置文件、合约脚手架和测试框架。
选项:
| 选项 | 默认值 | 描述 |
|---|---|---|
--template <type> |
contract |
项目类型: contract, dapp, fullstack |
--contract-name <name> |
my_contract |
合约名称(仅 contract 模板) |
--framework <lib> |
react |
前端框架: react, next, vanilla(仅 dapp) |
--language <lang> |
typescript |
语言: typescript, javascript |
--ci <provider> |
— | CI 模板: github-actions |
--yes |
false |
跳过确认提示 |
用法示例:
# 创建合约项目
msg-devkit init my-ai-agent
# 创建 dApp 项目(React + TypeScript)
msg-devkit init my-dapp --template dapp --framework react
# 创建全栈项目(合约 + Next.js 前端)
msg-devkit init my-fullstack --template fullstack --framework next
# 创建合约项目 + GitHub Actions CI
msg-devkit init my-ai-agent --ci github-actions
# 非交互式创建(跳过确认)
msg-devkit init my-ai-agent --yes
生成结构:
my-ai-agent/
├── contracts/
│ └── cosmwasm/
│ └── all/
│ └── my_ai_agent/
│ ├── Cargo.toml
│ ├── src/
│ │ ├── contract.rs
│ │ ├── msg.rs
│ │ ├── state.rs
│ │ ├── error.rs
│ │ ├── lib.rs
│ │ └── schema.rs
│ └── tests/
│ └── integration.rs
├── scripts/
│ └── deploy.ts
├── test/
│ └── integration/
│ └── my_ai_agent.test.ts
├── msgchain.config.ts
├── networks.json
├── package.json
├── tsconfig.json
├── .gitignore
└── README.md
3.3 msg-devkit compile
编译项目中的所有 CosmWasm 合约。
说明: 内部封装了 MSG Chain 实际的编译命令 cargo build --target wasm32-unknown-unknown --release。自动发现 contracts/cosmwasm/all/ 目录下的所有合约包(MSG Chain 有 46 个合约包),并针对每个合约执行 WASM 编译 + wasm-opt 优化。类似 Hardhat 的 npx hardhat compile。
实际包装的命令:
# 对每个合约执行:
cd contracts/cosmwasm/all/{name} && cargo build --target wasm32-unknown-unknown --release
选项:
| 选项 | 默认值 | 描述 |
|---|---|---|
--contract <name> |
全部 | 仅编译指定合约 |
--optimize |
true |
启用 wasm-opt 优化 |
--optimize-level <level> |
z |
优化级别: z(size), 2(speed), 3(aggressive) |
--out-dir <path> |
./artifacts |
输出目录 |
--watch |
false |
监听文件变化自动重编译 |
--force |
false |
强制重新编译(忽略缓存) |
用法示例:
# 编译所有合约
msg-devkit compile
# 编译指定合约
msg-devkit compile --contract my_ai_agent
# 编译 + 禁用 wasm-opt(用于调试)
msg-devkit compile --optimize false
# 编译 + 监听模式
msg-devkit compile --watch
# 指定输出目录
msg-devkit compile --out-dir ./build/wasm
编译输出示例:
$ msg-devkit compile
@msg-chain/devkit 编译引擎
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
发现 3 个合约包:
├─ my_ai_agent (contracts/cosmwasm/all/my_ai_agent)
├─ data_oracle (contracts/cosmwasm/all/data_oracle)
└─ token_bridge (contracts/cosmwasm/all/token_bridge)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
编译 my_ai_agent...
cargo build --target wasm32-unknown-unknown --release
✓ 编译成功 (12.4s)
→ wasm-opt -Oz artifacts/my_ai_agent.wasm
✓ 优化完成 (1.2s, 减少 23% 大小)
编译 data_oracle...
cargo build --target wasm32-unknown-unknown --release
✓ 编译成功 (8.7s)
→ artifacts/data_oracle.wasm
编译 token_bridge...
cargo build --target wasm32-unknown-unknown --release
✓ 编译成功 (15.3s)
→ artifacts/token_bridge.wasm
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✓ 3/3 合约编译成功
输出目录: ./artifacts
⏱ 总耗时: 36.4s
编译产物清单:
├─ artifacts/my_ai_agent.wasm (148 KB)
├─ artifacts/data_oracle.wasm (112 KB)
├─ artifacts/token_bridge.wasm (196 KB)
└─ artifacts/checksums.txt
3.4 msg-devkit test
运行项目中所有合约的单元测试和集成测试。
说明: 内部封装了 MSG Chain 的实际测试命令 cargo test。自动发现 contracts/cosmwasm/all/ 目录下的所有合约包,对每个合约执行 cargo test。同时可运行 TypeScript 集成测试。
实际包装的命令:
# 对每个合约执行:
cd contracts/cosmwasm/all/{name} && cargo test
# 可选集成测试:
npx jest test/integration/
选项:
| 选项 | 默认值 | 描述 |
|---|---|---|
--contract <name> |
全部 | 仅测试指定合约 |
--test-name <pattern> |
— | 仅运行匹配的测试 |
--integration |
false |
包含 TypeScript 集成测试 |
--coverage |
false |
生成测试覆盖率报告 |
--verbose |
false |
详细输出 |
--no-rust |
false |
跳过 Rust 测试 |
用法示例:
# 运行所有合约测试
msg-devkit test
# 测试指定合约
msg-devkit test --contract my_ai_agent
# 运行特定测试函数
msg-devkit test --test-name test_instantiate
# 包含集成测试
msg-devkit test --integration
# 生成覆盖率报告
msg-devkit test --coverage
# 详细输出
msg-devkit test --verbose
测试输出示例:
$ msg-devkit test
@msg-chain/devkit 测试运行器
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
发现 3 个合约包需要测试
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
my_ai_agent — cargo test
running 8 tests
✓ test_instantiate (0.02s)
✓ test_execute_create_agent (0.15s)
✓ test_execute_update_agent (0.12s)
✓ test_execute_delete_agent (0.08s)
✓ test_query_get_agent (1,000,000,000 attoMSG)
✓ test_query_list_agents (1,000,000,000 attoMSG)
✓ test_execute_deposit (0.09s)
✓ test_execute_withdraw (0.11s)
✓ 8 passed, 0 failed (0.59s)
data_oracle — cargo test
running 12 tests
✓ 12 passed, 0 failed (0.87s)
token_bridge — cargo test
running 15 tests
✓ 15 passed, 0 failed (1.23s)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✓ 35/35 测试通过
⏱ 总耗时: 2.69s
集成测试 — jest
✓ my_ai_agent.integration.test.ts (4.2s)
✓ deployment.test.ts (2.1s)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✓ 全部测试通过
3.5 msg-devkit deploy
部署合约到目标网络。
说明: 实现完整的 CosmWasm 合约部署流程:Store Code → Instantiate Contract。内部使用 msgd tx wasm store 和 msgd tx wasm instantiate 实际命令。类似 Truffle 的 truffle migrate。
实际包装的命令:
# Store Code
msgd tx wasm store artifacts/my_contract.wasm \
--from $SIGNER \
--gas auto --gas-adjustment 1.3 \
--gas-prices 1000000000attoMSG \
--chain-id msg-chain-1 \
--node tcp://localhost:26657 -y
# Instantiate
msgd tx wasm instantiate $CODE_ID '{"key":"value"}' \
--label "my-contract" \
--admin $ADMIN \
--from $SIGNER \
--chain-id msg-chain-1 \
--node tcp://localhost:26657 -y
选项:
| 选项 | 默认值 | 描述 |
|---|---|---|
--network <name> |
local |
目标网络 |
--contract <name> |
全部 | 仅部署指定合约 |
--signer <name> |
— | 签名账户名称 |
--gas <price> |
1000000000attoMSG |
Gas 价格 |
--gas-limit <limit> |
3000000 |
Gas 上限 |
--label <label> |
— | 合约实例标签 |
--admin <address> |
— | 合约管理员地址 |
--no-admin |
false |
移除管理员(合约不可变) |
--dry-run |
false |
模拟部署 |
--guard |
false |
启用 Guard 模式(生产环境必用) |
用法示例:
# 部署到本地网络
msg-devkit deploy --network local
# 部署指定合约
msg-devkit deploy --contract my_ai_agent --network local
# 部署 + Guard 模式(生产必用)
msg-devkit deploy --network testnet --guard
# 无管理员部署(合约不可变)
msg-devkit deploy --no-admin
# 指定 gas 和签名者
msg-devkit deploy --network local --signer validator --gas 1000000000attoMSG
# 模拟部署(不发送交易)
msg-devkit deploy --dry-run
# 部署到主网(需经过审批门)
msg-devkit deploy --network mainnet --guard
部署输出示例:
$ msg-devkit deploy --network local --contract my_ai_agent
@msg-chain/devkit 部署引擎
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
目标网络: local (msg-chain-1)
签名账户: validator
Gas price: 1000000000attoMSG | Gas limit: 3,000,000
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
步骤 1/3: Store Code — my_ai_agent.wasm
→ 读取 artifacts/my_ai_agent.wasm (148 KB)
→ 发送 StoreCode 交易...
✓ Code ID: 1 (tx: 9A2B3C...DEF)
⏱ 确认时间: 5.2s (1 block)
步骤 2/3: Instantiate Contract
→ 消息: {"agent_name":"MyAgent","owner":"msg1..."}
→ 发送 Instantiate 交易...
✓ 合约地址: msg1x2y3z4...abc (tx: 1D2E3F...456)
⏱ 确认时间: 5.1s (1 block)
步骤 3/3: 验证部署
✓ 合约状态: 活跃
✓ 代码 ID 1 对应 my_ai_agent
✓ 管理员: msg1admin...xyz
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✓ 部署成功!
合约地址: msg1x2y3z4...abc
Code ID: 1
交易: https://explorer.msgchain.org/tx/9A2B3C...DEF
部署记录已保存至 ./deployments/local.json
部署记录文件 (deployments/local.json):
{
"network": "local",
"chainId": "msg-chain-1",
"timestamp": "2026-07-06T10:30:00Z",
"contracts": {
"my_ai_agent": {
"codeId": 1,
"address": "msg1x2y3z4...abc",
"txHash": "9A2B3C...DEF",
"label": "my-ai-agent-v1",
"admin": "msg1admin...xyz",
"wasmHash": "sha256:a1b2c3d4...",
"instantiateMsg": {
"agent_name": "MyAgent",
"owner": "msg1..."
}
}
},
"gasUsed": "2450000"
}
3.6 msg-devkit verify
验证已部署合约的源码。
说明: 将合约 WASM 哈希与链上存储的哈希进行比对,确保合约源代码与部署内容一致。可选的源码上传功能支持将合约源代码发布到区块浏览器供公开验证。
选项:
| 选项 | 默认值 | 描述 |
|---|---|---|
--network <name> |
local |
目标网络 |
--contract <name> |
— | 合约名称(对应 artifacts) |
--address <address> |
— | 合约地址 |
--code-id <id> |
— | Code ID |
--upload |
false |
将源码上传到验证服务 |
用法示例:
# 验证 artifacts 中所有合约
msg-devkit verify --network local
# 验证指定合约
msg-devkit verify --contract my_ai_agent --network local
# 验证指定地址
msg-devkit verify --address msg1x2y3z4...abc
# 验证指定 Code ID
msg-devkit verify --code-id 1 --network testnet
# 验证并上传源码到浏览器
msg-devkit verify --contract my_ai_agent --upload
验证输出示例:
$ msg-devkit verify --network local
✓ 验证通过 — my_ai_agent
本地 SHA256: a1b2c3d4e5f6...
链上 SHA256: a1b2c3d4e5f6...
✓ 匹配
✓ 验证通过 — data_oracle
本地 SHA256: b2c3d4e5f6a1...
链上 SHA256: b2c3d4e5f6a1...
✓ 匹配
✗ 验证失败 — token_bridge
本地 SHA256: c3d4e5f6a1b2...
链上 SHA256: d4e5f6a1b2c3...
✗ 不匹配 — 源代码已更改,请重新编译
3.7 msg-devkit console
启动交互式控制台,用于与已部署合约进行交互。
说明: 提供一个 Node.js REPL 环境,内置 CosmJS 客户端、已加载的合约实例和 MSG Chain 连接。类似 Truffle Console 和 Hardhat Console。内置 client(SigningCosmWasmClient)、accounts、contracts、msg(工具函数)等全局变量。
选项:
| 选项 | 默认值 | 描述 |
|---|---|---|
--network <name> |
local |
目标网络 |
--signer <name> |
— | 签名账户名称 |
--contract <name> |
— | 预加载指定合约 |
--eval <code> |
— | 执行单行代码后退出 |
--file <path> |
— | 执行脚本文件后退出 |
用法示例:
# 启动控制台(默认本地网络)
msg-devkit console
# 加载指定网络的合约
msg-devkit console --network testnet
# 执行单条查询
msg-devkit console --eval 'contract.query({get_agent: {id: "1"}})'
# 执行脚本文件
msg-devkit console --file scripts/query_all_agents.ts
控制台交互示例:
$ msg-devkit console --network local
@msg-chain/devkit 控制台
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
网络: local (msg-chain-1)
签名者: validator
Gas: 1000000000attoMSG
可用变量:
client — CosmJS SigningCosmWasmClient
accounts — [name: validator, address: msg1...]
contracts — 已部署合约实例
msg — CosmJS 工具函数
tx — 发送交易
示例:
await client.queryContractSmart("msg1...", { get_agent: { id: "1" } })
await contract.increment()
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
msg> await client.getChainId()
'msg-chain-1'
msg> const agent = await client.queryContractSmart(
... "msg1x2y3z4...abc",
... { get_agent: { id: "1" } }
... )
{ id: "1", name: "MyAgent", owner: "msg1...", status: "active" }
msg> agent.name
'MyAgent'
msg> const tx = await client.execute(
... "msg1x2y3z4...abc",
... { update_agent: { id: "1", status: "paused" } },
... "auto"
... )
{ transactionHash: "E5F6...789", height: 1284 }
msg> .exit
再见!
3.8 msg-devkit node
管理本地 MSG Chain 开发节点。
说明: 封装 Docker Compose 和 msgd start 命令,提供一键启动、停止、查看本地节点的能力。支持多节点集群模式。
子命令:
| 子命令 | 描述 |
|---|---|
start |
启动本地节点 |
stop |
停止本地节点 |
status |
查看节点状态 |
logs |
查看节点日志 |
reset |
重置节点数据 |
选项(对 start 子命令):
| 选项 | 默认值 | 描述 |
|---|---|---|
--clean |
false |
启动时清理旧数据 |
--validators <n> |
1 |
验证节点数量 |
--ports |
26657,1317,9090 |
暴露端口 |
--detach |
true |
后台运行 |
用法示例:
# 启动本地节点
msg-devkit node start
# 启动并清理旧数据
msg-devkit node start --clean
# 启动 3 节点集群
msg-devkit node start --validators 3
# 前台运行
msg-devkit node start --detach false
# 停止节点
msg-devkit node stop
# 查看状态
msg-devkit node status
# 查看日志
msg-devkit node logs
# 重置数据(清空所有链上数据)
msg-devkit node reset
节点启动输出示例:
$ msg-devkit node start --clean
@msg-chain/devkit 节点管理
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
清理旧数据...
✓ /tmp/msg-chain/data 已清理
启动 Docker Compose 服务...
✓ msg-chain-node-1 已启动
✓ msg-chain-db-1 已启动
等待节点同步... (5s)
✓ 节点正在产生区块
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ 本地节点运行中
RPC: http://localhost:26657
REST: http://localhost:1317
gRPC: localhost:9090
Faucet: http://localhost:8000
Explorer:http://localhost:3000
验证者信息:
├─ 地址: msg1validator...abc
├─ 质押: 10000000umsg
└─ 状态: 投票中
Block Time: 5s
共识机制: Round-Robin + DAR
区块高度: 24 | 交易数: 12
3.9 msg-devkit faucet
从水龙头申请测试代币。
说明: 为开发测试网络申请 umsg 测试代币。支持自动创建水龙头请求、查看余额。
选项:
| 选项 | 默认值 | 描述 |
|---|---|---|
--address <addr> |
— | 接收地址 |
--amount <amount> |
1000000 |
申请数量(umsg) |
--network <name> |
local |
目标网络 |
用法示例:
# 申请默认数量测试代币(本地网络)
msg-devkit faucet --address msg1...
# 指定申请数量
msg-devkit faucet --address msg1... --amount 5000000
# 从 testnet 水龙头申请
msg-devkit faucet --address msg1... --network testnet
输出示例:
$ msg-devkit faucet --address msg1abc...def --amount 1000000
@msg-chain/devkit 水龙头
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
地址: msg1abc...def
数量: 1000000 umsg (1 MSG)
网络: local
正在处理...
✓ 成功! 交易哈希: FAUCET...123
新余额:
账户: msg1abc...def
余额: 1000000 umsg
3.10 msg-devkit schema
生成合约的 JSON Schema 文件。
说明: 对 CosmWasm 合约的 QueryMsg 和 ExecuteMsg 生成 JSON Schema,便于前端开发、API 文档生成和类型安全交互。内部使用 cargo run --bin schema 或从 msg.rs 源码解析。
选项:
| 选项 | 默认值 | 描述 |
|---|---|---|
--contract <name> |
全部 | 仅生成指定合约 |
--out-dir <path> |
./schemas |
输出目录 |
--format |
json |
格式: json, yaml |
用法示例:
# 生成所有合约的 Schema
msg-devkit schema
# 生成指定合约
msg-devkit schema --contract my_ai_agent
# 指定输出目录
msg-devkit schema --out-dir ./api/schemas
输出示例:
$ msg-devkit schema
@msg-chain/devkit Schema 生成器
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
my_ai_agent
✓ InstantiateMsg → schemas/my_ai_agent/instantiate_msg.json
✓ ExecuteMsg → schemas/my_ai_agent/execute_msg.json
✓ QueryMsg → schemas/my_ai_agent/query_msg.json
✓ State → schemas/my_ai_agent/state.json
data_oracle
✓ schemas/data_oracle/ (4 files)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✓ 8 schema files generated in schemas/
3.11 msg-devkit network
管理 MSG Chain 网络配置。
说明: 列出、添加、切换网络配置。支持预设网络和自定义网络。
子命令:
| 子命令 | 描述 |
|---|---|
list |
列出所有网络配置 |
add <name> |
添加新网络 |
use <name> |
切换默认网络 |
选项(对 add 子命令):
| 选项 | 默认值 | 描述 |
|---|---|---|
--rpc <url> |
— | RPC 端点 |
--rest <url> |
— | REST 端点 |
--chain-id <id> |
— | 链 ID |
--explorer <url> |
— | 浏览器 URL |
用法示例:
# 列出所有网络
msg-devkit network list
# 添加自定义网络
msg-devkit network add mynet \
--rpc https://rpc.mynet.msgchain.org \
--rest https://rest.mynet.msgchain.org \
--chain-id mynet-1
# 切换默认网络
msg-devkit network use testnet
网络列表输出示例:
$ msg-devkit network list
@msg-chain/devkit 网络配置
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
local (默认) msg-chain-1
├─ RPC: http://localhost:26657
├─ REST: http://localhost:1317
└─ Gas: 1000000000attoMSG
devnet devnet-1
├─ RPC: https://rpc.devnet.msgchain.org
├─ REST: https://rest.devnet.msgchain.org
└─ Gas: 1000000000attoMSG
testnet testnet-1
├─ RPC: https://rpc.testnet.msgchain.org
├─ REST: https://rest.testnet.msgchain.org
└─ Gas: 1000000000attoMSG
mainnet msg-chain-1
├─ RPC: https://rpc.mainnet.msgchain.org
├─ REST: https://rest.mainnet.msgchain.org
└─ Gas: 1000000000attoMSG(推荐高 gas)
3.12 msg-devkit publish
受保护的生产部署流程。
说明: 这是 devkit 最核心的安全特性 — 带审批门(Approval Gates)的生产发布命令。必须使用 --guard 标志,否则命令拒绝执行。发布前依次检查 4 道审批门,收集人工审批证据。
requires_human_approval: true — 此命令在所有生产环境操作前要求人工批准。
实际审批门定义(来自 MSG Chain approval_gates.json):
| 审批门 ID | 阶段 | 需要人工输入 |
|---|---|---|
| scope_lock | before_codegen | 产品目标, 业务规则, 验收标准 |
| secret_injection | before_real_write_or_deploy | 签名账户, API token, 环境变量 |
| production_release | before_launch | 最终审批, 回滚确认 |
| governance_or_treasury | high_risk_write | DAO通过, timelock结束 |
选项:
| 选项 | 默认值 | 描述 |
|---|---|---|
--guard |
false |
必须指定 — 启用审批门 |
--network <name> |
— | 目标网络(必须是 mainnet) |
--contract <name> |
全部 | 发布的合约 |
--signer <name> |
— | 签名账户 |
--tag <version> |
— | 发布版本标签 |
--skip-gates <ids> |
— | 跳过指定审批门(需要特殊权限) |
--dry-run |
false |
模拟发布 |
用法示例:
# Guard 模式发布(完整审批流程)
msg-devkit publish --guard --network mainnet --tag v1.0.0
# 跳过特定审批门(需要管理员权限)
msg-devkit publish --guard --network mainnet --skip-gates governance_or_treasury
发布流程输出示例:
$ msg-devkit publish --guard --network mainnet --tag v1.0.0
@msg-chain/devkit — GUARD 模式发布
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
⚠️ 生产环境部署 — 审批门已启用
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
审批门 1/4: scope_lock (before_codegen)
需要审批: 产品目标, 业务规则, 验收标准
请输入产品目标:
> 发布 AI Agent 合约 v1.0.0 至主网
请输入业务规则:
> Agent 创建需支付 0.1 MSG 押金
请输入验收标准:
> 所有测试通过, Code ID 对应正确哈希
✓ 已记录
审批门 2/4: secret_injection (before_real_write_or_deploy)
需要审批: 签名账户, API token, 环境变量
请输入签名账户:
> mainnet-deployer-1
请输入 API token:
> ******** (安全输入)
请输入环境变量确认:
> NODE_ENV=production, MSG_NETWORK=mainnet
✓ 已记录
审批门 3/4: production_release (before_launch)
需要审批: 最终审批, 回滚确认
最终审批确认 (yes/no):
> yes
回滚方案确认 (yes/no):
> yes
✓ 已记录
审批门 4/4: governance_or_treasury (high_risk_write)
需要审批: DAO通过, timelock结束
DAO 提案通过确认 (yes/no):
> yes
Timelock 结束确认 (yes/no):
> yes
✓ 已记录
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ 全部审批门已通过 — 开始发布流程
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
步骤 1/4: 编译合约 (release profile)
✓ my_ai_agent — compiled (optimized)
步骤 2/4: 存储代码 — Store Code
✓ Code ID: 42 (tx: MAINNET...ABC)
步骤 3/4: 实例化合约
✓ 合约地址: msg1production...xyz
步骤 4/4: 验证与证据收集
✓ 合约验证通过
✓ 交易哈希已记录: MAINNET...ABC
✓ 区块确认数: 12
✓ 余额检查通过
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ 发布完成 (v1.0.0)
证据存档: .approval-evidence/v1.0.0/
├─ scope_lock.md
├─ secret_injection.md
├─ production_release.md
├─ governance_or_treasury.md
├─ deployment_receipt.json
└─ post_state_query.json
4. 完整实现代码
4.1 项目结构
@msg-chain/devkit/
├── package.json
├── tsconfig.json
├── src/
│ ├── index.ts # 入口
│ ├── cli.ts # CLI 主程序 (commander)
│ ├── commands/
│ │ ├── init.ts # msg-devkit init
│ │ ├── compile.ts # msg-devkit compile
│ │ ├── test.ts # msg-devkit test
│ │ ├── deploy.ts # msg-devkit deploy
│ │ ├── verify.ts # msg-devkit verify
│ │ ├── console.ts # msg-devkit console
│ │ ├── node.ts # msg-devkit node
│ │ ├── faucet.ts # msg-devkit faucet
│ │ ├── schema.ts # msg-devkit schema
│ │ ├── network.ts # msg-devkit network
│ │ └── publish.ts # msg-devkit publish
│ ├── core/
│ │ ├── compile-engine.ts # 编译引擎
│ │ ├── test-runner.ts # 测试运行器
│ │ ├── deploy-engine.ts # 部署引擎
│ │ ├── scaffold.ts # 脚手架生成器
│ │ ├── node-manager.ts # 节点管理器
│ │ ├── guard.ts # Guard 审批门引擎
│ │ ├── ci-generator.ts # CI 模板生成器
│ │ └── schema-generator.ts # Schema 生成器
│ ├── config/
│ │ ├── config-loader.ts # 配置加载
│ │ ├── defaults.ts # 默认配置
│ │ └── networks.ts # 网络管理
│ ├── templates/
│ │ ├── contract/ # 合约模板文件
│ │ ├── dapp/ # dApp 模板文件
│ │ └── ci/ # CI 模板文件
│ └── types/
│ └── index.ts # 类型定义
├── artifacts/ # 编译输出
└── schemas/ # Schema 输出
4.2 package.json
{
"name": "@msg-chain/devkit",
"version": "1.0.0",
"description": "MSG Chain 智能合约开发工具链 — 类似 Hardhat 的任务运行器 + Truffle 的合约管理",
"keywords": ["msg-chain", "cosmwasm", "blockchain", "smart-contracts", "cli"],
"homepage": "https://github.com/msg-chain/devkit",
"repository": {
"type": "git",
"url": "https://github.com/msg-chain/devkit.git"
},
"license": "Apache-2.0",
"bin": {
"msg-devkit": "./dist/index.js"
},
"files": ["dist/", "templates/", "README.md"],
"scripts": {
"build": "tsc",
"prepublishOnly": "npm run build",
"dev": "ts-node src/index.ts",
"test": "jest",
"lint": "eslint src/ --ext .ts",
"watch": "tsc --watch"
},
"engines": {
"node": ">=18.0.0"
},
"dependencies": {
"commander": "^12.0.0",
"chalk": "^5.3.0",
"ora": "^8.0.1",
"inquirer": "^9.2.0",
"cosmjs-types": "^0.9.0",
"@cosmjs/stargate": "^0.32.0",
"@cosmjs/cosmwasm-stargate": "^0.32.0",
"@cosmjs/proto-signing": "^0.32.0",
"@cosmjs/encoding": "^0.32.0",
"@cosmjs/faucet": "^0.32.0",
"fs-extra": "^11.2.0",
"glob": "^10.3.0",
"listr2": "^8.0.0",
"conf": "^12.0.0",
"execa": "^8.0.0",
"chokidar": "^3.6.0",
"semver": "^7.6.0",
"js-yaml": "^4.1.0",
"text-table": "^0.2.0"
},
"devDependencies": {
"@types/node": "^20.11.0",
"@types/fs-extra": "^11.0.4",
"@types/glob": "^8.1.0",
"@types/inquirer": "^9.0.7",
"@types/semver": "^7.5.8",
"@types/text-table": "^0.2.5",
"typescript": "^5.4.0",
"ts-node": "^10.9.0",
"jest": "^29.7.0",
"ts-jest": "^29.1.0",
"@types/jest": "^29.5.0",
"eslint": "^8.56.0",
"@typescript-eslint/eslint-plugin": "^7.0.0",
"@typescript-eslint/parser": "^7.0.0"
}
}
4.3 tsconfig.json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2022"],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist", "templates"]
}
4.4 入口与 CLI 框架 (src/index.ts)
#!/usr/bin/env node
import { Command } from 'commander';
import chalk from 'chalk';
import figlet from 'figlet';
import { initCommand } from './commands/init.js';
import { compileCommand } from './commands/compile.js';
import { testCommand } from './commands/test.js';
import { deployCommand } from './commands/deploy.js';
import { verifyCommand } from './commands/verify.js';
import { consoleCommand } from './commands/console.js';
import { nodeCommand } from './commands/node.js';
import { faucetCommand } from './commands/faucet.js';
import { schemaCommand } from './commands/schema.js';
import { networkCommand } from './commands/network.js';
import { publishCommand } from './commands/publish.js';
import { loadConfig } from './config/config-loader.js';
const VERSION = '1.0.0';
function showBanner(): void {
console.log(
chalk.cyan(
figlet.textSync('MSG Chain DevKit', { font: 'ANSI Shadow' })
)
);
console.log(chalk.gray('@msg-chain/devkit v' + VERSION + ' — MSG Chain 智能合约开发工具链'));
console.log(chalk.gray('类似 Hardhat 的任务运行器 + 类似 Truffle 的合约管理\n'));
}
async function main() {
showBanner();
const program = new Command();
program
.name('msg-devkit')
.description('MSG Chain 智能合约开发工具链 — CosmWasm 合约开发、编译、测试、部署一站式工具')
.version(VERSION);
program
.command('init')
.argument('[project-name]', '项目名称')
.option('--template <type>', '项目类型 (contract, dapp, fullstack)', 'contract')
.option('--contract-name <name>', '合约名称', 'my_contract')
.option('--framework <lib>', '前端框架 (react, next, vanilla)', 'react')
.option('--language <lang>', '语言 (typescript, javascript)', 'typescript')
.option('--ci <provider>', 'CI 模板 (github-actions)')
.option('--yes', '跳过确认', false)
.description('创建新的 MSG Chain 项目')
.action(initCommand);
program
.command('compile')
.option('--contract <name>', '仅编译指定合约')
.option('--optimize <bool>', '启用 wasm-opt 优化', 'true')
.option('--optimize-level <level>', '优化级别 (z, 2, 3)', 'z')
.option('--out-dir <path>', '输出目录', './artifacts')
.option('--watch', '监听模式', false)
.option('--force', '强制重新编译', false)
.description('编译 CosmWasm 合约 (封装 cargo build --target wasm32-unknown-unknown --release)')
.action(compileCommand);
program
.command('test')
.option('--contract <name>', '仅测试指定合约')
.option('--test-name <pattern>', '仅运行匹配的测试')
.option('--integration', '包含集成测试', false)
.option('--coverage', '生成覆盖率报告', false)
.option('--verbose', '详细输出', false)
.option('--no-rust', '跳过 Rust 测试', false)
.description('运行合约测试 (封装 cargo test)')
.action(testCommand);
program
.command('deploy')
.option('--network <name>', '目标网络', 'local')
.option('--contract <name>', '仅部署指定合约')
.option('--signer <name>', '签名账户')
.option('--gas <price>', 'Gas 价格', '1000000000attoMSG')
.option('--gas-limit <limit>', 'Gas 上限', '3000000')
.option('--label <label>', '合约实例标签')
.option('--admin <address>', '合约管理员地址')
.option('--no-admin', '移除管理员(合约不可变)', false)
.option('--dry-run', '模拟部署', false)
.option('--guard', '启用 Guard 审批门模式', false)
.description('部署合约到目标网络 (封装 msgd tx wasm store/instantiate)')
.action(deployCommand);
program
.command('verify')
.option('--network <name>', '目标网络', 'local')
.option('--contract <name>', '合约名称')
.option('--address <address>', '合约地址')
.option('--code-id <id>', 'Code ID')
.option('--upload', '上传源码到验证服务', false)
.description('验证已部署合约的源代码')
.action(verifyCommand);
program
.command('console')
.option('--network <name>', '目标网络', 'local')
.option('--signer <name>', '签名账户')
.option('--contract <name>', '预加载合约')
.option('--eval <code>', '执行单行代码后退出')
.option('--file <path>', '执行脚本文件后退出')
.description('启动交互式控制台 (REPL + CosmJS)')
.action(consoleCommand);
const nodeCmd = program
.command('node')
.description('管理本地 MSG Chain 节点');
nodeCmd
.command('start')
.option('--clean', '启动时清理旧数据', false)
.option('--validators <n>', '验证节点数量', '1')
.option('--ports <ports>', '暴露端口', '26657,1317,9090')
.option('--detach', '后台运行', 'true')
.description('启动本地节点')
.action((opts) => nodeCommand('start', opts));
nodeCmd
.command('stop')
.description('停止本地节点')
.action(() => nodeCommand('stop', {}));
nodeCmd
.command('status')
.description('查看节点状态')
.action(() => nodeCommand('status', {}));
nodeCmd
.command('logs')
.description('查看节点日志')
.action(() => nodeCommand('logs', {}));
nodeCmd
.command('reset')
.description('重置节点数据')
.action(() => nodeCommand('reset', {}));
program
.command('faucet')
.option('--address <addr>', '接收地址')
.option('--amount <amount>', '申请数量 (umsg)', '1000000')
.option('--network <name>', '目标网络', 'local')
.description('从水龙头申请测试代币')
.action(faucetCommand);
program
.command('schema')
.option('--contract <name>', '仅生成指定合约')
.option('--out-dir <path>', '输出目录', './schemas')
.option('--format <format>', '输出格式 (json, yaml)', 'json')
.description('生成合约 JSON Schema')
.action(schemaCommand);
const networkCmd = program
.command('network')
.description('管理网络配置');
networkCmd
.command('list')
.description('列出所有网络配置')
.action(() => networkCommand('list', {}));
networkCmd
.command('add')
.argument('<name>', '网络名称')
.option('--rpc <url>', 'RPC 端点')
.option('--rest <url>', 'REST 端点')
.option('--chain-id <id>', '链 ID')
.option('--explorer <url>', '浏览器 URL')
.description('添加新网络')
.action((name, opts) => networkCommand('add', { ...opts, name }));
networkCmd
.command('use')
.argument('<name>', '网络名称')
.description('切换默认网络')
.action((name) => networkCommand('use', { name }));
program
.command('publish')
.option('--guard', '启用 Guard 审批门模式 (生产环境必须)', false)
.option('--network <name>', '目标网络')
.option('--contract <name>', '发布的合约')
.option('--signer <name>', '签名账户')
.option('--tag <version>', '发布版本标签')
.option('--skip-gates <ids>', '跳过的审批门 ID 列表,逗号分隔')
.option('--dry-run', '模拟发布', false)
.description('受保护的生产部署 (requires_human_approval: true)')
.action(publishCommand);
await program.parseAsync(process.argv);
}
main().catch((err) => {
console.error(chalk.red('\n错误: ' + err.message));
process.exit(1);
});
4.5 类型定义 (src/types/index.ts)
export interface NetworkConfig {
name: string;
chainId: string;
rpcUrl: string;
restUrl: string;
explorerUrl?: string;
gasPrice: string;
gasLimit: number;
denom: string;
faucetUrl?: string;
bech32Config: Bech32Config;
}
export interface Bech32Config {
bech32PrefixAccAddr: string;
bech32PrefixAccPub: string;
bech32PrefixValAddr: string;
bech32PrefixValPub: string;
bech32PrefixConsAddr: string;
bech32PrefixConsPub: string;
}
export interface MsgChainConfig {
projectName: string;
networks: Record<string, NetworkConfig>;
defaultNetwork: string;
contractsPath: string;
artifactsDir: string;
compiler: {
optimize: boolean;
optimizeLevel: 'z' | '2' | '3';
target: string;
};
deploy: {
gasPrice: string;
gasLimit: number;
signer: string;
};
}
export interface DeployResult {
network: string;
chainId: string;
timestamp: string;
contracts: Record<string, ContractDeployInfo>;
gasUsed: string;
}
export interface ContractDeployInfo {
codeId: number;
address: string;
txHash: string;
label: string;
admin: string;
wasmHash: string;
instantiateMsg: Record<string, unknown>;
}
export interface ApprovalGate {
id: string;
stage: 'before_codegen' | 'before_real_write_or_deploy' | 'before_launch' | 'high_risk_write';
requiredHumanInputs: string[];
}
export interface ApprovalEvidence {
gate: ApprovalGate;
responses: Record<string, string>;
timestamp: string;
approvedBy: string;
}
export interface ContractPackage {
name: string;
path: string;
cargoPath: string;
}
export interface CompileResult {
contractName: string;
success: boolean;
duration: number;
wasmSize?: number;
optimizedSize?: number;
error?: string;
}
export interface TestResult {
contractName: string;
passed: number;
failed: number;
duration: number;
tests: Array<{
name: string;
status: 'passed' | 'failed';
duration: number;
}>;
}
export interface NetworkOperationResult {
success: boolean;
txHash?: string;
codeId?: number;
contractAddress?: string;
height?: number;
gasUsed?: string;
error?: string;
}
export interface ScaffoldOptions {
projectName: string;
template: 'contract' | 'dapp' | 'fullstack';
contractName?: string;
framework?: string;
language?: string;
ci?: string;
yes?: boolean;
}
4.6 配置系统 (src/config/defaults.ts)
import { MsgChainConfig, NetworkConfig } from '../types/index.js';
export const BECH32_PREFIX = 'msg';
export const CHAIN_ID = 'msg-chain-1';
export const COIN_TYPE = 118;
export const DECIMALS = 18;
export const DENOM = 'umsg';
export const GAS_PRESETS = {
low: '1000000000attoMSG',
average: '1000000000attoMSG',
high: '1000000000attoMSG',
};
export const DEFAULT_NETWORKS: Record<string, NetworkConfig> = {
local: {
name: 'local',
chainId: 'msg-chain-1',
rpcUrl: 'http://localhost:26657',
restUrl: 'http://localhost:1317',
explorerUrl: 'http://localhost:3000',
gasPrice: '1000000000attoMSG',
gasLimit: 3000000,
denom: DENOM,
faucetUrl: 'http://localhost:8000',
bech32Config: {
bech32PrefixAccAddr: 'msg',
bech32PrefixAccPub: 'msgpub',
bech32PrefixValAddr: 'msgvaloper',
bech32PrefixValPub: 'msgvaloperpub',
bech32PrefixConsAddr: 'msgvalcons',
bech32PrefixConsPub: 'msgvalconspub',
},
},
devnet: {
name: 'devnet',
chainId: 'devnet-1',
rpcUrl: 'https://rpc.devnet.msgchain.org',
restUrl: 'https://rest.devnet.msgchain.org',
explorerUrl: 'https://explorer.devnet.msgchain.org',
gasPrice: '1000000000attoMSG',
gasLimit: 3000000,
denom: DENOM,
bech32Config: {
bech32PrefixAccAddr: 'msg',
bech32PrefixAccPub: 'msgpub',
bech32PrefixValAddr: 'msgvaloper',
bech32PrefixValPub: 'msgvaloperpub',
bech32PrefixConsAddr: 'msgvalcons',
bech32PrefixConsPub: 'msgvalconspub',
},
},
testnet: {
name: 'testnet',
chainId: 'testnet-1',
rpcUrl: 'https://rpc.testnet.msgchain.org',
restUrl: 'https://rest.testnet.msgchain.org',
explorerUrl: 'https://explorer.testnet.msgchain.org',
gasPrice: '1000000000attoMSG',
gasLimit: 5000000,
denom: DENOM,
bech32Config: {
bech32PrefixAccAddr: 'msg',
bech32PrefixAccPub: 'msgpub',
bech32PrefixValAddr: 'msgvaloper',
bech32PrefixValPub: 'msgvaloperpub',
bech32PrefixConsAddr: 'msgvalcons',
bech32PrefixConsPub: 'msgvalconspub',
},
},
mainnet: {
name: 'mainnet',
chainId: 'msg-chain-1',
rpcUrl: 'https://rpc.mainnet.msgchain.org',
restUrl: 'https://rest.mainnet.msgchain.org',
explorerUrl: 'https://explorer.msgchain.org',
gasPrice: '1000000000attoMSG',
gasLimit: 5000000,
denom: DENOM,
bech32Config: {
bech32PrefixAccAddr: 'msg',
bech32PrefixAccPub: 'msgpub',
bech32PrefixValAddr: 'msgvaloper',
bech32PrefixValPub: 'msgvaloperpub',
bech32PrefixConsAddr: 'msgvalcons',
bech32PrefixConsPub: 'msgvalconspub',
},
},
};
export const DEFAULT_CONFIG: MsgChainConfig = {
projectName: 'msg-chain-project',
networks: DEFAULT_NETWORKS,
defaultNetwork: 'local',
contractsPath: 'contracts/cosmwasm/all',
artifactsDir: 'artifacts',
compiler: {
optimize: true,
optimizeLevel: 'z',
target: 'wasm32-unknown-unknown',
},
deploy: {
gasPrice: '1000000000attoMSG',
gasLimit: 3000000,
signer: '',
},
};
4.7 配置加载器 (src/config/config-loader.ts)
import fs from 'fs-extra';
import path from 'path';
import chalk from 'chalk';
import { MsgChainConfig } from '../types/index.js';
import { DEFAULT_CONFIG } from './defaults.js';
const CONFIG_FILES = [
'msgchain.config.ts',
'msgchain.config.json',
'msgchain.config.js',
];
export async function loadConfig(cwd?: string): Promise<MsgChainConfig> {
const dir = cwd || process.cwd();
for (const filename of CONFIG_FILES) {
const configPath = path.join(dir, filename);
if (await fs.pathExists(configPath)) {
try {
if (filename.endsWith('.ts')) {
const config = await import('file://' + path.resolve(configPath).replace(/\\/g, '/'));
return mergeWithDefaults(config.default || config);
}
if (filename.endsWith('.json')) {
const config = await fs.readJson(configPath);
return mergeWithDefaults(config);
}
if (filename.endsWith('.js')) {
const config = await import('file://' + path.resolve(configPath).replace(/\\/g, '/'));
return mergeWithDefaults(config.default || config);
}
} catch (err) {
console.warn(chalk.yellow('无法加载 ' + filename + ': ' + (err as Error).message));
}
}
}
console.log(chalk.gray('未找到配置文件,使用默认配置'));
return DEFAULT_CONFIG;
}
function mergeWithDefaults(config: Partial<MsgChainConfig>): MsgChainConfig {
return {
...DEFAULT_CONFIG,
...config,
networks: {
...DEFAULT_CONFIG.networks,
...(config.networks || {}),
},
compiler: {
...DEFAULT_CONFIG.compiler,
...(config.compiler || {}),
},
deploy: {
...DEFAULT_CONFIG.deploy,
...(config.deploy || {}),
},
};
}
export async function saveConfig(config: MsgChainConfig, cwd?: string): Promise<void> {
const dir = cwd || process.cwd();
const configPath = path.join(dir, 'msgchain.config.json');
const { networks, ...rest } = config;
await fs.writeJson(configPath, { ...rest, networks }, { spaces: 2 });
console.log(chalk.green('配置已保存至 ' + configPath));
}
export async function writeDefaultConfig(cwd: string): Promise<void> {
const configPath = path.join(cwd, 'msgchain.config.json');
if (await fs.pathExists(configPath)) {
console.log(chalk.yellow(configPath + ' 已存在,跳过'));
return;
}
const config = {
$schema: 'https://raw.githubusercontent.com/msg-chain/devkit/main/schemas/config.schema.json',
...DEFAULT_CONFIG,
};
await fs.writeJson(configPath, config, { spaces: 2 });
console.log(chalk.green('默认配置已创建: ' + configPath));
}
4.8 编译引擎 (src/core/compile-engine.ts)
import path from 'path';
import fs from 'fs-extra';
import { execa } from 'execa';
import chalk from 'chalk';
import ora from 'ora';
import { glob } from 'glob';
import { ContractPackage, CompileResult } from '../types/index.js';
import { loadConfig } from '../config/config-loader.js';
export async function findContractPackages(contractsPath: string): Promise<ContractPackage[]> {
const dirs = await fs.readdir(contractsPath);
const packages: ContractPackage[] = [];
for (const dir of dirs) {
const cargoPath = path.join(contractsPath, dir, 'Cargo.toml');
if (await fs.pathExists(cargoPath)) {
packages.push({
name: dir,
path: path.join(contractsPath, dir),
cargoPath,
});
}
}
return packages;
}
export interface CompileOptions {
contract?: string;
optimize?: boolean;
optimizeLevel?: string;
outDir?: string;
force?: boolean;
}
export async function compileContract(
pkg: ContractPackage,
options: CompileOptions
): Promise<CompileResult> {
const startTime = Date.now();
try {
const contractDir = pkg.path;
// 执行实际的编译命令
const buildArgs = [
'build',
'--target', 'wasm32-unknown-unknown',
'--release',
];
if (options.force) {
buildArgs.push('--force');
}
await execa('cargo', buildArgs, {
cwd: contractDir,
stdio: 'pipe',
});
// 定位 WASM 输出
const wasmOutputDir = path.join(contractDir, 'target', 'wasm32-unknown-unknown', 'release');
const wasmFiles = await glob('*.wasm', { cwd: wasmOutputDir });
if (wasmFiles.length === 0) {
throw new Error('未找到 WASM 输出文件在 ' + wasmOutputDir);
}
const wasmFile = wasmFiles[0];
const wasmPath = path.join(wasmOutputDir, wasmFile);
const wasmSize = (await fs.stat(wasmPath)).size;
const outDir = options.outDir || './artifacts';
await fs.ensureDir(outDir);
const outputPath = path.join(outDir, pkg.name + '.wasm');
await fs.copy(wasmPath, outputPath);
let optimizedSize: number | undefined;
// wasm-opt 优化
if (options.optimize !== false) {
try {
const level = options.optimizeLevel || 'z';
await execa('wasm-opt', [
'-O' + level,
outputPath,
'-o', outputPath,
]);
optimizedSize = (await fs.stat(outputPath)).size;
} catch (optErr) {
console.warn(chalk.yellow('wasm-opt 优化失败(可忽略): ' + (optErr as Error).message));
}
}
const duration = (Date.now() - startTime) / 1000;
return {
contractName: pkg.name,
success: true,
duration,
wasmSize,
optimizedSize,
};
} catch (err) {
return {
contractName: pkg.name,
success: false,
duration: (Date.now() - startTime) / 1000,
error: (err as Error).message,
};
}
}
export async function compileAllContracts(options: CompileOptions): Promise<CompileResult[]> {
const config = await loadConfig();
const contractsPath = path.resolve(config.contractsPath);
let packages = await findContractPackages(contractsPath);
if (options.contract) {
packages = packages.filter((p) => p.name === options.contract);
if (packages.length === 0) {
throw new Error('未找到合约: ' + options.contract);
}
}
console.log(chalk.cyan('\n发现 ' + packages.length + ' 个合约包:\n'));
const results: CompileResult[] = [];
for (const pkg of packages) {
const spinner = ora('编译 ' + pkg.name + '...').start();
const result = await compileContract(pkg, options);
spinner.stop();
if (result.success) {
const sizeInfo = result.optimizedSize
? formatBytes(result.optimizedSize) + ' (优化后)'
: result.wasmSize ? formatBytes(result.wasmSize) : '';
console.log(chalk.green(' ✓ ' + pkg.name + ' — ' + sizeInfo + ' [' + result.duration.toFixed(1) + 's]'));
} else {
console.log(chalk.red(' ✗ ' + pkg.name + ' — ' + result.error));
}
results.push(result);
}
// 生成校验和文件
const outDir = path.resolve(options.outDir || './artifacts');
await generateChecksums(outDir);
const successCount = results.filter((r) => r.success).length;
console.log(chalk.cyan('\n✓ ' + successCount + '/' + results.length + ' 合约编译成功\n'));
return results;
}
async function generateChecksums(outDir: string): Promise<void> {
const wasmFiles = await glob('*.wasm', { cwd: outDir });
if (wasmFiles.length === 0) return;
const crypto = await import('crypto');
const lines: string[] = [];
for (const file of wasmFiles) {
const content = await fs.readFile(path.join(outDir, file));
const hash = crypto.createHash('sha256').update(content).digest('hex');
lines.push(hash + ' ' + file);
}
await fs.writeFile(path.join(outDir, 'checksums.txt'), lines.join('\n'));
}
function formatBytes(bytes: number): string {
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
4.9 测试运行器 (src/core/test-runner.ts)
import { execa } from 'execa';
import chalk from 'chalk';
import ora from 'ora';
import { TestResult } from '../types/index.js';
import { loadConfig } from '../config/config-loader.js';
import { findContractPackages } from './compile-engine.js';
export interface TestOptions {
contract?: string;
testName?: string;
integration?: boolean;
coverage?: boolean;
verbose?: boolean;
noRust?: boolean;
}
export async function runContractTests(
pkg: { name: string; path: string },
options: TestOptions
): Promise<TestResult> {
const startTime = Date.now();
try {
const cargoArgs = ['test'];
if (options.testName) {
cargoArgs.push(options.testName);
}
if (options.verbose) {
cargoArgs.push('--', '--nocapture');
}
const { stdout } = await execa('cargo', cargoArgs, {
cwd: pkg.path,
stdio: 'pipe',
});
const duration = (Date.now() - startTime) / 1000;
const passedMatch = stdout.match(/(\d+) passed/);
const failedMatch = stdout.match(/(\d+) failed/);
const testNames = extractTestNames(stdout);
const tests = testNames.map((name) => ({
name,
status: (stdout.includes(name + ' FAILED') ? 'failed' : 'passed') as 'passed' | 'failed',
duration: 0,
}));
return {
contractName: pkg.name,
passed: passedMatch ? parseInt(passedMatch[1]) : (failedMatch ? 0 : 1),
failed: failedMatch ? parseInt(failedMatch[1]) : 0,
duration,
tests,
};
} catch (err) {
return {
contractName: pkg.name,
passed: 0,
failed: 1,
duration: (Date.now() - startTime) / 1000,
tests: [],
};
}
}
function extractTestNames(output: string): string[] {
const testPattern = /test\s+(\S+)\s+\.\.\.\s+(ok|FAILED)/g;
const names: string[] = [];
let match;
while ((match = testPattern.exec(output)) !== null) {
names.push(match[1]);
}
return names;
}
export async function runAllTests(options: TestOptions): Promise<TestResult[]> {
const config = await loadConfig();
const contractsPath = path.resolve(config.contractsPath);
let packages = await findContractPackages(contractsPath);
if (options.contract) {
packages = packages.filter((p) => p.name === options.contract);
}
if (!options.noRust) {
console.log(chalk.cyan('\n发现 ' + packages.length + ' 个合约包需要测试\n'));
const results: TestResult[] = [];
let totalPassed = 0;
let totalFailed = 0;
for (const pkg of packages) {
const spinner = ora('测试 ' + pkg.name + '...').start();
const result = await runContractTests(pkg, options);
spinner.stop();
if (result.failed === 0) {
console.log(chalk.green(' ✓ ' + pkg.name + ' — ' + result.passed + ' passed [' + result.duration.toFixed(2) + 's]'));
} else {
console.log(chalk.red(' ✗ ' + pkg.name + ' — ' + result.passed + ' passed, ' + result.failed + ' failed [' + result.duration.toFixed(2) + 's]'));
}
totalPassed += result.passed;
totalFailed += result.failed;
results.push(result);
}
const statusSymbol = totalFailed === 0 ? chalk.green('✓') : chalk.red('✗');
console.log(chalk.cyan('\n' + statusSymbol + ' 总计: ' + totalPassed + ' passed, ' + totalFailed + ' failed\n'));
}
if (options.integration) {
console.log(chalk.cyan('运行集成测试 (jest)...\n'));
try {
await execa('npx', ['jest', 'test/integration/'], {
cwd: process.cwd(),
stdio: 'inherit',
});
} catch {
console.log(chalk.yellow(' 集成测试未配置或失败'));
}
}
return [];
}
4.10 部署引擎 (src/core/deploy-engine.ts)
import path from 'path';
import fs from 'fs-extra';
import chalk from 'chalk';
import { execa } from 'execa';
import { loadConfig } from '../config/config-loader.js';
import { NetworkConfig, DeployResult, NetworkOperationResult } from '../types/index.js';
import { findContractPackages } from './compile-engine.js';
export interface DeployOptions {
network?: string;
contract?: string;
signer?: string;
gas?: string;
gasLimit?: string;
label?: string;
admin?: string;
noAdmin?: boolean;
dryRun?: boolean;
guard?: boolean;
outDir?: string;
}
export async function storeCode(
wasmPath: string,
network: NetworkConfig,
signer: string,
gasPrice: string,
gasLimit: string
): Promise<NetworkOperationResult> {
if (!await fs.pathExists(wasmPath)) {
return { success: false, error: 'WASM 文件不存在: ' + wasmPath };
}
// 封装实际命令: msgd tx wasm store
const args = [
'tx', 'wasm', 'store', wasmPath,
'--from', signer,
'--gas', 'auto',
'--gas-adjustment', '1.3',
'--gas-prices', gasPrice,
'--chain-id', network.chainId,
'--node', network.rpcUrl,
'-y', '-o', 'json',
];
if (gasLimit !== 'auto') {
args.splice(4, 0, '--gas', gasLimit);
}
if (process.env.MSG_LOG_LEVEL === 'debug') {
console.log(chalk.gray(' $ msgd ' + args.join(' ')));
}
// 如果在 dry-run 模式,模拟返回
try {
const { stdout } = await execa('msgd', args, { timeout: 60000 });
const codeIdMatch = stdout.match(/code_id["\s:]+(\d+)/i);
const txHashMatch = stdout.match(/txhash["\s:]+"([^"]+)"/i);
return {
success: true,
codeId: codeIdMatch ? parseInt(codeIdMatch[1]) : undefined,
txHash: txHashMatch ? txHashMatch[1] : undefined,
};
} catch (err) {
return {
success: false,
error: (err as Error).message,
};
}
}
export async function instantiateContract(
codeId: number,
initMsg: Record<string, unknown>,
label: string,
network: NetworkConfig,
signer: string,
gasPrice: string,
admin?: string
): Promise<NetworkOperationResult> {
const initMsgStr = JSON.stringify(initMsg);
// 封装实际命令: msgd tx wasm instantiate
const args = [
'tx', 'wasm', 'instantiate', String(codeId), initMsgStr,
'--label', label,
'--from', signer,
'--gas', 'auto',
'--gas-adjustment', '1.3',
'--gas-prices', gasPrice,
'--chain-id', network.chainId,
'--node', network.rpcUrl,
'-y', '-o', 'json',
];
if (admin) {
args.push('--admin', admin);
}
try {
const { stdout } = await execa('msgd', args, { timeout: 60000 });
const contractAddrMatch = stdout.match(/contract_address["\s:]+"([^"]+)"/i);
const txHashMatch = stdout.match(/txhash["\s:]+"([^"]+)"/i);
return {
success: true,
contractAddress: contractAddrMatch ? contractAddrMatch[1] : undefined,
txHash: txHashMatch ? txHashMatch[1] : undefined,
};
} catch (err) {
return {
success: false,
error: (err as Error).message,
};
}
}
export async function deployContract(
wasmPath: string,
contractName: string,
initMsg: Record<string, unknown>,
options: DeployOptions,
config: any
): Promise<DeployResult> {
const networkName = options.network || config.defaultNetwork || 'local';
const network = config.networks[networkName];
if (!network) throw new Error('未找到网络配置: ' + networkName);
const signer = options.signer || config.deploy.signer || 'validator';
const gasPrice = options.gas || config.deploy.gasPrice || '1000000000attoMSG';
const label = options.label || contractName + '-' + Date.now();
const admin = options.noAdmin ? undefined : (options.admin || undefined);
const deployResult: DeployResult = {
network: networkName,
chainId: network.chainId,
timestamp: new Date().toISOString(),
contracts: {},
gasUsed: '0',
};
// 步骤 1: Store Code
console.log(chalk.cyan('\n步骤 1/2: Store Code — ' + contractName));
console.log(chalk.gray(' 读取 ' + wasmPath));
const wasmHash = await computeWasmHash(wasmPath);
const storeResult = await storeCode(wasmPath, network, signer, gasPrice, options.gasLimit || '3000000');
if (!storeResult.success) {
throw new Error('Store Code 失败: ' + storeResult.error);
}
console.log(chalk.green(' ✓ Code ID: ' + storeResult.codeId + ' (tx: ' + storeResult.txHash + ')'));
console.log(chalk.gray(' ⏱ 已确认 (1 block)'));
// 步骤 2: Instantiate
console.log(chalk.cyan('\n步骤 2/2: Instantiate Contract'));
console.log(chalk.gray(' 消息: ' + JSON.stringify(initMsg)));
const instantiateResult = await instantiateContract(
storeResult.codeId!,
initMsg,
label,
network,
signer,
gasPrice,
admin
);
if (!instantiateResult.success) {
throw new Error('Instantiate 失败: ' + instantiateResult.error);
}
console.log(chalk.green(' ✓ 合约地址: ' + instantiateResult.contractAddress));
deployResult.contracts[contractName] = {
codeId: storeResult.codeId!,
address: instantiateResult.contractAddress!,
txHash: instantiateResult.txHash || storeResult.txHash || '',
label,
admin: admin || '',
wasmHash,
instantiateMsg: initMsg,
};
deployResult.gasUsed = instantiateResult.gasUsed || storeResult.gasUsed || '0';
// 保存到 deployment 记录文件
const deployDir = path.resolve('deployments');
await fs.ensureDir(deployDir);
const deployFile = path.join(deployDir, networkName + '.json');
let existing: Record<string, any> = {};
if (await fs.pathExists(deployFile)) {
existing = await fs.readJson(deployFile);
}
existing[contractName] = deployResult.contracts[contractName];
existing._meta = {
network: networkName,
chainId: network.chainId,
lastUpdated: deployResult.timestamp,
};
await fs.writeJson(deployFile, existing, { spaces: 2 });
return deployResult;
}
async function computeWasmHash(wasmPath: string): Promise<string> {
const crypto = await import('crypto');
const content = await fs.readFile(wasmPath);
return crypto.createHash('sha256').update(content).digest('hex');
}
export async function deployAllContracts(options: DeployOptions): Promise<DeployResult[]> {
const config = await loadConfig();
const wasmDir = path.resolve(options.outDir || './artifacts');
const contractsDir = path.resolve(config.contractsPath);
const packages = await findContractPackages(contractsDir);
const filtered = options.contract
? packages.filter((p) => p.name === options.contract)
: packages;
const results: DeployResult[] = [];
for (const pkg of filtered) {
const wasmFile = path.join(wasmDir, pkg.name + '.wasm');
if (!await fs.pathExists(wasmFile)) {
console.log(chalk.yellow('未找到 ' + pkg.name + '.wasm,请先执行 compile'));
continue;
}
const initMsg = await loadInitMsg(pkg.name);
const result = await deployContract(wasmFile, pkg.name, initMsg, options, config);
results.push(result);
}
return results;
}
async function loadInitMsg(contractName: string): Promise<Record<string, unknown>> {
const initMsgPath = path.resolve('init-msgs', contractName + '.json');
if (await fs.pathExists(initMsgPath)) {
return fs.readJson(initMsgPath);
}
return {};
}
4.11 Guard 审批门引擎 (src/core/guard.ts)
import inquirer from 'inquirer';
import chalk from 'chalk';
import fs from 'fs-extra';
import path from 'path';
import { ApprovalGate, ApprovalEvidence } from '../types/index.js';
const APPROVAL_GATES: ApprovalGate[] = [
{
id: 'scope_lock',
stage: 'before_codegen',
requiredHumanInputs: ['产品目标', '业务规则', '验收标准'],
},
{
id: 'secret_injection',
stage: 'before_real_write_or_deploy',
requiredHumanInputs: ['签名账户', 'API token', '环境变量'],
},
{
id: 'production_release',
stage: 'before_launch',
requiredHumanInputs: ['最终审批', '回滚确认'],
},
{
id: 'governance_or_treasury',
stage: 'high_risk_write',
requiredHumanInputs: ['DAO通过', 'timelock结束'],
},
];
export interface GuardOptions {
skipGates?: string;
dryRun?: boolean;
}
export class GuardEngine {
private evidenceDir: string;
private evidence: ApprovalEvidence[] = [];
constructor(tag?: string) {
const dir = tag
? path.resolve('.approval-evidence', tag)
: path.resolve('.approval-evidence', 'deploy-' + Date.now());
this.evidenceDir = dir;
}
async executeAllGates(options: GuardOptions = {}): Promise<boolean> {
console.log(chalk.cyan('\n🛡️ Guard 审批门引擎'));
console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
console.log(chalk.yellow('⚠️ 生产环境部署 — 审批门已启用\n'));
const skipIds = options.skipGates
? options.skipGates.split(',').map((s) => s.trim())
: [];
let allPassed = true;
for (let i = 0; i < APPROVAL_GATES.length; i++) {
const gate = APPROVAL_GATES[i];
if (skipIds.includes(gate.id)) {
console.log(chalk.gray(' 跳过审批门 ' + (i + 1) + '/' + APPROVAL_GATES.length + ': ' + gate.id));
continue;
}
const passed = await this.executeGate(gate, i + 1);
if (!passed) {
allPassed = false;
console.log(chalk.red('\n 审批门 ' + gate.id + ' 未通过,流程终止'));
break;
}
}
if (allPassed) {
console.log(chalk.green('\n✅ 全部审批门已通过'));
await this.saveEvidence();
}
return allPassed;
}
private async executeGate(gate: ApprovalGate, index: number): Promise<boolean> {
console.log(chalk.cyan('🔒 审批门 ' + index + '/' + APPROVAL_GATES.length + ': ' + gate.id + ' (' + gate.stage + ')'));
console.log(chalk.gray(' 需要审批: ' + gate.requiredHumanInputs.join(', ')));
const responses: Record<string, string> = {};
for (const input of gate.requiredHumanInputs) {
const { value } = await inquirer.prompt([
{
type: 'input',
name: 'value',
message: '请输入 ' + chalk.yellow(input) + ':',
validate: (v: string) => v.length > 0 || input + ' 不能为空',
},
]);
responses[input] = value;
}
this.evidence.push({
gate,
responses,
timestamp: new Date().toISOString(),
approvedBy: process.env.USER || 'unknown',
});
console.log(chalk.green(' ✓ 已记录\n'));
return true;
}
private async saveEvidence(): Promise<void> {
await fs.ensureDir(this.evidenceDir);
for (const item of this.evidence) {
const content = [
'# 审批门证据: ' + item.gate.id,
'- 阶段: ' + item.gate.stage,
'- 审批时间: ' + item.timestamp,
'- 审批人: ' + item.approvedBy,
'',
'## 审批输入',
...Object.entries(item.responses).map(
([key, value]) => '- **' + key + '**: ' + value
),
].join('\n');
const filePath = path.join(this.evidenceDir, item.gate.id + '.md');
await fs.writeFile(filePath, content);
}
console.log(chalk.gray('\n📝 证据存档: ' + this.evidenceDir + '/'));
}
async createEvidenceSummary(deploymentResult: Record<string, unknown>): Promise<void> {
const summary = {
evidence: this.evidence,
deployment: deploymentResult,
timestamp: new Date().toISOString(),
};
await fs.writeJson(
path.join(this.evidenceDir, 'deployment_receipt.json'),
summary,
{ spaces: 2 }
);
}
static assertGuardFlag(guard: boolean | undefined): void {
if (!guard) {
console.error(chalk.red([
'',
'╔══════════════════════════════════════════════════════════════╗',
'║ 🛡️ GUARD MODE REQUIRED ║',
'║ ║',
'║ 生产环境部署必须使用 --guard 标志以激活审批门检查。 ║',
'║ ║',
'║ 正确的用法: ║',
'║ $ msg-devkit publish --guard --network mainnet ║',
'║ $ msg-devkit deploy --guard --network mainnet ║',
'║ ║',
'║ requires_human_approval: true ║',
'╚══════════════════════════════════════════════════════════════╝',
'',
].join('\n')));
process.exit(1);
}
}
}
4.12 脚手架生成器 (src/core/scaffold.ts)
import path from 'path';
import fs from 'fs-extra';
import chalk from 'chalk';
import ora from 'ora';
import { writeDefaultConfig } from '../config/config-loader.js';
import { generateCiWorkflow } from './ci-generator.js';
import { ScaffoldOptions } from '../types/index.js';
export async function scaffoldProject(options: ScaffoldOptions): Promise<void> {
const projectDir = path.resolve(options.projectName);
const spinner = ora('创建项目 ' + options.projectName + '...').start();
try {
await fs.ensureDir(projectDir);
switch (options.template) {
case 'contract':
await scaffoldContractTemplate(projectDir, options);
break;
case 'dapp':
await scaffoldDappTemplate(projectDir, options);
break;
case 'fullstack':
await scaffoldContractTemplate(projectDir, options);
await scaffoldDappTemplate(path.join(projectDir, 'frontend'), options);
break;
}
await writeDefaultConfig(projectDir);
await generatePackageJson(projectDir, options);
await generateGitignore(projectDir);
if (options.ci) {
await generateCiWorkflow(projectDir, options.ci);
}
spinner.stop();
console.log(chalk.green('\n✅ 项目 ' + options.projectName + ' 创建成功!\n'));
printNextSteps(projectDir, options);
} catch (err) {
spinner.stop();
throw err;
}
}
async function scaffoldContractTemplate(dir: string, options: ScaffoldOptions): Promise<void> {
const contractName = options.contractName || 'my_contract';
const contractDir = path.join(dir, 'contracts', 'cosmwasm', 'all', contractName);
const srcDir = path.join(contractDir, 'src');
const testsDir = path.join(contractDir, 'tests');
await fs.ensureDir(srcDir);
await fs.ensureDir(testsDir);
await fs.ensureDir(path.join(dir, 'scripts'));
await fs.ensureDir(path.join(dir, 'test', 'integration'));
await fs.ensureDir(path.join(dir, 'init-msgs'));
await fs.ensureDir(path.join(dir, 'artifacts'));
await fs.writeFile(path.join(contractDir, 'Cargo.toml'), [
'[package]',
'name = "' + contractName + '"',
'version = "1.0.0"',
'edition = "2021"',
'description = "MSG Chain CosmWasm Contract"',
'',
'[lib]',
'crate-type = ["cdylib", "rlib"]',
'',
'[features]',
'default = []',
'library = []',
'',
'[dependencies]',
'cosmwasm-std = { version = "2.1", features = ["staking"] }',
'cosmwasm-schema = "2.1"',
'cw-storage-plus = "2.0"',
'cw-utils = "2.0"',
'cw2 = "2.0"',
'schemars = "0.8"',
'serde = { version = "1.0", features = ["derive"] }',
'serde-json-wasm = "1.0"',
'thiserror = "2.0"',
'',
'[dev-dependencies]',
'cw-multi-test = "2.0"',
'anyhow = "1.0"',
'',
'[profile.release]',
'opt-level = 3',
'debug = false',
'rpath = false',
'lto = true',
'overflow-checks = true',
'codegen-units = 1',
].join('\n'));
await fs.writeFile(path.join(srcDir, 'contract.rs'), [
'use cosmwasm_std::{',
' entry_point, to_json_binary, Binary, Deps, DepsMut, Env,',
' MessageInfo, Response, StdResult,',
'};',
'use crate::msg::{InstantiateMsg, ExecuteMsg, QueryMsg};',
'use crate::state::{Config, CONFIG};',
'use crate::error::ContractError;',
'',
'#[entry_point]',
'pub fn instantiate(',
' deps: DepsMut,',
' _env: Env,',
' info: MessageInfo,',
' msg: InstantiateMsg,',
') -> StdResult<Response> {',
' let config = Config {',
' owner: info.sender,',
' key: msg.key,',
' };',
' CONFIG.save(deps.storage, &config)?;',
' Ok(Response::new()',
' .add_attribute("method", "instantiate")',
' .add_attribute("owner", info.sender))',
'}',
'',
'#[entry_point]',
'pub fn execute(',
' deps: DepsMut,',
' env: Env,',
' info: MessageInfo,',
' msg: ExecuteMsg,',
') -> Result<Response, ContractError> {',
' match msg {',
' ExecuteMsg::UpdateConfig { key } => execute_update_config(deps, env, info, key),',
' }',
'}',
'',
'fn execute_update_config(',
' deps: DepsMut,',
' _env: Env,',
' info: MessageInfo,',
' key: String,',
') -> Result<Response, ContractError> {',
' let mut config = CONFIG.load(deps.storage)?;',
' if info.sender != config.owner {',
' return Err(ContractError::Unauthorized {});',
' }',
' config.key = key;',
' CONFIG.save(deps.storage, &config)?;',
' Ok(Response::new().add_attribute("method", "update_config"))',
'}',
'',
'#[entry_point]',
'pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {',
' match msg {',
' QueryMsg::GetConfig {} => to_json_binary(&query_config(deps)?),',
' }',
'}',
'',
'fn query_config(deps: Deps) -> StdResult<Config> {',
' CONFIG.load(deps.storage)',
'}',
].join('\n'));
await fs.writeFile(path.join(srcDir, 'msg.rs'), [
'use cosmwasm_schema::cw_serde;',
'',
'#[cw_serde]',
'pub struct InstantiateMsg {',
' pub key: String,',
'}',
'',
'#[cw_serde]',
'pub enum ExecuteMsg {',
' UpdateConfig { key: String },',
'}',
'',
'#[cw_serde]',
'pub enum QueryMsg {',
' GetConfig {},',
'}',
].join('\n'));
await fs.writeFile(path.join(srcDir, 'state.rs'), [
'use cosmwasm_schema::cw_serde;',
'use cosmwasm_std::Addr;',
'use cw_storage_plus::Item;',
'',
'#[cw_serde]',
'pub struct Config {',
' pub owner: Addr,',
' pub key: String,',
'}',
'',
'pub const CONFIG: Item<Config> = Item::new("config");',
].join('\n'));
await fs.writeFile(path.join(srcDir, 'error.rs'), [
'use cosmwasm_std::StdError;',
'use thiserror::Error;',
'',
'#[derive(Error, Debug, PartialEq)]',
'pub enum ContractError {',
' #[error("{0}")]',
' Std(#[from] StdError),',
'',
' #[error("Unauthorized")]',
' Unauthorized {},',
'',
' #[error("Invalid input: {reason}")]',
' InvalidInput { reason: String },',
'}',
].join('\n'));
await fs.writeFile(path.join(srcDir, 'lib.rs'), [
'pub mod contract;',
'mod error;',
'pub mod msg;',
'pub mod state;',
'',
'pub use crate::error::ContractError;',
].join('\n'));
await fs.writeFile(path.join(srcDir, 'schema.rs'), [
'use cosmwasm_schema::write_api;',
'use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};',
'',
'fn main() {',
' write_api! {',
' instantiate: InstantiateMsg,',
' execute: ExecuteMsg,',
' query: QueryMsg,',
' }',
'}',
].join('\n'));
await fs.writeFile(path.join(testsDir, 'integration.rs'), [
'use cosmwasm_std::Addr;',
'use cw_multi_test::{App, ContractWrapper, Executor};',
'use ' + contractName + '::msg::{ExecuteMsg, InstantiateMsg, QueryMsg};',
'use ' + contractName + '::state::Config;',
'',
'#[test]',
'fn test_full_flow() {',
' let mut app = App::default();',
' let code_id = app.store_code(Box::new(ContractWrapper::new(',
' ' + contractName + '::contract::execute,',
' ' + contractName + '::contract::instantiate,',
' ' + contractName + '::contract::query,',
' )));',
'',
' let owner = Addr::unchecked("owner");',
' let contract_addr = app',
' .instantiate_contract(',
' code_id,',
' owner.clone(),',
' &InstantiateMsg {',
' key: "initial".to_string(),',
' },',
' &[],',
' "test-contract",',
' None,',
' )',
' .unwrap();',
'',
' let config: Config = app',
' .wrap()',
' .query_wasm_smart(contract_addr.clone(), &QueryMsg::GetConfig {})',
' .unwrap();',
' assert_eq!(config.key, "initial");',
'}',
].join('\n'));
await fs.writeJson(path.join(dir, 'init-msgs', contractName + '.json'), { key: "value" }, { spaces: 2 });
}
async function scaffoldDappTemplate(dir: string, options: ScaffoldOptions): Promise<void> {
const framework = options.framework || 'react';
await fs.ensureDir(path.join(dir, 'src', 'chain'));
await fs.ensureDir(path.join(dir, 'src', 'components'));
await fs.ensureDir(path.join(dir, 'src', 'config'));
await fs.writeFile(path.join(dir, 'src', 'chain', 'client.ts'), [
'import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";',
'import { OfflineSigner } from "@cosmjs/proto-signing";',
'',
'declare global {',
' interface Window {',
' keplr?: any;',
' getOfflineSigner?: (chainId: string) => OfflineSigner;',
' }',
'}',
'',
'const MSG_CHAIN_CONFIG = {',
' chainId: "msg-chain-1",',
' chainName: "MSG Chain",',
' rpc: process.env.NEXT_PUBLIC_RPC_URL || "http://localhost:26657",',
' rest: process.env.NEXT_PUBLIC_REST_URL || "http://localhost:1317",',
' bip44: { coinType: 118 },',
' bech32Config: {',
' bech32PrefixAccAddr: "msg",',
' bech32PrefixAccPub: "msgpub",',
' bech32PrefixValAddr: "msgvaloper",',
' bech32PrefixValPub: "msgvaloperpub",',
' bech32PrefixConsAddr: "msgvalcons",',
' bech32PrefixConsPub: "msgvalconspub",',
' },',
' currencies: [{',
' coinDenom: "MSG",',
' coinMinimalDenom: "umsg",',
' coinDecimals: 18,',
' coinGeckoId: "msg-chain",',
' }],',
' feeCurrencies: [{',
' coinDenom: "MSG",',
' coinMinimalDenom: "umsg",',
' coinDecimals: 18,',
' gasPriceStep: { low: 1000000000attoMSG, average: 1000000000attoMSG, high: 1000000000attoMSG },',
' }],',
' stakeCurrency: {',
' coinDenom: "MSG",',
' coinMinimalDenom: "umsg",',
' coinDecimals: 18,',
' },',
'};',
'',
'export async function connectKeplr() {',
' if (!window.keplr) {',
' throw new Error("请安装 Keplr 钱包: https://www.keplr.app/");',
' }',
' await window.keplr.experimentalSuggestChain(MSG_CHAIN_CONFIG);',
' await window.keplr.enable(MSG_CHAIN_CONFIG.chainId);',
' const offlineSigner = window.getOfflineSigner!(MSG_CHAIN_CONFIG.chainId);',
' const [account] = await offlineSigner.getAccounts();',
' return { offlineSigner, address: account.address };',
'}',
].join('\n'));
await fs.writeFile(path.join(dir, 'src', 'config', 'chain.ts'), [
'export const MSG_CHAIN = {',
' chainId: "msg-chain-1",',
' chainName: "MSG Chain",',
' rpcUrl: process.env.NEXT_PUBLIC_RPC_URL || "http://localhost:26657",',
' restUrl: process.env.NEXT_PUBLIC_REST_URL || "http://localhost:1317",',
' denom: "umsg",',
' decimals: 18,',
' bech32Prefix: "msg",',
' gasPrice: { low: "1000000000attoMSG", average: "1000000000attoMSG", high: "1000000000attoMSG" },',
' explorerUrl: process.env.NEXT_PUBLIC_EXPLORER_URL || "https://explorer.msgchain.org",',
' blockTime: 5000,',
' consensus: "Round-Robin + DAR",',
'} as const;',
].join('\n'));
}
async function generatePackageJson(dir: string, options: ScaffoldOptions): Promise<void> {
const pkg = {
name: options.projectName,
version: '1.0.0',
private: true,
scripts: {
compile: 'msg-devkit compile',
test: 'msg-devkit test',
'test:integration': 'msg-devkit test --integration',
deploy: 'msg-devkit deploy',
'deploy:local': 'msg-devkit deploy --network local',
'deploy:mainnet': 'msg-devkit deploy --network mainnet --guard',
node: 'msg-devkit node',
'node:start': 'msg-devkit node start',
'node:stop': 'msg-devkit node stop',
console: 'msg-devkit console',
schema: 'msg-devkit schema',
lint: 'make lint',
'make:deps': 'make deps',
'make:test': 'make test',
'make:test-quantum': 'make test-quantum',
'make:build-linux': 'make build-linux',
'make:ci-contracts': 'make ci-contracts',
'make:package': 'make package',
},
dependencies: {
'@msg-chain/devkit': '^1.0.0',
},
devDependencies: {
typescript: '^5.4.0',
'ts-node': '^10.9.0',
'@types/node': '^20.11.0',
},
};
await fs.writeJson(path.join(dir, 'package.json'), pkg, { spaces: 2 });
}
async function generateGitignore(dir: string): Promise<void> {
const gitignore = [
'node_modules/',
'dist/',
'artifacts/',
'target/',
'schemas/',
'deployments/',
'.approval-evidence/',
'*.wasm',
'.env',
'.env.*',
'!.env.example',
'*.log',
'coverage/',
'.DS_Store',
].join('\n');
await fs.writeFile(path.join(dir, '.gitignore'), gitignore);
}
function printNextSteps(dir: string, options: ScaffoldOptions): void {
console.log(chalk.cyan('\n📁 项目结构: ' + options.projectName + '/'));
console.log(chalk.gray('├── contracts/cosmwasm/all/ — 合约源码'));
console.log(chalk.gray('├── scripts/ — 部署脚本'));
console.log(chalk.gray('├── test/ — 集成测试'));
console.log(chalk.gray('├── msgchain.config.json — 链配置'));
console.log(chalk.gray('└── package.json — 项目依赖\n'));
console.log(chalk.bold('🚀 快速开始:\n'));
console.log(chalk.cyan(' cd ' + options.projectName));
console.log(chalk.cyan(' npm install'));
console.log(chalk.cyan(' msg-devkit compile'));
console.log(chalk.cyan(' msg-devkit test'));
console.log(chalk.cyan(' msg-devkit node start'));
console.log(chalk.cyan(' msg-devkit deploy --network local'));
console.log(chalk.cyan(' msg-devkit console\n'));
if (options.template === 'contract') {
console.log(chalk.bold('合约相关命令:\n'));
console.log(chalk.gray(' make deps # 安装依赖'));
console.log(chalk.gray(' make lint # 代码质量检查 (gofmt, go vet, golangci-lint)'));
console.log(chalk.gray(' make test # 单元测试'));
console.log(chalk.gray(' make test-quantum # 量子模块测试'));
console.log(chalk.gray(' make build-linux # 构建 Linux 二进制'));
console.log(chalk.gray(' make ci-contracts # 合约 CI 构建'));
console.log(chalk.gray(' make package # 打包部署包\n'));
}
}
4.13 CI/CD 生成器 (src/core/ci-generator.ts)
import path from 'path';
import fs from 'fs-extra';
export async function generateCiWorkflow(projectDir: string, provider: string): Promise<void> {
switch (provider) {
case 'github-actions':
await generateGithubActions(projectDir);
break;
default:
throw new Error('不支持的 CI 提供者: ' + provider);
}
}
async function generateGithubActions(projectDir: string): Promise<void> {
const workflowDir = path.join(projectDir, '.github', 'workflows');
await fs.ensureDir(workflowDir);
const workflow = [
'name: MSG Chain CI/CD',
'',
'on:',
' push:',
' branches: [main, develop]',
' pull_request:',
' branches: [main]',
'',
'env:',
' MSG_NETWORK: test',
' MSG_RPC_URL: http://localhost:26657',
'',
'jobs:',
' quality_gate:',
' name: 代码质量检查',
' runs-on: ubuntu-latest',
' steps:',
' - uses: actions/checkout@v4',
' - name: Setup Go',
' uses: actions/setup-go@v5',
' with:',
' go-version: "1.24"',
' - name: Setup Rust',
' uses: actions-rust-lang/setup-rust-toolchain@v1',
' with:',
' target: wasm32-unknown-unknown',
' - name: Setup Node.js',
' uses: actions/setup-node@v4',
' with:',
' node-version: "20"',
' - name: 安装依赖',
' run: make deps',
' - name: 代码质量检查',
' run: make lint',
' - name: 运行单元测试',
' run: make test',
' - name: 量子模块测试',
' run: make test-quantum',
'',
' contract_validation:',
' name: 合约编译与测试',
' runs-on: ubuntu-latest',
' steps:',
' - uses: actions/checkout@v4',
' - name: Setup Rust',
' uses: actions-rust-lang/setup-rust-toolchain@v1',
' with:',
' target: wasm32-unknown-unknown',
' - name: Setup Node.js',
' uses: actions/setup-node@v4',
' with:',
' node-version: "20"',
' - name: 安装 devkit',
' run: npm install -g @msg-chain/devkit',
' - name: 安装依赖',
' run: npm ci',
' - name: 编译合约',
' run: msg-devkit compile',
' - name: 运行合约测试',
' run: msg-devkit test',
' - name: 生成 Schema',
' run: msg-devkit schema',
' - name: 上传编译产物',
' uses: actions/upload-artifact@v4',
' with:',
' name: wasm-artifacts',
' path: artifacts/',
'',
' build:',
' name: 构建节点二进制',
' runs-on: ubuntu-latest',
' needs: [quality_gate, contract_validation]',
' steps:',
' - uses: actions/checkout@v4',
' - name: Setup Go',
' uses: actions/setup-go@v5',
' with:',
' go-version: "1.24"',
' - name: 构建 Linux 二进制',
' run: make build-linux',
' - name: 上传构建产物',
' uses: actions/upload-artifact@v4',
' with:',
' name: node-binaries',
' path: |',
' bin/genesis_node_linux',
' bin/quantum_node_linux',
'',
' package:',
' name: 打包发布',
' runs-on: ubuntu-latest',
' needs: [build]',
' if: github.ref == "refs/heads/main"',
' steps:',
' - uses: actions/checkout@v4',
' - name: 下载所有产物',
' uses: actions/download-artifact@v4',
' - name: 打包部署包',
' run: make package',
' - name: 上传部署包',
' uses: actions/upload-artifact@v4',
' with:',
' name: deploy-package',
' path: deploy-*.tar.gz',
'',
' deploy_testnet:',
' name: 部署到 Testnet',
' runs-on: ubuntu-latest',
' needs: [package]',
' if: github.ref == "refs/heads/main"',
' environment: testnet',
' steps:',
' - uses: actions/checkout@v4',
' - name: Setup Node.js',
' uses: actions/setup-node@v4',
' with:',
' node-version: "20"',
' - name: 安装 devkit',
' run: npm install -g @msg-chain/devkit',
' - name: 安装依赖',
' run: npm ci',
' - name: 部署到 Testnet',
' run: msg-devkit deploy --network testnet --guard',
'',
' deploy_mainnet:',
' name: 部署到 Mainnet',
' runs-on: ubuntu-latest',
' needs: [deploy_testnet]',
' if: github.ref == "refs/heads/main" && startsWith(github.ref, "refs/tags/v")',
' environment: mainnet',
' concurrency: production',
' steps:',
' - uses: actions/checkout@v4',
' - name: Setup Node.js',
' uses: actions/setup-node@v4',
' with:',
' node-version: "20"',
' - name: 安装 devkit',
' run: npm install -g @msg-chain/devkit',
' - name: 安装依赖',
' run: npm ci',
' - name: Guard 模式生产发布',
' run: msg-devkit publish --guard --network mainnet --tag ${{ github.ref_name }}',
].join('\n');
await fs.writeFile(path.join(workflowDir, 'ci.yml'), workflow);
}
4.14 Schema 生成器 (src/core/schema-generator.ts)
import path from 'path';
import fs from 'fs-extra';
import { execa } from 'execa';
import { findContractPackages } from './compile-engine.js';
import { loadConfig } from '../config/config-loader.js';
export interface SchemaOptions {
contract?: string;
outDir?: string;
format?: string;
}
export async function generateSchemas(options: SchemaOptions): Promise<string[]> {
const config = await loadConfig();
const contractsPath = path.resolve(config.contractsPath);
let packages = await findContractPackages(contractsPath);
if (options.contract) {
packages = packages.filter((p) => p.name === options.contract);
}
const schemasDir = path.resolve(options.outDir || './schemas');
await fs.ensureDir(schemasDir);
const generatedFiles: string[] = [];
for (const pkg of packages) {
try {
await execa('cargo', ['run', '--bin', 'schema'], {
cwd: pkg.path,
stdio: 'pipe',
});
const schemaOutputDir = path.join(pkg.path, 'schema');
if (await fs.pathExists(schemaOutputDir)) {
const contractSchemaDir = path.join(schemasDir, pkg.name);
await fs.copy(schemaOutputDir, contractSchemaDir);
const files = await fs.readdir(contractSchemaDir);
for (const f of files) {
generatedFiles.push(path.join(contractSchemaDir, f));
}
}
} catch {
await generateSchemaFromSource(pkg, schemasDir);
}
}
return generatedFiles;
}
async function generateSchemaFromSource(
pkg: { name: string; path: string },
schemasDir: string
): Promise<void> {
const msgPath = path.join(pkg.path, 'src', 'msg.rs');
if (!await fs.pathExists(msgPath)) return;
const contractSchemaDir = path.join(schemasDir, pkg.name);
await fs.ensureDir(contractSchemaDir);
const msgContent = await fs.readFile(msgPath, 'utf-8');
const schemas: Record<string, any> = {};
if (msgContent.includes('struct InstantiateMsg')) {
schemas.instantiate_msg = {
$schema: 'http://json-schema.org/draft-07/schema#',
title: 'InstantiateMsg',
type: 'object',
properties: { key: { type: 'string' } },
required: ['key'],
};
}
if (msgContent.includes('enum ExecuteMsg')) {
schemas.execute_msg = {
$schema: 'http://json-schema.org/draft-07/schema#',
title: 'ExecuteMsg',
oneOf: [{
type: 'object',
properties: {
update_config: {
type: 'object',
properties: { key: { type: 'string' } },
required: ['key'],
},
},
required: ['update_config'],
}],
};
}
if (msgContent.includes('enum QueryMsg')) {
schemas.query_msg = {
$schema: 'http://json-schema.org/draft-07/schema#',
title: 'QueryMsg',
oneOf: [{
type: 'object',
properties: {
get_config: { type: 'object', properties: {} },
},
required: ['get_config'],
}],
};
}
for (const [name, schema] of Object.entries(schemas)) {
await fs.writeJson(path.join(contractSchemaDir, name + '.json'), schema, { spaces: 2 });
}
}
4.15 节点管理器 (src/core/node-manager.ts)
import { execa } from 'execa';
import chalk from 'chalk';
import fs from 'fs-extra';
const NODE_DATA_DIR = '/tmp/msg-chain/data';
export interface NodeOptions {
clean?: boolean;
validators?: string;
ports?: string;
detach?: string;
}
export async function startNode(options: NodeOptions): Promise<void> {
if (options.clean) {
console.log(chalk.gray('清理旧数据...'));
await fs.remove(NODE_DATA_DIR);
await fs.ensureDir(NODE_DATA_DIR);
console.log(chalk.green(' ✓ 已清理'));
}
const validatorCount = parseInt(options.validators || '1', 10);
const detach = options.detach !== 'false';
console.log(chalk.cyan('启动 Docker Compose 服务...'));
const composeConfig = generateDockerCompose(validatorCount);
try {
const args = ['-f', '-'];
if (detach) {
args.push('up', '-d');
} else {
args.push('up');
}
await execa('docker-compose', args, {
input: composeConfig,
stdio: detach ? 'pipe' : 'inherit',
});
if (detach) {
console.log(chalk.green(' ✓ 本地节点已启动'));
console.log(chalk.gray('等待节点同步...'));
await waitForBlock();
printNodeInfo(validatorCount);
}
} catch (err) {
console.error(chalk.red(' 启动失败: ' + (err as Error).message));
throw err;
}
}
export async function stopNode(): Promise<void> {
console.log(chalk.cyan('停止本地节点...'));
try {
await execa('docker-compose', ['down', '-v'], { stdio: 'pipe' });
console.log(chalk.green(' ✓ 已停止'));
} catch (err) {
console.error(chalk.red(' 停止失败: ' + (err as Error).message));
}
}
export async function nodeStatus(): Promise<void> {
try {
const { stdout } = await execa('curl', ['-s', 'http://localhost:26657/status']);
const status = JSON.parse(stdout);
const syncInfo = status.result.sync_info;
console.log(chalk.cyan('\n节点: 运行中'));
console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
console.log(' 区块高度: ' + chalk.bold(syncInfo.latest_block_height));
console.log(' 最新区块: ' + syncInfo.latest_block_time);
console.log(' 节点状态: ' + (syncInfo.catching_up ? 'CatchUp' : 'Synced'));
console.log(' 同步中: ' + syncInfo.catching_up);
} catch {
console.log(chalk.red('\n节点: 未运行'));
console.log(chalk.gray(' 使用 msg-devkit node start 启动'));
}
}
export async function resetNode(): Promise<void> {
console.log(chalk.yellow('即将重置节点数据...'));
await stopNode();
await fs.remove(NODE_DATA_DIR);
await fs.ensureDir(NODE_DATA_DIR);
console.log(chalk.green('✓ 节点数据已重置'));
}
async function waitForBlock(timeout = 30000): Promise<void> {
const start = Date.now();
while (Date.now() - start < timeout) {
try {
const { stdout } = await execa('curl', ['-s', 'http://localhost:26657/abci_info']);
const info = JSON.parse(stdout);
if (info.result?.response?.last_block_height > 0) {
console.log(chalk.green(' ✓ 节点正在产生区块'));
return;
}
} catch {
// 等待
}
await new Promise((r) => setTimeout(r, 1000));
}
console.log(chalk.yellow(' 等待超时,节点可能尚未就绪'));
}
function printNodeInfo(validatorCount: number): void {
console.log(chalk.cyan('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
console.log(chalk.green('✅ 本地节点运行中\n'));
console.log(' RPC: http://localhost:26657');
console.log(' REST: http://localhost:1317');
console.log(' gRPC: localhost:9090');
console.log(' Faucet: http://localhost:8000');
console.log('\n 验证者数量: ' + validatorCount);
console.log(' Block Time: 5s');
console.log(' 共识机制: Round-Robin + DAR\n');
}
function generateDockerCompose(validatorCount: number): string {
return [
'version: "3.8"',
'services:',
' msg-chain-node:',
' image: msgchain/node:latest',
' container_name: msg-chain-node-1',
' ports:',
' - "26657:26657"',
' - "1317:1317"',
' - "9090:9090"',
' volumes:',
' - ' + NODE_DATA_DIR + ':/root/.msgd',
' environment:',
' - CHAIN_ID=msg-chain-1',
' - VALIDATORS=' + validatorCount,
' - BECH32_PREFIX=msg',
' - DENOM=umsg',
' - COIN_TYPE=118',
' - BLOCK_TIME=5s',
' command: msgd start --rpc.laddr tcp://0.0.0.0:26657',
' healthcheck:',
' test: ["CMD", "curl", "-f", "http://localhost:26657/status"]',
' interval: 10s',
' timeout: 5s',
' retries: 5',
' msg-chain-db:',
' image: postgres:16-alpine',
' container_name: msg-chain-db-1',
' environment:',
' POSTGRES_USER: msgchain',
' POSTGRES_PASSWORD: msgchain',
' POSTGRES_DB: msgchain',
' ports:',
' - "5432:5432"',
' msg-chain-explorer:',
' image: pingpub/explorer:latest',
' container_name: msg-chain-explorer-1',
' ports:',
' - "3000:3000"',
' depends_on:',
' - msg-chain-node',
' environment:',
' CHAIN_RPC: http://msg-chain-node:26657',
' CHAIN_REST: http://msg-chain-node:1317',
].join('\n');
}
4.16 网络管理器 (src/config/networks.ts)
import fs from 'fs-extra';
import path from 'path';
import chalk from 'chalk';
import { NetworkConfig } from '../types/index.js';
import { DEFAULT_NETWORKS } from './defaults.js';
const NETWORKS_FILE = 'networks.json';
export async function listNetworks(cwd?: string): Promise<void> {
const dir = cwd || process.cwd();
let networks: Record<string, NetworkConfig> = { ...DEFAULT_NETWORKS };
const networksPath = path.join(dir, NETWORKS_FILE);
if (await fs.pathExists(networksPath)) {
const custom = await fs.readJson(networksPath);
networks = { ...networks, ...custom };
}
console.log(chalk.cyan('\n网络配置:'));
console.log(chalk.gray('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n'));
for (const [name, net] of Object.entries(networks)) {
const isDefault = name === 'local';
console.log(chalk.bold(' ' + name) + (isDefault ? ' (默认)' : ''));
console.log(' Chain: ' + net.chainId);
console.log(' RPC: ' + net.rpcUrl);
console.log(' REST: ' + net.restUrl);
console.log(' Gas: ' + net.gasPrice + '\n');
}
}
export async function addNetwork(
name: string,
config: Partial<NetworkConfig>,
cwd?: string
): Promise<void> {
const dir = cwd || process.cwd();
const networksPath = path.join(dir, NETWORKS_FILE);
let networks: Record<string, any> = {};
if (await fs.pathExists(networksPath)) {
networks = await fs.readJson(networksPath);
}
networks[name] = {
name,
chainId: config.chainId || name + '-1',
rpcUrl: config.rpcUrl || 'http://localhost:26657',
restUrl: config.restUrl || 'http://localhost:1317',
explorerUrl: config.explorerUrl || '',
gasPrice: '1000000000attoMSG',
gasLimit: 3000000,
denom: 'umsg',
bech32Config: {
bech32PrefixAccAddr: 'msg',
bech32PrefixAccPub: 'msgpub',
bech32PrefixValAddr: 'msgvaloper',
bech32PrefixValPub: 'msgvaloperpub',
bech32PrefixConsAddr: 'msgvalcons',
bech32PrefixConsPub: 'msgvalconspub',
},
};
await fs.writeJson(networksPath, networks, { spaces: 2 });
console.log(chalk.green('网络 ' + name + ' 已添加'));
}
export async function useNetwork(name: string, cwd?: string): Promise<void> {
const dir = cwd || process.cwd();
const configPath = path.join(dir, 'msgchain.config.json');
if (!await fs.pathExists(configPath)) {
console.log(chalk.red('未找到 msgchain.config.json'));
return;
}
const config = await fs.readJson(configPath);
config.defaultNetwork = name;
await fs.writeJson(configPath, config, { spaces: 2 });
console.log(chalk.green('默认网络已切换为: ' + name));
}
4.17 命令处理层 (src/commands/)
每个命令文件小而集中,主要职责是解析选项、调用核心引擎、格式化输出。下面以几个核心命令为例。
src/commands/init.ts:
import { scaffoldProject } from '../core/scaffold.js';
import { ScaffoldOptions } from '../types/index.js';
export async function initCommand(
projectName: string | undefined,
options: any
): Promise<void> {
const name = projectName || 'msg-chain-project';
const scaffoldOpts: ScaffoldOptions = {
projectName: name,
template: options.template || 'contract',
contractName: options.contractName,
framework: options.framework,
language: options.language,
ci: options.ci,
yes: options.yes,
};
await scaffoldProject(scaffoldOpts);
}
src/commands/compile.ts:
import { compileAllContracts, CompileOptions } from '../core/compile-engine.js';
export async function compileCommand(options: any): Promise<void> {
const opts: CompileOptions = {
contract: options.contract,
optimize: options.optimize !== 'false',
optimizeLevel: options.optimizeLevel || 'z',
outDir: options.outDir || './artifacts',
force: options.force || false,
};
await compileAllContracts(opts);
}
src/commands/test.ts:
import { runAllTests, TestOptions } from '../core/test-runner.js';
export async function testCommand(options: any): Promise<void> {
const opts: TestOptions = {
contract: options.contract,
testName: options.testName,
integration: options.integration || false,
coverage: options.coverage || false,
verbose: options.verbose || false,
noRust: options.noRust === false,
};
await runAllTests(opts);
}
src/commands/deploy.ts:
import chalk from 'chalk';
import { GuardEngine } from '../core/guard.js';
import { deployContract, DeployOptions } from '../core/deploy-engine.js';
import { loadConfig } from '../config/config-loader.js';
export async function deployCommand(options: any): Promise<void> {
const config = await loadConfig();
// 生产环境检查 Guard 模式
if (options.network === 'mainnet' || options.network === 'testnet') {
GuardEngine.assertGuardFlag(options.guard);
const guard = new GuardEngine();
const gatesPassed = await guard.executeAllGates({
skipGates: options.skipGates,
});
if (!gatesPassed) {
process.exit(1);
}
}
const deployOpts: DeployOptions = {
network: options.network || 'local',
contract: options.contract,
signer: options.signer,
gas: options.gas || '1000000000attoMSG',
gasLimit: options.gasLimit || '3000000',
label: options.label,
admin: options.admin,
noAdmin: options.noAdmin || false,
dryRun: options.dryRun || false,
guard: options.guard || false,
};
console.log(chalk.cyan('\n部署合约到 ' + deployOpts.network + '\n'));
// 具体部署逻辑调用 deploy-engine
const results = await deployContract(
'artifacts/' + (deployOpts.contract || 'contract') + '.wasm',
deployOpts.contract || 'unknown',
{},
deployOpts,
config
);
console.log(chalk.green('\n✓ 部署成功\n'));
console.log(JSON.stringify(results, null, 2));
}
src/commands/publish.ts:
import chalk from 'chalk';
import { GuardEngine } from '../core/guard.js';
import { loadConfig } from '../config/config-loader.js';
import { compileAllContracts } from '../core/compile-engine.js';
import { deployAllContracts, DeployOptions } from '../core/deploy-engine.js';
export async function publishCommand(options: any): Promise<void> {
// Guard 模式强制检查
GuardEngine.assertGuardFlag(options.guard);
const config = await loadConfig();
const tag = options.tag || 'untagged';
const guard = new GuardEngine(tag);
const gatesPassed = await guard.executeAllGates({
skipGates: options.skipGates,
});
if (!gatesPassed) {
console.log(chalk.red('审批门未通过,发布终止'));
process.exit(1);
}
console.log(chalk.cyan('\n✅ 全部审批门已通过 — 开始发布流程\n'));
// 步骤 1: 编译
console.log(chalk.cyan('步骤 1/4: 编译合约\n'));
await compileAllContracts({ optimize: true, optimizeLevel: 'z' });
// 步骤 2/3: 部署
const deployOpts: DeployOptions = {
network: options.network || 'mainnet',
contract: options.contract,
signer: options.signer || config.deploy.signer,
guard: true,
};
console.log(chalk.cyan('\n步骤 2-3/4: Store Code + Instantiate\n'));
const results = await deployAllContracts(deployOpts);
// 步骤 4: 验证与证据收集
console.log(chalk.cyan('\n步骤 4/4: 验证与证据收集\n'));
await guard.createEvidenceSummary({
tag,
results,
network: options.network,
});
console.log(chalk.green('\n✅ 发布完成 (' + tag + ')\n'));
console.log(chalk.gray('证据存档: .approval-evidence/' + tag + '/'));
}
4.18 插件系统
@msg-chain/devkit 支持插件扩展机制,允许社区开发者添加自定义命令和任务。
import { Command } from 'commander';
export interface DevkitPlugin {
name: string;
version: string;
setup: (program: Command, context: PluginContext) => void;
}
export interface PluginContext {
config: any;
log: (msg: string) => void;
}
const plugins: DevkitPlugin[] = [];
export function registerPlugin(plugin: DevkitPlugin): void {
plugins.push(plugin);
console.log('插件已注册: ' + plugin.name + '@' + plugin.version);
}
export function loadPlugins(program: Command): void {
for (const plugin of plugins) {
plugin.setup(program, {
config: {},
log: (msg) => console.log('[plugin] ' + msg),
});
}
}
5. 项目模板
5.1 合约模板结构
msg-devkit init my-ai-agent --template contract 生成以下完整文件:
my-ai-agent/
├── contracts/
│ └── cosmwasm/
│ └── all/
│ └── my_ai_agent/
│ ├── Cargo.toml # Rust 包配置
│ ├── src/
│ │ ├── contract.rs # 合约入口点
│ │ ├── msg.rs # 消息类型
│ │ ├── state.rs # 状态存储
│ │ ├── error.rs # 错误类型
│ │ ├── lib.rs # 模块导出
│ │ └── schema.rs # Schema 生成
│ └── tests/
│ └── integration.rs # 集成测试
├── scripts/
│ └── deploy.ts # TypeScript 部署脚本
├── test/
│ └── integration/
│ └── my_ai_agent.test.ts # 端到端测试
├── init-msgs/
│ └── my_ai_agent.json # 初始化消息
├── msgchain.config.json # 链配置
├── networks.json # 网络配置
├── package.json # Node 依赖
└── .gitignore
5.2 dApp 模板结构
msg-devkit init my-dapp --template dapp --framework react 生成:
my-dapp/
├── src/
│ ├── chain/
│ │ └── client.ts # CosmJS + Keplr 封装
│ ├── components/
│ │ └── WalletConnect.tsx # 钱包连接组件
│ └── config/
│ └── chain.ts # MSG Chain 配置常量
├── package.json
└── tsconfig.json
5.3 Keplr 钱包配置
在 dApp 模板中自动包含的 Keplr 配置参数:
// MSG Chain Keplr 配置
const MSG_CHAIN_KEPLR = {
chainId: "msg-chain-1",
chainName: "MSG Chain",
rpc: "http://localhost:26657",
rest: "http://localhost:1317",
bip44: { coinType: 118 },
bech32Config: {
bech32PrefixAccAddr: "msg",
bech32PrefixAccPub: "msgpub",
bech32PrefixValAddr: "msgvaloper",
bech32PrefixValPub: "msgvaloperpub",
bech32PrefixConsAddr: "msgvalcons",
bech32PrefixConsPub: "msgvalconspub",
},
currencies: [{
coinDenom: "MSG",
coinMinimalDenom: "umsg",
coinDecimals: 18,
}],
feeCurrencies: [{
coinDenom: "MSG",
coinMinimalDenom: "umsg",
coinDecimals: 18,
gasPriceStep: { low: 1000000000attoMSG, average: 1000000000attoMSG, high: 1000000000attoMSG },
}],
stakeCurrency: {
coinDenom: "MSG",
coinMinimalDenom: "umsg",
coinDecimals: 18,
},
};
6. 网络管理
6.1 预设网络
| 网络 | Chain ID | RPC | 用途 | Gas |
|---|---|---|---|---|
local |
msg-chain-1 | http://localhost:26657 | 本地开发 | 1000000000attoMSG |
devnet |
devnet-1 | https://rpc.devnet.msgchain.org | 开发网络(预留) | 1000000000attoMSG |
testnet |
testnet-1 | https://rpc.testnet.msgchain.org | 测试网络(预留) | 1000000000attoMSG |
mainnet |
msg-chain-1 | https://rpc.mainnet.msgchain.org | 主网 | 1000000000attoMSG |
6.2 Gas 价格预设
export const GAS_PRESETS = {
low: '1000000000attoMSG', // 低优先级
average: '1000000000attoMSG', // 标准
high: '1000000000attoMSG', // 高优先级
};
6.3 自定义网络
除预设网络外,开发者可通过 msg-devkit network add 添加自定义网络,配置存储在 networks.json 中。
7. Guard 模式
7.1 概述
Guard 模式是 @msg-chain/devkit 的生产环境安全机制。任何涉及主网或测试网的操作都必须使用 --guard 标志激活审批门检查。
无需审批的操作: compile, test, schema, init
需要 Guard 的操作: deploy --network mainnet, publish, deploy --guard
7.2 四道审批门
| # | 审批门 ID | 阶段 | 人工输入 | 强制 |
|---|---|---|---|---|
| 1 | scope_lock | before_codegen | 产品目标, 业务规则, 验收标准 | 是 |
| 2 | secret_injection | before_real_write_or_deploy | 签名账户, API token, 环境变量 | 是 |
| 3 | production_release | before_launch | 最终审批, 回滚确认 | 是 |
| 4 | governance_or_treasury | high_risk_write | DAO通过, timelock结束 | 是 |
7.3 证据收集
通行每次 Guard 部署后,系统自动在 .approval-evidence/<tag>/ 目录下生成:
.approval-evidence/v1.0.0/
├── scope_lock.md # 审批门1 人工输入记录
├── secret_injection.md # 审批门2 人工输入记录
├── production_release.md # 审批门3 人工输入记录
├── governance_or_treasury.md # 审批门4 人工输入记录
├── deployment_receipt.json # 完整发布收据
└── post_state_query.json # 部署后链上状态查询
8. CI/CD 集成
8.1 GitHub Actions 工作流
msg-devkit init --ci github-actions 生成 .github/workflows/ci.yml,包含以下 job:
| Job | 对应命令 | 触发条件 |
|---|---|---|
| quality_gate | make deps, make lint, make test, make test-quantum |
push/PR |
| contract_validation | msg-devkit compile, msg-devkit test, msg-devkit schema |
push/PR |
| build | make build-linux |
quality_gate + contract_validation 通过 |
| package | make package |
main 分支 |
| deploy_testnet | msg-devkit deploy --network testnet --guard |
main 分支 |
| deploy_mainnet | msg-devkit publish --guard --network mainnet |
tag v* 推送 |
8.2 Makefile 命令映射
make deps # 安装依赖
make lint # 代码质量检查 (gofmt, go vet, golangci-lint)
make test # 运行测试
make test-quantum # 量子模块测试
make build-linux # 构建 Linux 二进制 (genesis_node + quantum_node)
make ci-contracts # 合约 CI: 构建所有合约 wasm + cargo test
make package # 打包部署包 (deploy-<version>.tar.gz)
9. 完整的端到端示例
以下演示从零开始创建、编译、测试、部署和交互一个 MSG Chain 合约的完整流程。
9.1 环境准备
# 确保环境满足前提条件
node --version # >= 18.0.0
npm --version # 最新
rustc --version # >= 1.75.0
go version # >= 1.24.6
docker --version # 最新
docker-compose --version
# 安装 wasm 目标
rustup target add wasm32-unknown-unknown
# 安装 binaryen
sudo apt install binaryen # Linux
# brew install binaryen # macOS
9.2 创建项目
# 方式 1: npx(推荐,无需全局安装)
npx @msg-chain/devkit init my-ai-agent --template contract
# 方式 2: 全局安装后
npm install -g @msg-chain/devkit
msg-devkit init my-ai-agent
# 进入项目目录
cd my-ai-agent
npm install
9.3 检查项目结构
ls -la
# 输出:
# contracts/
# scripts/
# test/
# msgchain.config.json
# package.json
# tsconfig.json
# .gitignore
ls contracts/cosmwasm/all/my_ai_agent/src/
# contract.rs error.rs lib.rs msg.rs schema.rs state.rs
9.4 编译合约
npx @msg-chain/devkit compile
# 预期输出:
# 发现 1 个合约包
# 编译 my_ai_agent...
# ✓ my_ai_agent — 148.2 KB (优化后) [24.5s]
# ✓ 1/1 合约编译成功
# 检查编译产物
ls -la artifacts/
# my_ai_agent.wasm
# checksums.txt
9.5 运行测试
npx @msg-chain/devkit test
# 预期输出:
# 发现 1 个合约包需要测试
# 测试 my_ai_agent...
# ✓ my_ai_agent — 8 passed, 0 failed [0.59s]
# ✓ 总计: 8 passed, 0 failed
9.6 生成 Schema
npx @msg-chain/devkit schema
# 预期输出:
# my_ai_agent
# ✓ InstantiateMsg → schemas/my_ai_agent/instantiate_msg.json
# ✓ ExecuteMsg → schemas/my_ai_agent/execute_msg.json
# ✓ QueryMsg → schemas/my_ai_agent/query_msg.json
# ✓ 3 schema files generated in schemas/
9.7 启动本地节点
npx @msg-chain/devkit node start --clean
# 预期输出:
# 清理旧数据...
# 启动 Docker Compose 服务...
# 等待节点同步...
# ✅ 本地节点运行中
# RPC: http://localhost:26657
# REST: http://localhost:1317
# 验证节点状态
npx @msg-chain/devkit node status
# 节点: 运行中
# 区块高度: 42
9.8 部署合约
# 写入初始化消息
echo '{"key":"my_ai_agent_v1"}' > init-msgs/my_ai_agent.json
# 部署到本地网络
npx @msg-chain/devkit deploy --network local --contract my_ai_agent
# 预期输出:
# 目标网络: local (msg-chain-1)
# 签名账户: validator
# 步骤 1/2: Store Code
# ✓ Code ID: 1 (tx: 9A2B3C...DEF)
# 步骤 2/2: Instantiate
# ✓ 合约地址: msg1x2y3z4...abc
# ✅ 部署成功!
# 查看部署记录
cat deployments/local.json
# {"my_ai_agent": {"codeId": 1, "address": "msg1x2y3z4...abc", ...}}
9.9 合约验证
npx @msg-chain/devkit verify --network local --contract my_ai_agent
# 预期输出:
# ✓ 验证通过 — my_ai_agent
# 本地 SHA256: a1b2c3d4e5f6...
# 链上 SHA256: a1b2c3d4e5f6...
# ✓ 匹配
9.10 交互控制台
npx @msg-chain/devkit console --network local
# 交互示例:
msg> await client.getChainId()
'msg-chain-1'
msg> await client.queryContractSmart("msg1x2y3z4...abc", { get_config: {} })
{ owner: "msg1...", key: "my_ai_agent_v1" }
msg> .exit
9.11 水龙头申请代币
# 如果有新地址需要测试代币
npx @msg-chain/devkit faucet --address msg1new...addr --amount 1000000
# 预期输出:
# 地址: msg1new...addr
# 数量: 1000000 umsg
# ✓ 成功! 交易哈希: FAUCET...123
9.12 Guard 模式生产发布
# 注意: 此命令需要人工交互输入审批信息
npx @msg-chain/devkit publish --guard --network mainnet --tag v1.0.0
# 交互审批流程:
# 审批门 1/4: scope_lock — 输入产品目标、业务规则、验收标准
# 审批门 2/4: secret_injection — 输入签名账户、API token、环境变量
# 审批门 3/4: production_release — 确认最终审批和回滚方案
# 审批门 4/4: governance_or_treasury — 确认 DAO 通过和 timelock 结束
# 审批通过后自动执行:
# 步骤 1/4: 编译合约 (release profile)
# 步骤 2/4: Store Code
# 步骤 3/4: Instantiate
# 步骤 4/4: 验证与证据收集
# ✅ 发布完成 (v1.0.0)
# 证据存档: .approval-evidence/v1.0.0/
9.13 网络切换
# 查看所有网络
npx @msg-chain/devkit network list
# 切换到 testnet
npx @msg-chain/devkit network use testnet
# 添加自定义网络
npx @msg-chain/devkit network add mynet \
--rpc https://rpc.mynet.msgchain.org \
--chain-id mynet-1
9.14 停止节点
npx @msg-chain/devkit node stop
# ✓ 已停止
10. 附录
A. 常用命令速查
| 命令 | 用途 | 等价操作 |
|---|---|---|
msg-devkit init <name> |
创建项目 | — |
msg-devkit compile |
编译合约 | cargo build --target wasm32-unknown-unknown --release |
msg-devkit test |
测试合约 | cargo test |
msg-devkit deploy |
部署合约 | msgd tx wasm store + msgd tx wasm instantiate |
msg-devkit verify |
验证合约 | msgd query wasm code |
msg-devkit console |
交互控制台 | REPL + CosmJS |
msg-devkit node start |
启动节点 | docker-compose up + msgd start |
msg-devkit node stop |
停止节点 | docker-compose down |
msg-devkit node status |
节点状态 | curl localhost:26657/status |
msg-devkit faucet |
水龙头 | msgd tx bank send |
msg-devkit schema |
生成 Schema | cargo run --bin schema |
msg-devkit network list |
网络列表 | 读取 networks.json |
msg-devkit publish --guard |
生产发布 | Guard 审批 + deploy |
B. MSG Chain 技术参数汇总
| 参数 | 值 |
|---|---|
| Chain ID | msg-chain-1 |
| 原生代币 | umsg |
| 精度 | 18 位小数 |
| 地址前缀 | msg |
| 验证者地址前缀 | msgvaloper |
| 共识 | Round-Robin + DAR |
| 出块时间 | 5 秒 |
| Bech32 类型 | msg/msgpub/msgvaloper/msgvaloperpub/msgvalcons/msgvalconspub |
| CoinType | 118 |
| Gas 价格范围 | 1000000000attoMSG ~ 1000000000attoMSG umsg |
| 构建产物 | bin/genesis_node_linux, bin/quantum_node_linux |
| 合约数量 | 46 个 CosmWasm 合约包 |
C. 配置示例 (msgchain.config.json)
{
"$schema": "https://raw.githubusercontent.com/msg-chain/devkit/main/schemas/config.schema.json",
"projectName": "my-ai-agent",
"defaultNetwork": "local",
"contractsPath": "contracts/cosmwasm/all",
"artifactsDir": "artifacts",
"compiler": {
"optimize": true,
"optimizeLevel": "z",
"target": "wasm32-unknown-unknown"
},
"deploy": {
"gasPrice": "1000000000attoMSG",
"gasLimit": 3000000,
"signer": "validator"
},
"networks": {
"local": {
"chainId": "msg-chain-1",
"rpcUrl": "http://localhost:26657",
"restUrl": "http://localhost:1317",
"gasPrice": "1000000000attoMSG",
"denom": "umsg"
},
"mainnet": {
"chainId": "msg-chain-1",
"rpcUrl": "https://rpc.mainnet.msgchain.org",
"gasPrice": "1000000000attoMSG",
"denom": "umsg"
}
}
}
D. 安全最佳实践
- 本地开发永远在
--network local下进行 - 生产部署必须使用
--guard标志 - 助记词仅通过环境变量传递,勿硬编码
- 部署前运行
msg-devkit verify确保 WASM 一致性 - 使用
msg-devkit test --integration包含端到端验证 - 保留
.approval-evidence/目录作为审计追踪 - 多签合约管理使用
--admin指定 DAO 多签地址
E. 故障排除
| 问题 | 原因 | 解决方案 |
|---|---|---|
cargo build 失败 |
缺少 wasm 目标 | rustup target add wasm32-unknown-unknown |
| wasm-opt 失败 | 缺少 binaryen | apt install binaryen 或 brew install binaryen |
| Docker 节点启动失败 | Docker 未运行 | 启动 Docker 桌面版 |
部署时 code ID not found |
编译产物路径错误 | 检查 artifacts/ 目录 |
| 控制台连接超时 | 节点未启动 | msg-devkit node start |
--guard 标志缺失 |
Guard 强制要求 | 添加 --guard 标志 |
msgd 命令未找到 |
MSG Chain 守护程序未安装 | 安装 msgd 或使用 Docker 节点 |
本文档为
@msg-chain/devkitv1.0.0 的完整参考指南。
更多信息请访问: https://github.com/msg-chain/devkit
问题反馈: https://github.com/msg-chain/devkit/issues
