MSG Chain 多链部署与治理同步指南
数据来源:MSG Chain 代码库核实
主网状态: No-Go — 当前 MSGChain 主网裁决为 No-Go,以下内容反映代码实际状态,不代表生产可用。
目录
1. 概述
1.1 为什么需要多链部署
1.1.1 市场触达
单一区块链的有限用户群体,多链部署可以:
| 指标 | 单链 | 多链 (3-5条) |
|---|---|---|
| 潜在用户基数 | 100 万 | 500-1000 万 |
| TVL 上限 | 链上总锁仓量 | 多链聚合 TVL |
| 交易吞吐量 | 单链 TPS | 聚合多链 TPS |
| 费用弹性 | 单链 Gas 波动 | 可选低费用链 |
| 生态接入 | 限于单链 DApp | 跨链组合性 |
多链覆盖的主流生态系统:
MSG Chain ─────┬── Cosmos Hub (IBC 枢纽)
│
├── Osmosis (跨链 DEX)
│
├── Neutron (跨链合约)
│
└── Stride (跨链质押)
│
└── (未来) Ethereum / BSC / Solana (自定义桥接)
1.1.2 风险分散
多链部署的核心风险管理价值:
- 协议风险: 单链宕机不影响其他链上的合约运行
- 经济风险: 单链代币价格波动不传染到其他链的流动性池
- 治理风险: 跨链治理同步可实现多链一致的规约变更
- 监管风险: 不同链的法律管辖可能不同
| 风险类型 | 单链部署 | 多链部署 | 缓解倍数 |
|---|---|---|---|
| 共识故障停机 | 100% | 1/N | N |
| 智能合约漏洞 | 100% | 风险隔离 | 取决于架构 |
| 治理攻击 | 单点 | 多链阈值 | 2-3x |
| 流动性危机 | 全部冻结 | 部分可用 | 2-5x |
1.1.3 用户基础与流动性聚合
用户获取渠道:
MSG Chain 原生用户 ───────────────► ┌─────────────────┐
Cosmos Hub 用户 (IBC 桥接) ──────► │ │
Osmosis 流动性提供者 ─────────────► │ 多链 DApp │
Neutron 开发者社区 ───────────────► │ 聚合流动性 │
Stride 质押用户 ──────────────────► │ 共享治理 │
└─────────────────┘
| 链 | 月活跃地址 | 链上 TVL | 用户获取成本 | 交叉潜力 |
|---|---|---|---|---|
| MSG Chain | 5 万 (估) | 5000 万 MSG | 低 | 基础 |
| Cosmos Hub | 50 万 | 30 亿 ATOM | 中 | IBC 直达 |
| Osmosis | 30 万 | 15 亿 OSMO | 中 | IBC + DEX |
| Neutron | 10 万 | 5 亿 NTRN | 低 | 跨链合约 |
| Stride | 5 万 | 3 亿 STRD | 低 | 跨链质押 |
1.2 MSG Chain 的 IBC 与跨链愿景
1.2.1 跨链架构定位
MSG Chain 在 Cosmos 生态中的跨链定位:
MSG Chain 跨链生态
┌──────────────────┐
│ AI Agent 核心层 │
│ (Agent 注册/执行) │
└────────┬─────────┘
│
┌─────────────┼─────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌────▼────┐
│ Cosmos │ │ 跨链 │ │ 非 Cosmos│
│ Hub │ │ DEX │ │ 桥 (ETH) │
│ (路由) │ │(流动) │ │ (扩展) │
└─────────┘ └─────────┘ └─────────┘
核心原则:
- IBC 优先: 所有 Cosmos 生态内跨链优先使用 IBC 协议
- 治理同步: 关键协议升级通过跨链治理协调
- 合约同构: 多链部署相同的合约代码,参数按链配置
- 统一前端: 用户通过单一接口与多链交互
1.2.2 多链治理同步的核心理念
传统多链部署问题:
┌──────────────┐ ┌──────────────┐
│ MSG Chain │ │ Cosmos Hub │
│ 治理提案 #1 │ │ 独自治理 │
│ 升级合约 A │ │ 合约 A 仍 │
│ 到 v2 │ │ 在 v1 │
└──────────────┘ └──────────────┘
↑ ↑
版本不一致 功能断裂
跨链治理同步方案:
┌──────────────┐ ┌──────────────┐
│ MSG Chain │◄───►│ Cosmos Hub │
│ 提案 #1 │ IBC │ 执行相同 │
│ 升级合约 A │◄───►│ 合约 A v2 │
│ 到 v2 │ │ │
└──────────────┘ └──────────────┘
↑ ↑
版本一致 功能同步
1.2.3 治理同步的工作流
治理同步 4 步工作流:
Step 1: 提交提案 ──────► 在 MSG Chain 提交多链治理提案
│ 包含目标链列表和执行消息
▼
Step 2: 投票通过 ──────► MSG Chain 治理通过
│ 达到法定投票率
▼
Step 3: IBC 中继 ──────► Relayer 将提案结果中继到目标链
│ IBC 数据包携带执行指令
▼
Step 4: 目标链执行 ────► 目标链合约解析指令并执行
完成状态变更
1.3 开发状态
本文档中所有标记
X-MSG-Stub=true的内容均为规划中的多链功能,尚未在主网上实现。
| 功能模块 | 状态 | 预计完成 |
|---|---|---|
| 多链部署脚本 | 规划态 | Q3 2026 |
| 跨链合约地址映射 | 规划态 | Q4 2026 |
| 跨链治理提案格式 | 规划态 | Q4 2026 |
| IBC 连接 | 规划态 | Q1 2027 |
| 治理投票中继 | 规划态 | Q2 2027 |
| 跨链执行协调 | 规划态 | Q3 2027 |
| 多链部署 CLI | 规划态 | Q3 2026 |
2. 多链部署策略
2.1 部署架构
2.1.1 同构部署模式
多链部署的最佳实践是在每条链上部署相同代码的合约,仅配置参数不同:
同构部署架构:
MSG Chain Cosmos Hub
┌─────────────────────────┐ ┌─────────────────────────┐
│ code_id: 5 │ │ code_id: 12 │
│ ┌──────────────────┐ │ │ ┌──────────────────┐ │
│ │ core_v1.0.0.wasm │ │ │ │ core_v1.0.0.wasm │ │
│ │ (相同 bytecode) │ │ │ │ (相同 bytecode) │ │
│ └──────────────────┘ │ │ └──────────────────┘ │
│ │ │ │ │ │
│ ┌──────▼───────┐ │ │ ┌──────▼───────┐ │
│ │ Contract A │ │ │ │ Contract B │ │
│ │ msg1abc... │ │ │ │ cosmos1xyz...│ │
│ └──────────────┘ │ │ └──────────────┘ │
│ init params: │ │ init params: │
│ owner = msg1... │ │ owner = cosmos1.. │
│ chain_id = msg.. │ │ chain_id = cosmos │
└─────────────────────────┘ └─────────────────────────┘
同构部署的优势:
| 维度 | 优势 |
|---|---|
| 代码管理 | 单仓库单版本,减少维护成本 |
| 审计效率 | 一次审计覆盖所有链 |
| 治理同步 | 相同合约接口,治理消息一致 |
| 用户体验 | 相同交互逻辑,仅地址不同 |
| 测试复用 | 跨链共享测试用例 |
2.1.2 链特定配置
每条链需要维护独立的配置参数:
chain_config:
msg-chain-1:
network: mainnet
chain_id: msg-chain-1
bech32_prefix: msg
rpc_url: "https://rpc.msgchain.org:26657"
grpc_url: "https://grpc.msgchain.org:9090"
gas_prices: "1000000000umsg"
gas_adjustment: 1.3
block_time: "5s"
confirmation_blocks: 10
explorer: "https://explorer.msgchain.org"
cosmoshub-4:
network: mainnet
chain_id: cosmoshub-4
bech32_prefix: cosmos
rpc_url: "https://rpc.cosmoshub-4.com:26657"
grpc_url: "https://grpc.cosmoshub-4.com:9090"
gas_prices: "0.025uatom"
gas_adjustment: 1.5
block_time: "6s"
confirmation_blocks: 15
explorer: "https://atomscan.com"
osmosis-1:
network: mainnet
chain_id: osmosis-1
bech32_prefix: osmo
rpc_url: "https://rpc.osmosis.zone:26657"
grpc_url: "https://grpc.osmosis.zone:9090"
gas_prices: "0.025uosmo"
gas_adjustment: 1.3
block_time: "6s"
confirmation_blocks: 12
explorer: "https://mintscan.io/osmosis"
合约级别的每链参数:
contract_config:
core:
WASM: "./artifacts/core_v1.0.0.wasm"
code_hash: "sha256:abc123..."
instances:
msg-chain-1:
label: "MSG Core v1"
admin: "msg1admin..."
init_msg:
owner: "msg1owner..."
chain_name: "msg-chain-1"
fee_recipient: "msg1fee..."
max_supply: "1000000000000000000000000"
paused: false
cosmoshub-4:
label: "Cosmos Core v1"
admin: "cosmos1admin..."
init_msg:
owner: "cosmos1owner..."
chain_name: "cosmoshub-4"
fee_recipient: "cosmos1fee..."
max_supply: "1000000000000000000000000"
paused: false
token:
WASM: "./artifacts/token_cw20_v1.0.0.wasm"
code_hash: "sha256:def456..."
instances:
msg-chain-1:
label: "MSG Token"
admin: "msg1admin..."
init_msg:
name: "MSG Token"
symbol: "MSG"
decimals: 18
initial_balances:
- address: "msg1treasury..."
amount: "500000000000000000000000000"
cosmoshub-4:
label: "MSG Token (Cosmos)"
admin: "cosmos1admin..."
init_msg:
name: "MSG Token"
symbol: "msg.MSG"
decimals: 18
initial_balances:
- address: "cosmos1treasury..."
amount: "500000000000000000000000000"
2.2 部署清单
2.2.1 完整部署清单格式
# X-MSG-Stub=true: 多链部署清单
# deployment-manifest.yaml
project:
name: "MSG Multi-Chain DApp"
version: "1.0.0"
repository: "https://github.com/msgchain/dapp-multichain"
deployments:
- chain: msg-chain-1
network: mainnet
chain_id: msg-chain-1
rpc: "https://rpc.msgchain.org"
grpc: "https://grpc.msgchain.org:9090"
contracts:
core:
address: "msg1abc123..."
code_id: 5
admin: "msg1admin..."
token:
address: "msg1def456..."
code_id: 6
admin: "msg1admin..."
staking:
address: "msg1ghi789..."
code_id: 7
admin: "msg1admin..."
ibc:
connection_id: "connection-0"
channels:
transfer: "channel-0"
governance: "channel-1"
- chain: cosmoshub-4
network: mainnet
chain_id: cosmoshub-4
rpc: "https://rpc.cosmos.org"
grpc: "https://grpc.cosmos.org:9090"
contracts:
token:
address: "cosmos1xyz789..."
code_id: 12
admin: "cosmos1admin..."
staking:
address: "cosmos1uvw345..."
code_id: 13
admin: "cosmos1admin..."
ibc:
connection_id: "connection-0"
channels:
transfer: "channel-3"
governance: "channel-4"
- chain: osmosis-1
network: mainnet
chain_id: osmosis-1
rpc: "https://rpc.osmosis.zone"
grpc: "https://grpc.osmosis.zone:9090"
contracts:
token:
address: "osmo1rst012..."
code_id: 25
admin: "osmo1admin..."
ibc:
connection_id: "connection-1"
channels:
transfer: "channel-7"
2.2.2 TypeScript 部署清单解析器
// X-MSG-Stub=true: deployment-manifest.ts
import { readFileSync, writeFileSync } from "fs";
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
interface ContractInstance {
address?: string;
code_id?: number;
admin?: string;
init_msg?: Record<string, unknown>;
label?: string;
}
interface ChainDeployment {
chain: string;
network: string;
chain_id: string;
rpc: string;
grpc: string;
contracts: Record<string, ContractInstance>;
ibc?: {
connection_id: string;
channels: Record<string, string>;
};
}
interface DeploymentManifest {
project: {
name: string;
version: string;
repository: string;
};
deployments: ChainDeployment[];
}
class MultiChainDeployment {
private manifest: DeploymentManifest;
constructor(manifestPath: string) {
const content = readFileSync(manifestPath, "utf-8");
this.manifest = parseYaml(content) as DeploymentManifest;
}
getChains(): string[] {
return this.manifest.deployments.map((d) => d.chain);
}
getChainDeployment(chain: string): ChainDeployment | undefined {
return this.manifest.deployments.find((d) => d.chain === chain);
}
getContractAddress(chain: string, contractName: string): string | undefined {
const deployment = this.getChainDeployment(chain);
return deployment?.contracts[contractName]?.address;
}
getIBCChannel(chain: string, channelType: string): string | undefined {
const deployment = this.getChainDeployment(chain);
return deployment?.ibc?.channels[channelType];
}
updateContractAddress(
chain: string,
contractName: string,
address: string
): void {
const deployment = this.getChainDeployment(chain);
if (deployment) {
deployment.contracts[contractName] = {
...deployment.contracts[contractName],
address,
};
}
}
save(path: string): void {
const yaml = stringifyYaml(this.manifest);
writeFileSync(path, yaml, "utf-8");
}
validate(): string[] {
const errors: string[] = [];
for (const dep of this.manifest.deployments) {
if (!dep.chain_id) {
errors.push(`Missing chain_id for deployment: ${dep.chain}`);
}
if (!dep.rpc) {
errors.push(`Missing rpc for: ${dep.chain}`);
}
for (const [name, contract] of Object.entries(dep.contracts)) {
if (!contract.address && !contract.code_id) {
errors.push(
`Contract ${name} on ${dep.chain} has no address or code_id`
);
}
}
}
return errors;
}
}
export {
DeploymentManifest,
ChainDeployment,
ContractInstance,
MultiChainDeployment,
};
2.2.3 Python 清单工具
"""
X-MSG-Stub=true: deployment_manifest.py
"""
import yaml
from typing import Dict, List, Optional
from dataclasses import dataclass, field, asdict
@dataclass
class ContractInstance:
address: Optional[str] = None
code_id: Optional[int] = None
admin: Optional[str] = None
init_msg: Optional[Dict] = None
label: Optional[str] = None
@dataclass
class ChainDeployment:
chain: str
network: str
chain_id: str
rpc: str
grpc: str
contracts: Dict[str, ContractInstance] = field(default_factory=dict)
ibc: Optional[Dict] = None
@dataclass
class DeploymentManifest:
project: Dict
deployments: List[ChainDeployment]
class ManifestManager:
def __init__(self, path: str):
self.path = path
with open(path, "r") as f:
raw = yaml.safe_load(f)
self.manifest = self._from_dict(raw)
def _from_dict(self, raw: Dict) -> DeploymentManifest:
deps = []
for dep_raw in raw["deployments"]:
contracts = {}
for name, c in dep_raw.get("contracts", {}).items():
contracts[name] = ContractInstance(**c)
dep_raw["contracts"] = contracts
deps.append(ChainDeployment(**dep_raw))
raw["deployments"] = deps
return DeploymentManifest(**raw)
def get_all_addresses(self) -> Dict[str, Dict[str, str]]:
result = {}
for dep in self.manifest.deployments:
chain_addrs = {}
for name, contract in dep.contracts.items():
if contract.address:
chain_addrs[name] = contract.address
result[dep.chain] = chain_addrs
return result
def set_address(self, chain: str, contract: str, address: str):
for dep in self.manifest.deployments:
if dep.chain == chain and contract in dep.contracts:
dep.contracts[contract].address = address
def save(self):
raw = {
"project": self.manifest.project,
"deployments": [
{
"chain": d.chain,
"network": d.network,
"chain_id": d.chain_id,
"rpc": d.rpc,
"grpc": d.grpc,
"contracts": {
name: asdict(c)
for name, c in d.contracts.items()
},
"ibc": d.ibc,
}
for d in self.manifest.deployments
],
}
with open(self.path, "w") as f:
yaml.dump(raw, f, default_flow_style=False)
2.3 部署脚本
2.3.1 TypeScript 部署脚本
// X-MSG-Stub=true: deploy-multichain.ts
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { GasPrice } from "@cosmjs/stargate";
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing";
import { readFileSync } from "fs";
import { MultiChainDeployment } from "./deployment-manifest";
interface ChainConnection {
chainId: string;
rpcUrl: string;
client: SigningCosmWasmClient;
sender: string;
gasPrice: GasPrice;
}
interface DeployResult {
chain: string;
contract: string;
codeId: number;
address: string;
txHash: string;
}
class MultiChainDeployer {
private connections: Map<string, ChainConnection> = new Map();
private manifest: MultiChainDeployment;
constructor(manifestPath: string) {
this.manifest = new MultiChainDeployment(manifestPath);
}
async connectChain(
chain: string,
mnemonic: string
): Promise<ChainConnection> {
const dep = this.manifest.getChainDeployment(chain);
if (!dep) throw new Error(`Chain ${chain} not found in manifest`);
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, {
prefix: dep.chain === "msg-chain-1" ? "msg" : "cosmos",
});
const gasPrice = GasPrice.fromString(
dep.chain === "msg-chain-1"
? "1000000000umsg"
: "0.025uatom"
);
const client = await SigningCosmWasmClient.connectWithSigner(
dep.rpc,
wallet
);
const [{ address }] = await wallet.getAccounts();
const connection: ChainConnection = {
chainId: dep.chain_id,
rpcUrl: dep.rpc,
client,
sender: address,
gasPrice,
};
this.connections.set(chain, connection);
return connection;
}
async uploadWasm(
chain: string,
wasmPath: string,
label: string
): Promise<number> {
const connection = this.connections.get(chain);
if (!connection) throw new Error(`Not connected to ${chain}`);
const wasm = readFileSync(wasmPath);
const result = await connection.client.upload(
connection.sender,
wasm,
{
amount: [{ denom: "umsg", amount: "500000" }],
gas: "5000000",
},
label
);
console.log(
`[${chain}] Uploaded ${wasmPath} -> code_id: ${result.codeId}`
);
return result.codeId;
}
async instantiateContract(
chain: string,
codeId: number,
initMsg: Record<string, unknown>,
label: string,
admin: string
): Promise<string> {
const connection = this.connections.get(chain);
if (!connection) throw new Error(`Not connected to ${chain}`);
const result = await connection.client.instantiate(
connection.sender,
codeId,
initMsg,
label,
{
amount: [{ denom: "umsg", amount: "250000" }],
gas: "2000000",
},
{ admin }
);
console.log(
`[${chain}] Instantiated ${label} at ${result.contractAddress}`
);
return result.contractAddress;
}
async deployToChain(
chain: string,
wasmPath: string,
contractName: string,
initMsg: Record<string, unknown>,
label: string,
admin: string
): Promise<DeployResult> {
const codeId = await this.uploadWasm(chain, wasmPath, label);
const address = await this.instantiateContract(
chain,
codeId,
initMsg,
label,
admin
);
this.manifest.updateContractAddress(chain, contractName, address);
this.manifest.save("deployment-manifest.yaml");
return {
chain,
contract: contractName,
codeId,
address,
txHash: "",
};
}
async deployAll(
wasmPath: string,
contractName: string,
initMsgs: Record<string, Record<string, unknown>>,
label: string,
admin: string
): Promise<DeployResult[]> {
const results: DeployResult[] = [];
for (const chain of this.manifest.getChains()) {
const initMsg = initMsgs[chain] || {};
const result = await this.deployToChain(
chain,
wasmPath,
contractName,
initMsg,
`${label}-${chain}`,
admin
);
results.push(result);
}
return results;
}
}
async function main() {
const deployer = new MultiChainDeployer("./deployment-manifest.yaml");
const mnemonic = process.env.DEPLOYER_MNEMONIC!;
await deployer.connectChain("msg-chain-1", mnemonic);
await deployer.connectChain("cosmoshub-4", mnemonic);
const results = await deployer.deployAll(
"./artifacts/token_cw20_v1.0.0.wasm",
"token",
{
"msg-chain-1": {
name: "MSG Token",
symbol: "MSG",
decimals: 18,
initial_balances: [],
},
"cosmoshub-4": {
name: "MSG Token",
symbol: "msg.MSG",
decimals: 18,
initial_balances: [],
},
},
"MSG Token v1",
"msg1admin..."
);
console.log("Deployment complete:", JSON.stringify(results, null, 2));
}
if (require.main === module) {
main().catch(console.error);
}
2.3.2 Python 部署脚本
"""
X-MSG-Stub=true: deploy_multichain.py
"""
import os
import json
import hashlib
import time
from typing import Dict, List, Optional
from dataclasses import dataclass
import requests
from deployment_manifest import ManifestManager
@dataclass
class ChainNode:
chain_id: str
rpc: str
grpc: str
prefix: str
gas_price: str
class CosmWasmClient:
def __init__(self, node: ChainNode, private_key: str):
self.node = node
self.private_key = private_key
self.base_url = node.rpc.replace(":26657", ":1317")
def upload_contract(self, wasm_path: str) -> Dict:
with open(wasm_path, "rb") as f:
wasm_bytes = f.read()
code_hash = hashlib.sha256(wasm_bytes).hexdigest()
print(f"[{self.node.chain_id}] WASM size: {len(wasm_bytes)} bytes")
print(f"[{self.node.chain_id}] Code hash: {code_hash}")
return {
"status": "simulated",
"code_id": 0,
"code_hash": code_hash,
"tx_hash": "simulated_tx_hash",
}
def instantiate_contract(
self, code_id: int, init_msg: Dict, label: str, admin: str
) -> Dict:
simulated_address = f"{self.node.prefix}1simulated{code_id}{int(time.time())}"
return {
"status": "simulated",
"contract_address": simulated_address,
"tx_hash": "simulated_tx_hash",
}
def query_contract(self, address: str, query_msg: Dict) -> Dict:
return {"status": "simulated"}
class MultiChainDeployer:
def __init__(self, manifest_path: str):
self.manifest = ManifestManager(manifest_path)
self.clients: Dict[str, CosmWasmClient] = {}
def connect(self, chain: str, private_key: str):
dep = None
for d in self.manifest.manifest.deployments:
if d.chain == chain:
dep = d
break
if not dep:
raise ValueError(f"Chain {chain} not in manifest")
prefix_map = {
"msg-chain-1": "msg",
"cosmoshub-4": "cosmos",
"osmosis-1": "osmo",
}
node = ChainNode(
chain_id=dep.chain_id,
rpc=dep.rpc,
grpc=dep.grpc,
prefix=prefix_map.get(chain, "msg"),
gas_price="1000000000umsg" if chain == "msg-chain-1" else "0.025uatom",
)
self.clients[chain] = CosmWasmClient(node, private_key)
print(f"[{chain}] Connected to {node.rpc}")
def deploy_contract(
self,
chain: str,
wasm_path: str,
contract_name: str,
init_msg: Dict,
label: str,
admin: str,
) -> Dict:
client = self.clients[chain]
upload_result = client.upload_contract(wasm_path)
code_id = upload_result["code_id"]
instantiate_result = client.instantiate_contract(
code_id, init_msg, label, admin
)
contract_address = instantiate_result["contract_address"]
self.manifest.set_address(chain, contract_name, contract_address)
self.manifest.save()
return {
"chain": chain,
"contract": contract_name,
"code_id": code_id,
"address": contract_address,
"tx_hash": instantiate_result["tx_hash"],
}
def deploy_to_all_chains(
self,
wasm_path: str,
contract_name: str,
init_msgs: Dict[str, Dict],
label: str,
admin: str,
parallel: bool = True,
) -> List[Dict]:
results = []
if parallel:
import concurrent.futures
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = []
for chain in self.manifest.get_all_addresses().keys():
init_msg = init_msgs.get(chain, {})
future = executor.submit(
self.deploy_contract,
chain,
wasm_path,
contract_name,
init_msg,
f"{label}-{chain}",
admin,
)
futures.append(future)
for future in concurrent.futures.as_completed(futures):
results.append(future.result())
else:
for chain in self.manifest.get_all_addresses().keys():
init_msg = init_msgs.get(chain, {})
result = self.deploy_contract(
chain,
wasm_path,
contract_name,
init_msg,
f"{label}-{chain}",
admin,
)
results.append(result)
return results
def main():
deployer = MultiChainDeployer("./deployment-manifest.yaml")
private_key = os.environ.get("DEPLOYER_KEY", "")
deployer.connect("msg-chain-1", private_key)
deployer.connect("cosmoshub-4", private_key)
results = deployer.deploy_to_all_chains(
wasm_path="./artifacts/core_v1.0.0.wasm",
contract_name="core",
init_msgs={
"msg-chain-1": {
"owner": "msg1owner...",
"chain_name": "msg-chain-1",
"paused": False,
},
"cosmoshub-4": {
"owner": "cosmos1owner...",
"chain_name": "cosmoshub-4",
"paused": False,
},
},
label="MSG Core v1",
admin="msg1admin...",
parallel=True,
)
print(json.dumps(results, indent=2))
if __name__ == "__main__":
main()
2.4 链特定参数管理
2.4.1 TypeScript 配置管理
// X-MSG-Stub=true: chain-config.ts
interface ChainConfig {
chainId: string;
prefix: string;
rpc: string;
grpc: string;
gasPrice: string;
gasAdjustment: number;
explorerTxUrl: string;
explorerAddressUrl: string;
}
const chainConfigs: Record<string, ChainConfig> = {
"msg-chain-1": {
chainId: "msg-chain-1",
prefix: "msg",
rpc: "https://rpc.msgchain.org:26657",
grpc: "https://grpc.msgchain.org:9090",
gasPrice: "1000000000umsg",
gasAdjustment: 1.3,
explorerTxUrl: "https://explorer.msgchain.org/tx/",
explorerAddressUrl: "https://explorer.msgchain.org/address/",
},
"cosmoshub-4": {
chainId: "cosmoshub-4",
prefix: "cosmos",
rpc: "https://rpc.cosmoshub-4.com:26657",
grpc: "https://grpc.cosmoshub-4.com:9090",
gasPrice: "0.025uatom",
gasAdjustment: 1.5,
explorerTxUrl: "https://atomscan.com/tx/",
explorerAddressUrl: "https://atomscan.com/address/",
},
"osmosis-1": {
chainId: "osmosis-1",
prefix: "osmo",
rpc: "https://rpc.osmosis.zone:26657",
grpc: "https://grpc.osmosis.zone:9090",
gasPrice: "0.025uosmo",
gasAdjustment: 1.3,
explorerTxUrl: "https://mintscan.io/osmosis/tx/",
explorerAddressUrl: "https://mintscan.io/osmosis/address/",
},
};
function getChainConfig(chainId: string): ChainConfig {
const config = chainConfigs[chainId];
if (!config) {
throw new Error(`Unknown chain: ${chainId}`);
}
return config;
}
export { ChainConfig, chainConfigs, getChainConfig };
2.4.2 Python 配置管理
"""
X-MSG-Stub=true: chain_config.py
"""
from typing import Dict
from dataclasses import dataclass
@dataclass
class ChainConfig:
chain_id: str
prefix: str
rpc: str
grpc: str
gas_price: str
gas_adjustment: float
explorer_tx_url: str
explorer_address_url: str
block_time_seconds: int = 5
confirmation_blocks: int = 10
CHAIN_CONFIGS: Dict[str, ChainConfig] = {
"msg-chain-1": ChainConfig(
chain_id="msg-chain-1",
prefix="msg",
rpc="https://rpc.msgchain.org:26657",
grpc="https://grpc.msgchain.org:9090",
gas_price="1000000000umsg",
gas_adjustment=1.3,
explorer_tx_url="https://explorer.msgchain.org/tx/",
explorer_address_url="https://explorer.msgchain.org/address/",
block_time_seconds=5,
confirmation_blocks=10,
),
"cosmoshub-4": ChainConfig(
chain_id="cosmoshub-4",
prefix="cosmos",
rpc="https://rpc.cosmoshub-4.com:26657",
grpc="https://grpc.cosmoshub-4.com:9090",
gas_price="0.025uatom",
gas_adjustment=1.5,
explorer_tx_url="https://atomscan.com/tx/",
explorer_address_url="https://atomscan.com/address/",
block_time_seconds=6,
confirmation_blocks=15,
),
"osmosis-1": ChainConfig(
chain_id="osmosis-1",
prefix="osmo",
rpc="https://rpc.osmosis.zone:26657",
grpc="https://grpc.osmosis.zone:9090",
gas_price="0.025uosmo",
gas_adjustment=1.3,
explorer_tx_url="https://mintscan.io/osmosis/tx/",
explorer_address_url="https://mintscan.io/osmosis/address/",
block_time_seconds=6,
confirmation_blocks=12,
),
}
def get_chain_config(chain_id: str) -> ChainConfig:
config = CHAIN_CONFIGS.get(chain_id)
if not config:
raise ValueError(f"Unknown chain: {chain_id}")
return config
2.5 部署验证
// X-MSG-Stub=true: verify-deployment.ts
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { MultiChainDeployment } from "./deployment-manifest";
interface VerificationResult {
chain: string;
contract: string;
address: string;
exists: boolean;
codeId: number;
codeHash: string;
expectedCodeHash?: string;
codeMatch: boolean;
admin: string;
expectedAdmin?: string;
adminMatch: boolean;
}
class DeploymentVerifier {
private manifest: MultiChainDeployment;
constructor(manifestPath: string) {
this.manifest = new MultiChainDeployment(manifestPath);
}
async verifyContract(
chain: string,
contractName: string
): Promise<VerificationResult> {
const dep = this.manifest.getChainDeployment(chain);
if (!dep) throw new Error(`Chain ${chain} not found`);
const contract = dep.contracts[contractName];
if (!contract) throw new Error(`Contract ${contractName} not found`);
const client = await CosmWasmClient.connect(dep.rpc);
const address = contract.address!;
let exists = false;
let codeId = 0;
let codeHash = "";
let admin = "";
try {
const info = await client.getContract(address);
exists = true;
codeId = info.codeId;
admin = info.admin || "";
const codeInfo = await client.getCodeDetails(codeId);
codeHash = codeInfo.checksum;
} catch {
exists = false;
}
const codeMatch = contract.code_id
? codeId === contract.code_id
: true;
const adminMatch = contract.admin ? admin === contract.admin : true;
return {
chain,
contract: contractName,
address,
exists,
codeId,
codeHash,
expectedCodeHash: undefined,
codeMatch,
admin,
expectedAdmin: contract.admin,
adminMatch,
};
}
async verifyAll(): Promise<VerificationResult[]> {
const results: VerificationResult[] = [];
for (const chain of this.manifest.getChains()) {
const dep = this.manifest.getChainDeployment(chain)!;
for (const contractName of Object.keys(dep.contracts)) {
const result = await this.verifyContract(chain, contractName);
results.push(result);
const status = result.exists ? "*" : "x";
console.log(
`[${status}] ${chain}:${contractName} @ ${result.address} ` +
`code=${result.codeId} admin=${result.admin.slice(0, 12)}...`
);
}
}
return results;
}
}
async function main() {
const verifier = new DeploymentVerifier("./deployment-manifest.yaml");
const results = await verifier.verifyAll();
const allOk = results.every((r) => r.exists && r.codeMatch);
console.log(`\nOverall status: ${allOk ? "* ALL OK" : "x ISSUES FOUND"}`);
if (!allOk) {
process.exit(1);
}
}
if (require.main === module) {
main().catch(console.error);
}
3. 跨链合约地址映射
3.1 地址映射合约
3.1.1 合约设计
每条链上需要部署一个地址映射合约,用于存储和管理跨链合约地址:
地址映射合约结构:
ChainAddressMap Contract
┌─────────────────────────────────┐
│ mapping: │
│ (chain_id, contract_name) │
│ -> remote_address │
│ │
│ msg-chain-1: core -> msg1abc... │
│ msg-chain-1: token -> msg1def... │
│ cosmoshub-4: token -> cosmos1xyz...│
│ osmosis-1: token -> osmo1rst... │
│ │
│ admin: msg1admin... │
└─────────────────────────────────┘
3.1.2 Rust 合约实现
// X-MSG-Stub=true: src/msg.rs
use cosmwasm_schema::cw_serde;
#[cw_serde]
pub struct InstantiateMsg {
pub admin: String,
pub initial_mappings: Vec<ChainAddressMapping>,
}
#[cw_serde]
pub struct ChainAddressMapping {
pub chain_id: String,
pub contract_name: String,
pub remote_address: String,
}
#[cw_serde]
pub enum ExecuteMsg {
RegisterAddress {
chain_id: String,
contract_name: String,
remote_address: String,
},
BatchRegisterAddresses {
mappings: Vec<ChainAddressMapping>,
},
UpdateAddress {
chain_id: String,
contract_name: String,
remote_address: String,
},
RemoveAddress {
chain_id: String,
contract_name: String,
},
SetAdmin {
admin: String,
},
}
#[cw_serde]
pub enum QueryMsg {
ResolveAddress {
chain_id: String,
contract_name: String,
},
ResolveContractName {
chain_id: String,
remote_address: String,
},
ListChainAddresses {
chain_id: String,
},
ListAllAddresses {},
GetAdmin {},
}
// X-MSG-Stub=true: src/state.rs
use cosmwasm_schema::cw_serde;
use cw_storage_plus::{Item, Map};
use cosmwasm_std::Addr;
#[cw_serde]
pub struct Config {
pub admin: Addr,
}
pub const CONFIG: Item<Config> = Item::new("config");
pub const ADDRESS_MAP: Map<(&str, &str), String> = Map::new("addr_map");
pub const REVERSE_MAP: Map<(&str, &str), String> = Map::new("rev_map");
pub const CHAINS: Map<&str, bool> = Map::new("chains");
// X-MSG-Stub=true: src/contract.rs
use cosmwasm_std::{
entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo,
Order, Response, StdError, StdResult,
};
use cw2::set_contract_version;
use crate::error::ContractError;
use crate::msg::{
AddressResponse, ChainAddressMapping, ExecuteMsg, InstantiateMsg,
QueryMsg, ResolveResult,
};
use crate::state::{ADDRESS_MAP, CHAINS, CONFIG, Config, REVERSE_MAP};
const CONTRACT_NAME: &str = "msg-chain-address-map";
const CONTRACT_VERSION: &str = "1.0.0";
#[entry_point]
pub fn instantiate(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
msg: InstantiateMsg,
) -> StdResult<Response> {
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
let config = Config {
admin: deps.api.addr_validate(&msg.admin)?,
};
CONFIG.save(deps.storage, &config)?;
for mapping in msg.initial_mappings {
ADDRESS_MAP.save(
deps.storage,
(&mapping.chain_id, &mapping.contract_name),
&mapping.remote_address,
)?;
REVERSE_MAP.save(
deps.storage,
(&mapping.chain_id, &mapping.remote_address),
&mapping.contract_name,
)?;
CHAINS.save(deps.storage, &mapping.chain_id, &true)?;
}
Ok(Response::new()
.add_attribute("method", "instantiate")
.add_attribute("admin", msg.admin))
}
#[entry_point]
pub fn execute(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
let config = CONFIG.load(deps.storage)?;
if info.sender != config.admin {
return Err(ContractError::Unauthorized {});
}
match msg {
ExecuteMsg::RegisterAddress {
chain_id,
contract_name,
remote_address,
} => execute_register(deps, chain_id, contract_name, remote_address),
ExecuteMsg::BatchRegisterAddresses { mappings } => {
execute_batch_register(deps, mappings)
}
ExecuteMsg::UpdateAddress {
chain_id,
contract_name,
remote_address,
} => execute_update(deps, chain_id, contract_name, remote_address),
ExecuteMsg::RemoveAddress {
chain_id,
contract_name,
} => execute_remove(deps, chain_id, contract_name),
ExecuteMsg::SetAdmin { admin } => execute_set_admin(deps, admin),
}
}
fn execute_register(
deps: DepsMut,
chain_id: String,
contract_name: String,
remote_address: String,
) -> Result<Response, ContractError> {
let key = (chain_id.as_str(), contract_name.as_str());
if ADDRESS_MAP.has(deps.storage, key) {
return Err(ContractError::AlreadyRegistered {
chain_id,
contract_name,
});
}
ADDRESS_MAP.save(deps.storage, key, &remote_address)?;
REVERSE_MAP.save(
deps.storage,
(&chain_id, &remote_address),
&contract_name,
)?;
CHAINS.save(deps.storage, &chain_id, &true)?;
Ok(Response::new()
.add_attribute("method", "register_address")
.add_attribute("chain_id", &chain_id)
.add_attribute("contract_name", &contract_name)
.add_attribute("remote_address", &remote_address))
}
fn execute_batch_register(
deps: DepsMut,
mappings: Vec<ChainAddressMapping>,
) -> Result<Response, ContractError> {
let mut response = Response::new()
.add_attribute("method", "batch_register")
.add_attribute("count", mappings.len().to_string());
for mapping in mappings {
let key = (mapping.chain_id.as_str(), mapping.contract_name.as_str());
if ADDRESS_MAP.has(deps.storage, key) {
continue;
}
ADDRESS_MAP.save(deps.storage, key, &mapping.remote_address)?;
REVERSE_MAP.save(
deps.storage,
(&mapping.chain_id, &mapping.remote_address),
&mapping.contract_name,
)?;
CHAINS.save(deps.storage, &mapping.chain_id, &true)?;
}
Ok(response)
}
fn execute_update(
deps: DepsMut,
chain_id: String,
contract_name: String,
remote_address: String,
) -> Result<Response, ContractError> {
let key = (chain_id.as_str(), contract_name.as_str());
let old_address = ADDRESS_MAP
.load(deps.storage, key)
.map_err(|_| ContractError::NotFound {
chain_id: chain_id.clone(),
contract_name: contract_name.clone(),
})?;
REVERSE_MAP.remove(deps.storage, (&chain_id, &old_address));
ADDRESS_MAP.save(deps.storage, key, &remote_address)?;
REVERSE_MAP.save(deps.storage, (&chain_id, &remote_address), &contract_name)?;
Ok(Response::new()
.add_attribute("method", "update_address")
.add_attribute("chain_id", &chain_id)
.add_attribute("contract_name", &contract_name)
.add_attribute("old_address", &old_address)
.add_attribute("new_address", &remote_address))
}
fn execute_remove(
deps: DepsMut,
chain_id: String,
contract_name: String,
) -> Result<Response, ContractError> {
let key = (chain_id.as_str(), contract_name.as_str());
let address = ADDRESS_MAP
.load(deps.storage, key)
.map_err(|_| ContractError::NotFound {
chain_id: chain_id.clone(),
contract_name: contract_name.clone(),
})?;
ADDRESS_MAP.remove(deps.storage, key);
REVERSE_MAP.remove(deps.storage, (&chain_id, &address));
Ok(Response::new()
.add_attribute("method", "remove_address")
.add_attribute("chain_id", &chain_id)
.add_attribute("contract_name", &contract_name))
}
fn execute_set_admin(
deps: DepsMut,
admin: String,
) -> Result<Response, ContractError> {
let validated = deps.api.addr_validate(&admin)?;
let mut config = CONFIG.load(deps.storage)?;
config.admin = validated.clone();
CONFIG.save(deps.storage, &config)?;
Ok(Response::new()
.add_attribute("method", "set_admin")
.add_attribute("admin", admin))
}
#[entry_point]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::ResolveAddress {
chain_id,
contract_name,
} => to_binary(&query_resolve(deps, chain_id, contract_name)?),
QueryMsg::ResolveContractName {
chain_id,
remote_address,
} => to_binary(&query_reverse(deps, chain_id, remote_address)?),
QueryMsg::ListChainAddresses { chain_id } => {
to_binary(&query_chain_addresses(deps, chain_id)?)
}
QueryMsg::ListAllAddresses {} => to_binary(&query_all(deps)?),
QueryMsg::GetAdmin {} => to_binary(&CONFIG.load(deps.storage)?.admin),
}
}
fn query_resolve(
deps: Deps,
chain_id: String,
contract_name: String,
) -> StdResult<ResolveResult> {
let address = ADDRESS_MAP
.may_load(deps.storage, (&chain_id, &contract_name))?;
Ok(ResolveResult {
found: address.is_some(),
address,
})
}
fn query_reverse(
deps: Deps,
chain_id: String,
remote_address: String,
) -> StdResult<ResolveResult> {
let name = REVERSE_MAP
.may_load(deps.storage, (&chain_id, &remote_address))?;
Ok(ResolveResult {
found: name.is_some(),
address: name,
})
}
fn query_chain_addresses(
deps: Deps,
chain_id: String,
) -> StdResult<Vec<AddressResponse>> {
let prefix = ADDRESS_MAP.prefix(&chain_id);
let results: StdResult<Vec<_>> = prefix
.range(deps.storage, None, None, Order::Ascending)
.map(|r| {
let (name, addr) = r?;
Ok(AddressResponse {
chain_id: chain_id.clone(),
contract_name: name,
remote_address: addr,
})
})
.collect();
results
}
fn query_all(deps: Deps) -> StdResult<Vec<AddressResponse>> {
let results: StdResult<Vec<_>> = ADDRESS_MAP
.range(deps.storage, None, None, Order::Ascending)
.map(|r| {
let ((chain_id, name), addr) = r?;
Ok(AddressResponse {
chain_id,
contract_name: name,
remote_address: addr,
})
})
.collect();
results
}
// X-MSG-Stub=true: src/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("Address already registered: {chain_id}/{contract_name}")]
AlreadyRegistered {
chain_id: String,
contract_name: String,
},
#[error("Address not found: {chain_id}/{contract_name}")]
NotFound {
chain_id: String,
contract_name: String,
},
}
3.2 跨链合约解析
3.2.1 TypeScript 解析库
// X-MSG-Stub=true: cross-chain-resolver.ts
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
interface ChainEndpoint {
chainId: string;
rpc: string;
addressMapContract: string;
}
interface AddressResolution {
chainId: string;
contractName: string;
address: string;
}
class CrossChainResolver {
private clients: Map<string, CosmWasmClient> = new Map();
private endpoints: Map<string, ChainEndpoint> = new Map();
constructor(endpoints: ChainEndpoint[]) {
for (const ep of endpoints) {
this.endpoints.set(ep.chainId, ep);
}
}
async connect(chainId: string): Promise<CosmWasmClient> {
if (this.clients.has(chainId)) {
return this.clients.get(chainId)!;
}
const ep = this.endpoints.get(chainId);
if (!ep) throw new Error(`Unknown chain: ${chainId}`);
const client = await CosmWasmClient.connect(ep.rpc);
this.clients.set(chainId, client);
return client;
}
async resolveAddress(
fromChain: string,
targetChainId: string,
contractName: string
): Promise<string> {
const client = await this.connect(fromChain);
const ep = this.endpoints.get(fromChain)!;
const result: { found: boolean; address?: string } =
await client.queryContractSmart(ep.addressMapContract, {
resolve_address: {
chain_id: targetChainId,
contract_name: contractName,
},
});
if (!result.found || !result.address) {
throw new Error(
`Address not found: ${targetChainId}/${contractName}`
);
}
return result.address;
}
async resolveContractName(
fromChain: string,
targetChainId: string,
remoteAddress: string
): Promise<string> {
const client = await this.connect(fromChain);
const ep = this.endpoints.get(fromChain)!;
const result: { found: boolean; address?: string } =
await client.queryContractSmart(ep.addressMapContract, {
resolve_contract_name: {
chain_id: targetChainId,
remote_address: remoteAddress,
},
});
if (!result.found || !result.address) {
throw new Error(
`Contract name not found: ${targetChainId}/${remoteAddress}`
);
}
return result.address;
}
async getAllAddresses(
chainId: string
): Promise<AddressResolution[]> {
const client = await this.connect(chainId);
const ep = this.endpoints.get(chainId)!;
const addresses: AddressResolution[] =
await client.queryContractSmart(ep.addressMapContract, {
list_chain_addresses: {
chain_id: chainId,
},
});
return addresses;
}
async batchResolve(
requests: { fromChain: string; targetChain: string; contractName: string }[]
): Promise<Map<string, AddressResolution>> {
const results = new Map<string, AddressResolution>();
const grouped = new Map<string, typeof requests>();
for (const req of requests) {
const key = req.fromChain;
if (!grouped.has(key)) grouped.set(key, []);
grouped.get(key)!.push(req);
}
for (const [fromChain, reqs] of grouped) {
const client = await this.connect(fromChain);
const ep = this.endpoints.get(fromChain)!;
for (const req of reqs) {
try {
const address = await this.resolveAddress(
fromChain,
req.targetChain,
req.contractName
);
results.set(`${req.targetChain}/${req.contractName}`, {
chainId: req.targetChain,
contractName: req.contractName,
address,
});
} catch (e) {
console.error(
`Failed to resolve ${req.targetChain}/${req.contractName}:`,
e
);
}
}
}
return results;
}
async verifyCrossChainConsistency(): Promise<{
consistent: boolean;
mismatches: Array<{
chainId: string;
contractName: string;
addresses: Map<string, string>;
}>;
}> {
const allChains = Array.from(this.endpoints.keys());
const mismatches: Array<{
chainId: string;
contractName: string;
addresses: Map<string, string>;
}> = [];
for (const chainId of allChains) {
const addresses = await this.getAllAddresses(chainId);
for (const addr of addresses) {
const resolved = await this.resolveAddress(
chainId,
addr.chainId,
addr.contractName
);
if (resolved !== addr.address) {
mismatches.push({
chainId,
contractName: addr.contractName,
addresses: new Map([
["stored", resolved],
["remote", addr.address],
]),
});
}
}
}
return {
consistent: mismatches.length === 0,
mismatches,
};
}
}
export { CrossChainResolver, ChainEndpoint, AddressResolution };
3.2.2 Python 解析库
"""
X-MSG-Stub=true: cross_chain_resolver.py
"""
import json
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass
import requests
@dataclass
class ChainEndpoint:
chain_id: str
rpc: str
address_map_contract: str
@property
def lcd_url(self) -> str:
return self.rpc.replace(":26657", ":1317")
class CrossChainResolver:
def __init__(self, endpoints: List[ChainEndpoint]):
self.endpoints = {ep.chain_id: ep for ep in endpoints}
def _query_contract(self, chain_id: str, query_msg: Dict) -> Dict:
ep = self.endpoints[chain_id]
url = f"{ep.lcd_url}/cosmwasm/wasm/v1/contract/{ep.address_map_contract}/smart/{json.dumps(query_msg)}"
resp = requests.get(url)
resp.raise_for_status()
return resp.json()["data"]
def resolve_address(
self, from_chain: str, target_chain: str, contract_name: str
) -> str:
result = self._query_contract(from_chain, {
"resolve_address": {
"chain_id": target_chain,
"contract_name": contract_name,
}
})
if not result.get("found"):
raise ValueError(f"Address not found: {target_chain}/{contract_name}")
return result["address"]
def resolve_contract_name(
self, from_chain: str, target_chain: str, remote_address: str
) -> str:
result = self._query_contract(from_chain, {
"resolve_contract_name": {
"chain_id": target_chain,
"remote_address": remote_address,
}
})
if not result.get("found"):
raise ValueError(f"Contract not found: {target_chain}/{remote_address}")
return result["address"]
def list_chain_addresses(self, chain_id: str) -> List[Dict]:
return self._query_contract(chain_id, {
"list_chain_addresses": {"chain_id": chain_id}
})
def list_all(self, chain_id: str) -> List[Dict]:
return self._query_contract(chain_id, {"list_all_addresses": {}})
def verify_cross_chain_consistency(self) -> Tuple[bool, List[Dict]]:
mismatches = []
all_chains = list(self.endpoints.keys())
for chain_id in all_chains:
addresses = self.list_chain_addresses(chain_id)
for addr in addresses:
try:
resolved = self.resolve_address(
chain_id, addr["chain_id"], addr["contract_name"]
)
if resolved != addr["remote_address"]:
mismatches.append({
"chain": chain_id,
"contract": addr["contract_name"],
"stored": resolved,
"remote": addr["remote_address"],
})
except Exception as e:
mismatches.append({
"chain": chain_id,
"contract": addr["contract_name"],
"error": str(e),
})
return len(mismatches) == 0, mismatches
3.3 跨链合约解析工作流
跨链合约解析流程:
DApp 需要调用 cosmoshub-4 上的 token 合约
|
1. 查询 MSG Chain 上的 address_map 合约
| request: { resolve_address:
| { chain_id: "cosmoshub-4",
| contract_name: "token" } }
|
2. 返回: cosmos1xyz789...
|
3. 使用解析的地址构造跨链 IBC 消息
| IBC 数据包:
| { action: "transfer",
| contract: "cosmos1xyz789...",
| amount: "1000000" }
|
4. 通过 IBC 通道发送到 Cosmos Hub
|
5. Cosmos Hub 上的合约接收并执行
3.3.1 TypeScript 解析调用
// X-MSG-Stub=true: address-resolution-usage.ts
import { CrossChainResolver, ChainEndpoint } from "./cross-chain-resolver";
async function demonstrateResolution() {
const endpoints: ChainEndpoint[] = [
{
chainId: "msg-chain-1",
rpc: "https://rpc.msgchain.org:26657",
addressMapContract: "msg1addressmapcontract...",
},
{
chainId: "cosmoshub-4",
rpc: "https://rpc.cosmoshub-4.com:26657",
addressMapContract: "cosmos1addressmapcontract...",
},
{
chainId: "osmosis-1",
rpc: "https://rpc.osmosis.zone:26657",
addressMapContract: "osmo1addressmapcontract...",
},
];
const resolver = new CrossChainResolver(endpoints);
const msgChainToken = await resolver.resolveAddress(
"msg-chain-1",
"cosmoshub-4",
"token"
);
console.log(`Token on Cosmos Hub: ${msgChainToken}`);
const tokenOnOsmosis = await resolver.resolveAddress(
"msg-chain-1", "osmosis-1", "token"
);
console.log(`Token on Osmosis: ${tokenOnOsmosis}`);
const allOnCosmos = await resolver.getAllAddresses("cosmoshub-4");
console.log("All registered addresses on Cosmos Hub:", allOnCosmos);
const { consistent, mismatches } =
await resolver.verifyCrossChainConsistency();
if (consistent) {
console.log("All cross-chain address mappings are consistent!");
} else {
console.error("Found inconsistencies:", mismatches);
}
}
demonstrateResolution().catch(console.error);
4. 跨链治理同步
4.1 多链治理提案格式
4.1.1 提案数据结构
多链治理提案结构:
┌─────────────────────────────────────────────┐
│ MultiChainProposal │
│ ┌───────────────────────────────────────┐ │
│ │ 元数据 │ │
│ │ - title: "Upgrade Token v2" │ │
│ │ - description: "..." │ │
│ │ - author: "msg1author..." │ │
│ │ - created_at: 1712345678 │ │
│ │ - voting_period: 604800 (7天) │ │
│ └───────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ 目标链列表 │ │
│ │ - msg-chain-1 │ │
│ │ - cosmoshub-4 │ │
│ │ - osmosis-1 │ │
│ └───────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ Actions (每条链独立) │ │
│ │ │ │
│ │ [0] chain: msg-chain-1 │ │
│ │ msgs: [...CosmosMsg] │ │
│ │ │ │
│ │ [1] chain: cosmoshub-4 │ │
│ │ pre_condition: "ibc:packet#42" │ │
│ │ msgs: [...CosmosMsg] │ │
│ │ │ │
│ │ [2] chain: osmosis-1 │ │
│ │ depends_on: [1] │ │
│ │ msgs: [...CosmosMsg] │ │
│ └───────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────┐ │
│ │ 执行条件 │ │
│ │ - quorum: 0.4 (40%) │ │
│ │ - threshold: 0.5 (50%) │ │
│ │ - min_voting_power: "1000000" │ │
│ └───────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
4.1.2 JSON 提案格式
{
"title": "Upgrade Token Contract to v2 Across MSG Chain and Cosmos Hub",
"description": "This proposal upgrades the MSG Token contract to version 2.0.0, adding cross-chain transfer capabilities and improved fee distribution. The upgrade must be executed on MSG Chain first, then relayed to Cosmos Hub.",
"author": "msg1authoraddress...",
"created_at": "1712345678",
"voting_period_seconds": 604800,
"chains": ["msg-chain-1", "cosmoshub-4"],
"actions": [
{
"chain": "msg-chain-1",
"order": 0,
"msg": {
"migrate": {
"contract_addr": "msg1tokencontract...",
"code_id": 8,
"msg": "{\"upgrade_version\": \"2.0.0\", \"new_feature\": \"cross_chain_transfer\"}"
}
},
"pre_conditions": []
},
{
"chain": "cosmoshub-4",
"order": 1,
"msg": {
"migrate": {
"contract_addr": "cosmos1tokencontract...",
"code_id": 15,
"msg": "{\"upgrade_version\": \"2.0.0\", \"new_feature\": \"cross_chain_transfer\"}"
}
},
"pre_conditions": [
{
"type": "ibc_packet_received",
"params": {
"source_chain": "msg-chain-1",
"sequence": 42
}
}
]
}
],
"execution_config": {
"execution_mode": "sequential",
"timeout_seconds": 86400,
"rollback_on_failure": true,
"require_all_confirmations": true
},
"voting_config": {
"quorum": "0.40",
"threshold": "0.50",
"min_voting_power": "1000000000000000000"
},
"metadata": {
"source_code_url": "https://github.com/msgchain/token-contract/releases/v2.0.0",
"audit_report_url": "https://audits.msgchain.org/token-v2-report.pdf",
"discussion_url": "https://forum.msgchain.org/t/upgrade-token-v2/42"
}
}
4.1.3 TypeScript 提案构建器
// X-MSG-Stub=true: governance-proposal.ts
interface ChainAction {
chain: string;
order: number;
msg: Record<string, unknown>;
pre_conditions: Array<{
type: string;
params: Record<string, unknown>;
}>;
}
interface ExecutionConfig {
execution_mode: "sequential" | "parallel" | "independent";
timeout_seconds: number;
rollback_on_failure: boolean;
require_all_confirmations: boolean;
}
interface VotingConfig {
quorum: string;
threshold: string;
min_voting_power: string;
}
interface MultiChainProposal {
title: string;
description: string;
author: string;
created_at: string;
voting_period_seconds: number;
chains: string[];
actions: ChainAction[];
execution_config: ExecutionConfig;
voting_config: VotingConfig;
metadata: Record<string, string>;
}
interface ProposalValidationResult {
valid: boolean;
errors: string[];
warnings: string[];
}
class MultiChainGovernanceProposal {
private proposal: MultiChainProposal;
constructor(proposal: Partial<MultiChainProposal>) {
this.proposal = {
title: proposal.title || "",
description: proposal.description || "",
author: proposal.author || "",
created_at: proposal.created_at || Math.floor(Date.now() / 1000).toString(),
voting_period_seconds: proposal.voting_period_seconds || 604800,
chains: proposal.chains || [],
actions: proposal.actions || [],
execution_config: proposal.execution_config || {
execution_mode: "sequential",
timeout_seconds: 86400,
rollback_on_failure: true,
require_all_confirmations: true,
},
voting_config: proposal.voting_config || {
quorum: "0.40",
threshold: "0.50",
min_voting_power: "1000000000000000000",
},
metadata: proposal.metadata || {},
};
}
static createUpgradeProposal(
title: string,
description: string,
author: string,
chainUpgrades: Array<{
chain: string;
contractAddress: string;
newCodeId: number;
migrateMsg: Record<string, unknown>;
}>
): MultiChainGovernanceProposal {
const actions: ChainAction[] = chainUpgrades.map((upgrade, index) => ({
chain: upgrade.chain,
order: index,
msg: {
migrate: {
contract_addr: upgrade.contractAddress,
code_id: upgrade.newCodeId,
msg: JSON.stringify(upgrade.migrateMsg),
},
},
pre_conditions:
index === 0
? []
: [
{
type: "previous_chain_executed",
params: {
chain: chainUpgrades[index - 1].chain,
},
},
],
}));
return new MultiChainGovernanceProposal({
title,
description,
author,
chains: chainUpgrades.map((u) => u.chain),
actions,
created_at: Math.floor(Date.now() / 1000).toString(),
});
}
static createParameterChangeProposal(
title: string,
description: string,
author: string,
chainChanges: Array<{
chain: string;
contractAddress: string;
paramChanges: Record<string, unknown>;
}>
): MultiChainGovernanceProposal {
const actions: ChainAction[] = chainChanges.map((change) => ({
chain: change.chain,
order: 0,
msg: {
execute: {
contract_addr: change.contractAddress,
msg: {
update_config: change.paramChanges,
},
},
},
pre_conditions: [],
}));
return new MultiChainGovernanceProposal({
title,
description,
author,
chains: chainChanges.map((c) => c.chain),
actions,
});
}
addAction(action: ChainAction): void {
const existingIndex = this.proposal.actions.findIndex(
(a) => a.chain === action.chain && a.order === action.order
);
if (existingIndex >= 0) {
this.proposal.actions[existingIndex] = action;
} else {
this.proposal.actions.push(action);
}
this.proposal.chains = [
...new Set(this.proposal.actions.map((a) => a.chain)),
];
}
validate(): ProposalValidationResult {
const errors: string[] = [];
const warnings: string[] = [];
if (!this.proposal.title) errors.push("Proposal title is required");
if (!this.proposal.description) errors.push("Proposal description is required");
if (!this.proposal.author) errors.push("Proposal author is required");
if (this.proposal.actions.length === 0) errors.push("At least one action is required");
const chainsInActions = new Set(this.proposal.actions.map((a) => a.chain));
if (chainsInActions.size === 0) errors.push("No chains specified in actions");
for (const chain of chainsInActions) {
if (!chain.match(/^[a-z0-9-]+$/)) {
errors.push(`Invalid chain ID format: ${chain}`);
}
}
const seen = new Set<string>();
for (const action of this.proposal.actions) {
const key = `${action.chain}:${action.order}`;
if (seen.has(key)) errors.push(`Duplicate action order ${action.order} for chain ${action.chain}`);
seen.add(key);
}
if (this.proposal.voting_period_seconds > 1209600) {
warnings.push("Voting period > 14 days may delay time-sensitive upgrades");
}
if (
this.proposal.execution_config.execution_mode === "parallel" &&
this.proposal.actions.some((a) => a.pre_conditions.length > 0)
) {
warnings.push("Parallel execution mode with pre-conditions may cause coordination issues");
}
return { valid: errors.length === 0, errors, warnings };
}
toJSON(): string {
return JSON.stringify(this.proposal, null, 2);
}
static fromJSON(json: string): MultiChainGovernanceProposal {
return new MultiChainGovernanceProposal(JSON.parse(json));
}
}
export {
MultiChainProposal, ChainAction, ExecutionConfig, VotingConfig,
MultiChainGovernanceProposal, ProposalValidationResult,
};
4.1.4 Python 提案工具
"""
X-MSG-Stub=true: governance_proposal.py
"""
import json
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass, field, asdict
@dataclass
class PreCondition:
type: str
params: Dict
@dataclass
class ChainAction:
chain: str
order: int
msg: Dict
pre_conditions: List[PreCondition] = field(default_factory=list)
@dataclass
class ExecutionConfig:
execution_mode: str = "sequential"
timeout_seconds: int = 86400
rollback_on_failure: bool = True
require_all_confirmations: bool = True
@dataclass
class VotingConfig:
quorum: str = "0.40"
threshold: str = "0.50"
min_voting_power: str = "1000000000000000000"
@dataclass
class MultiChainProposal:
title: str
description: str
author: str
chains: List[str] = field(default_factory=list)
actions: List[ChainAction] = field(default_factory=list)
execution_config: ExecutionConfig = field(default_factory=ExecutionConfig)
voting_config: VotingConfig = field(default_factory=VotingConfig)
metadata: Dict[str, str] = field(default_factory=dict)
created_at: str = field(
default_factory=lambda: str(int(datetime.now().timestamp()))
)
voting_period_seconds: int = 604800
def to_json(self, indent: int = 2) -> str:
return json.dumps(asdict(self), indent=indent, ensure_ascii=False)
@classmethod
def from_json(cls, raw: str) -> "MultiChainProposal":
data = json.loads(raw)
data["actions"] = [ChainAction(**a) for a in data.get("actions", [])]
if "execution_config" in data:
data["execution_config"] = ExecutionConfig(**data["execution_config"])
if "voting_config" in data:
data["voting_config"] = VotingConfig(**data["voting_config"])
return cls(**data)
def validate(self) -> Tuple[bool, List[str], List[str]]:
errors = []
warnings = []
if not self.title:
errors.append("Title is required")
if not self.description:
errors.append("Description is required")
if not self.author:
errors.append("Author is required")
if not self.actions:
errors.append("At least one action required")
chain_set = set()
for action in self.actions:
chain_set.add(action.chain)
if not action.chain or not isinstance(action.chain, str):
errors.append(f"Invalid chain: {action.chain}")
if not action.msg:
errors.append(f"Action for {action.chain} has no msg")
if not chain_set:
errors.append("No target chains")
if self.voting_period_seconds > 1209600:
warnings.append("Voting period > 14 days")
return len(errors) == 0, errors, warnings
def estimate_execution_time(self) -> timedelta:
base = timedelta(seconds=60)
if self.execution_config.execution_mode == "sequential":
return base * len(self.actions)
return base
def create_upgrade_proposal(
title: str, description: str, author: str, upgrades: List[Dict],
) -> MultiChainProposal:
actions = []
for i, upgrade in enumerate(upgrades):
action = ChainAction(
chain=upgrade["chain"],
order=i,
msg={
"migrate": {
"contract_addr": upgrade["contract_address"],
"code_id": upgrade["new_code_id"],
"msg": json.dumps(upgrade.get("migrate_msg", {})),
}
},
pre_conditions=(
[] if i == 0 else [
PreCondition(
type="previous_chain_executed",
params={"chain": upgrades[i - 1]["chain"]},
)
]
),
)
actions.append(action)
return MultiChainProposal(
title=title,
description=description,
author=author,
chains=[u["chain"] for u in upgrades],
actions=actions,
)
4.2 IBC 中继治理消息
4.2.1 治理消息中继流程
治理消息中继流程:
MSG Chain Cosmos Hub
| |
| 1. 治理提案通过 |
| (达到法定 quorum) |
| |
| 2. 构造 IBC 治理数据包 |
| Governance Middleware |
| Packet: source=msg-chain |
| Actions: migrate token v2 |
| |
| 3. Relayer 转发 IBC 数据包 |
| --------------------------------->|
| |
| 4. 验证治理数据包 |
| Verify: Proposal,|
| Source, Timestamp|
| |
| 5. 执行治理操作 |
| migrate token v2 |
| Done |
| |
| 6. 确认回执 |
| <---------------------------------|
| |
v v
Proposal #1 Executed Proposal #1 Executed
4.2.2 TypeScript 治理中继客户端
// X-MSG-Stub=true: governance-relay.ts
import { SigningStargateClient } from "@cosmjs/stargate";
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing";
import { toUtf8 } from "@cosmjs/encoding";
import { MsgExecuteContract } from "cosmjs-types/cosmwasm/wasm/v1/tx";
import { MultiChainProposal } from "./governance-proposal";
interface GovernanceRelayConfig {
sourceChain: string;
sourceRpc: string;
targetChain: string;
targetRpc: string;
governanceContract: string;
}
interface RelayResult {
proposalId: string;
sourceTxHash: string;
ibcSequence?: number;
targetTxHash?: string;
status: "pending" | "relayed" | "executed" | "failed";
error?: string;
}
class GovernanceRelayer {
private config: GovernanceRelayConfig;
private sourceClient: SigningStargateClient | null = null;
private targetClient: SigningStargateClient | null = null;
constructor(config: GovernanceRelayConfig) {
this.config = config;
}
async connect(mnemonic: string): Promise<void> {
const sourceWallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { prefix: "msg" });
const targetWallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { prefix: "cosmos" });
this.sourceClient = await SigningStargateClient.connectWithSigner(
this.config.sourceRpc, sourceWallet
);
this.targetClient = await SigningStargateClient.connectWithSigner(
this.config.targetRpc, targetWallet
);
}
async submitProposalOnSource(proposal: MultiChainProposal, depositAmount: string): Promise<string> {
if (!this.sourceClient) throw new Error("Not connected");
const submitMsg = {
submit_proposal: {
title: proposal.title,
description: proposal.description,
actions: JSON.stringify(proposal.actions),
execution_config: JSON.stringify(proposal.execution_config),
chains: proposal.chains,
},
};
const msg = {
typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract",
value: MsgExecuteContract.fromPartial({
sender: "",
contract: this.config.governanceContract,
msg: toUtf8(JSON.stringify(submitMsg)),
funds: [{ denom: "umsg", amount: depositAmount }],
}),
};
const fee = { amount: [{ denom: "umsg", amount: "50000" }], gas: "500000" };
const result = await this.sourceClient.signAndBroadcast("", [msg], fee, "");
return result.transactionHash;
}
async relayProposalToChain(
proposalId: string, targetChain: string,
actions: Array<{ chain: string; msg: Record<string, unknown> }>
): Promise<RelayResult> {
const targetActions = actions.filter((a) => a.chain === targetChain);
if (targetActions.length === 0) {
return { proposalId, sourceTxHash: "", status: "failed", error: `No actions for chain ${targetChain}` };
}
const executeMsg = {
receive_governance_relay: {
proposal_id: proposalId,
source_chain: this.config.sourceChain,
actions: targetActions,
proof: "",
},
};
if (!this.targetClient) throw new Error("Target client not connected");
const msg = {
typeUrl: "/cosmwasm.wasm.v1.MsgExecuteContract",
value: MsgExecuteContract.fromPartial({
sender: "",
contract: this.config.governanceContract,
msg: toUtf8(JSON.stringify(executeMsg)),
}),
};
const fee = { amount: [{ denom: "uatom", amount: "50000" }], gas: "500000" };
try {
const result = await this.targetClient.signAndBroadcast("", [msg], fee, "");
return { proposalId, sourceTxHash: "", targetTxHash: result.transactionHash, status: "executed" };
} catch (e: any) {
return { proposalId, sourceTxHash: "", status: "failed", error: e.message };
}
}
async execute(proposal: MultiChainProposal, depositAmount: string): Promise<RelayResult[]> {
const results: RelayResult[] = [];
const sourceTxHash = await this.submitProposalOnSource(proposal, depositAmount);
console.log(`Proposal submitted, tx: ${sourceTxHash}`);
results.push({ proposalId: proposal.title, sourceTxHash, status: "pending" });
for (const chain of proposal.chains) {
if (chain === this.config.sourceChain) continue;
const relayResult = await this.relayProposalToChain(proposal.title, chain, proposal.actions);
results.push(relayResult);
console.log(`Relay to ${chain}: ${relayResult.status}`);
}
return results;
}
}
export { GovernanceRelayer, GovernanceRelayConfig, RelayResult };
4.2.3 Python 治理中继
"""
X-MSG-Stub=true: governance_relay.py
"""
import json
import time
from typing import Dict, List, Optional
from dataclasses import dataclass
import requests
@dataclass
class GovernanceRelayResult:
chain: str
proposal_id: str
status: str
source_tx: Optional[str] = None
target_tx: Optional[str] = None
error: Optional[str] = None
class GovernanceRelayClient:
def __init__(self, source_chain: str, source_rpc: str, target_chain: str,
target_rpc: str, governance_contract: str):
self.source_chain = source_chain
self.source_rpc = source_rpc
self.target_chain = target_chain
self.target_rpc = target_rpc
self.governance_contract = governance_contract
def _lcd_url(self, rpc: str) -> str:
return rpc.replace(":26657", ":1317")
def _query_contract(self, rpc: str, contract: str, msg: Dict) -> Dict:
lcd = self._lcd_url(rpc)
url = f"{lcd}/cosmwasm/wasm/v1/contract/{contract}/smart/{json.dumps(msg)}"
resp = requests.get(url)
resp.raise_for_status()
return resp.json()["data"]
def check_proposal_status(self, proposal_id: str) -> Dict:
return self._query_contract(
self.source_rpc, self.governance_contract,
{"proposal": {"proposal_id": proposal_id}},
)
def verify_relay_proof(self, proposal_id: str, source_chain: str, ibc_sequence: int) -> bool:
try:
proof = self._query_contract(
self.target_rpc, self.governance_contract,
{
"verify_relay": {
"proposal_id": proposal_id,
"source_chain": source_chain,
"ibc_sequence": ibc_sequence,
}
},
)
return proof.get("verified", False)
except Exception:
return False
def relay_governance_packet(self, proposal_id: str, actions: List[Dict], private_key: str) -> GovernanceRelayResult:
packet = {
"governance_relay": {
"proposal_id": proposal_id,
"source_chain": self.source_chain,
"actions": actions,
"timestamp": int(time.time()),
"nonce": int(time.time() * 1000),
}
}
return GovernanceRelayResult(
chain=self.target_chain, proposal_id=proposal_id, status="prepared",
)
def execute_on_target_chain(self, proposal_id: str, actions: List[Dict], proof: str) -> GovernanceRelayResult:
execute_msg = {
"receive_governance_relay": {
"proposal_id": proposal_id,
"source_chain": self.source_chain,
"actions": actions,
"proof": proof,
}
}
return GovernanceRelayResult(
chain=self.target_chain, proposal_id=proposal_id, status="prepared_for_execution",
)
class MultiChainGovernanceCoordinator:
def __init__(self):
self.relayers: Dict[str, GovernanceRelayClient] = {}
def add_relayer(self, chain: str, relayer: GovernanceRelayClient):
self.relayers[chain] = relayer
def coordinate(self, proposal_id: str, target_actions: Dict[str, List[Dict]]) -> List[GovernanceRelayResult]:
results = []
for chain, actions in target_actions.items():
relayer = self.relayers.get(chain)
if not relayer:
results.append(GovernanceRelayResult(
chain=chain, proposal_id=proposal_id, status="failed",
error=f"No relayer configured for {chain}",
))
continue
result = relayer.relay_governance_packet(proposal_id, actions, "")
results.append(result)
return results
4.3 投票同步
4.3.1 跨链投票协调
投票同步策略:
方案 A: 单链投票 + IBC 中继执行 (推荐)
MSG Chain (治理主链) Cosmos Hub (治理从链)
| |
| 提案提交 |
| + 投票期开放 |
| |
| 用户投票 |
| + Yes: 1,000,000 MSG |
| + No: 200,000 MSG |
| + Abstain: 100,000 MSG |
| + Veto: 50,000 MSG |
| |
| 投票期结束 |
| quorum: 13.5% |
| threshold: 80% |
| -> PROPOSAL_PASSED |
| |
| IBC 中继: 提案结果 |
| ---------------------------> |
| 自动执行相同操作 |
4.3.2 TypeScript 投票同步
// X-MSG-Stub=true: vote-sync.ts
interface Vote {
voter: string;
proposalId: string;
option: "yes" | "no" | "abstain" | "veto";
weight: string;
timestamp: number;
chainId: string;
}
interface AggregatedVoteResult {
proposalId: string;
chainResults: Map<string, {
totalVotingPower: string;
yes: string;
no: string;
abstain: string;
veto: string;
quorum: number;
threshold: number;
passed: boolean;
}>;
overallPassed: boolean;
}
class CrossChainVoteAggregator {
private votes: Map<string, Vote[]> = new Map();
recordVote(vote: Vote): void {
const key = `${vote.proposalId}:${vote.chainId}`;
if (!this.votes.has(key)) this.votes.set(key, []);
this.votes.get(key)!.push(vote);
}
getChainVotes(proposalId: string, chainId: string): Vote[] {
return this.votes.get(`${proposalId}:${chainId}`) || [];
}
aggregate(
proposalId: string,
chainConfigs: Map<string, { totalVotingPower: string; quorum: number; threshold: number }>
): AggregatedVoteResult {
const chainResults = new Map();
let allPassed = true;
for (const [chainId, config] of chainConfigs) {
const votes = this.getChainVotes(proposalId, chainId);
let yes = BigInt(0), no = BigInt(0), abstain = BigInt(0), veto = BigInt(0);
const seenVoters = new Set<string>();
for (const vote of votes) {
if (seenVoters.has(vote.voter)) continue;
seenVoters.add(vote.voter);
const weight = BigInt(vote.weight);
switch (vote.option) {
case "yes": yes += weight; break;
case "no": no += weight; break;
case "abstain": abstain += weight; break;
case "veto": veto += weight; break;
}
}
const totalPower = BigInt(config.totalVotingPower);
const totalVoted = yes + no + abstain + veto;
const turnout = Number((totalVoted * BigInt(10000)) / totalPower) / 10000;
const nonAbstain = yes + no + veto;
const approvalRate = nonAbstain > BigInt(0)
? Number((yes * BigInt(10000)) / nonAbstain) / 10000 : 0;
const passed = turnout >= config.quorum && approvalRate >= config.threshold;
chainResults.set(chainId, {
totalVotingPower: config.totalVotingPower,
yes: yes.toString(), no: no.toString(),
abstain: abstain.toString(), veto: veto.toString(),
quorum: config.quorum, threshold: config.threshold, passed,
});
if (!passed) allPassed = false;
}
return { proposalId, chainResults, overallPassed: allPassed };
}
}
export { Vote, AggregatedVoteResult, CrossChainVoteAggregator };
4.4 执行协调
4.4.1 执行策略
顺序执行 (Sequential):
MSG Chain --1 execute --> confirmed
|
2 relay --> Cosmos Hub --3 execute --> confirmed
|
4 relay --> Osmosis --5 execute --> confirmed
并行执行 (Parallel):
MSG Chain --1 execute --> confirmed
|
2 relay --+--> Cosmos Hub --3 execute --> confirmed
|
+--> Osmosis --3 execute --> confirmed
4.4.2 TypeScript 执行协调器
// X-MSG-Stub=true: execution-coordinator.ts
import { MultiChainProposal, ChainAction } from "./governance-proposal";
type ExecutionStatus = "pending" | "executing" | "confirmed" | "failed" | "timeout";
interface ChainExecutionState {
chain: string;
action: ChainAction;
status: ExecutionStatus;
txHash?: string;
error?: string;
startTime?: number;
confirmTime?: number;
attempts: number;
}
interface ExecutionReport {
proposalId: string;
overallStatus: "success" | "partial" | "failed";
states: ChainExecutionState[];
startTime: number;
endTime?: number;
}
class ExecutionCoordinator {
private states: Map<string, ChainExecutionState> = new Map();
private proposal: MultiChainProposal;
private maxAttempts = 3;
private retryDelayMs = 5000;
constructor(proposal: MultiChainProposal) {
this.proposal = proposal;
for (const action of proposal.actions) {
this.states.set(action.chain, {
chain: action.chain, action, status: "pending", attempts: 0,
});
}
}
async execute(): Promise<ExecutionReport> {
const startTime = Date.now();
const report: ExecutionReport = { proposalId: this.proposal.title, overallStatus: "success", states: [], startTime };
const mode = this.proposal.execution_config.execution_mode;
if (mode === "sequential") await this.executeSequential();
else if (mode === "parallel") await this.executeParallel();
else await this.executeIndependent();
const allConfirmed = Array.from(this.states.values()).every((s) => s.status === "confirmed");
const anyFailed = Array.from(this.states.values()).some((s) => s.status === "failed");
report.overallStatus = allConfirmed ? "success" : anyFailed ? "failed" : "partial";
report.endTime = Date.now();
report.states = Array.from(this.states.values());
return report;
}
private async executeSequential(): Promise<void> {
const sorted = this.proposal.actions.sort((a, b) => a.order - b.order);
for (const action of sorted) {
const state = this.states.get(action.chain)!;
await this.executeWithRetry(state);
if (state.status === "failed" && this.proposal.execution_config.rollback_on_failure) break;
}
}
private async executeParallel(): Promise<void> {
await Promise.all(this.proposal.actions.map((a) => {
const state = this.states.get(a.chain)!;
return this.executeWithRetry(state);
}));
}
private async executeIndependent(): Promise<void> {
await Promise.all(this.proposal.actions.map((a) => {
const state = this.states.get(a.chain)!;
return this.executeWithRetry(state);
}));
}
private async executeWithRetry(state: ChainExecutionState): Promise<void> {
state.status = "executing";
state.startTime = Date.now();
while (state.attempts < this.maxAttempts) {
state.attempts++;
try {
state.txHash = await this.sendExecute(state.action);
state.status = "confirmed";
state.confirmTime = Date.now();
console.log(`[${state.chain}] Executed (attempt ${state.attempts}): ${state.txHash}`);
return;
} catch (e: any) {
state.error = e.message;
console.error(`[${state.chain}] Attempt ${state.attempts} failed: ${e.message}`);
if (state.attempts < this.maxAttempts) {
await this.delay(this.retryDelayMs * state.attempts);
}
}
}
state.status = "failed";
}
private async sendExecute(action: ChainAction): Promise<string> {
await this.delay(2000);
return `tx_${action.chain}_${Date.now()}`;
}
private delay(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}
}
export { ExecutionCoordinator, ChainExecutionState, ExecutionReport, ExecutionStatus };
4.4.3 Python 执行协调
"""
X-MSG-Stub=true: execution_coordinator.py
"""
import asyncio
import time
from enum import Enum
from typing import Dict, List, Optional
from dataclasses import dataclass
class ExecutionStatus(Enum):
PENDING = "pending"
EXECUTING = "executing"
CONFIRMED = "confirmed"
FAILED = "failed"
TIMEOUT = "timeout"
@dataclass
class ChainExecutionState:
chain: str
action: Dict
status: ExecutionStatus = ExecutionStatus.PENDING
tx_hash: Optional[str] = None
error: Optional[str] = None
start_time: Optional[float] = None
confirm_time: Optional[float] = None
attempts: int = 0
@dataclass
class ExecutionReport:
proposal_id: str
overall_status: str
states: List[ChainExecutionState]
start_time: float
end_time: Optional[float] = None
class ExecutionCoordinator:
def __init__(self, actions: List[Dict], execution_mode: str = "sequential",
max_attempts: int = 3, retry_delay_ms: int = 5000):
self.states: Dict[str, ChainExecutionState] = {}
self.execution_mode = execution_mode
self.max_attempts = max_attempts
self.retry_delay_ms = retry_delay_ms
for action in actions:
self.states[action["chain"]] = ChainExecutionState(chain=action["chain"], action=action)
async def _execute_single(self, state: ChainExecutionState) -> None:
state.status = ExecutionStatus.EXECUTING
state.start_time = time.time()
while state.attempts < self.max_attempts:
state.attempts += 1
try:
result = await self._send_execute(state.action)
state.tx_hash = result
state.status = ExecutionStatus.CONFIRMED
state.confirm_time = time.time()
print(f"[{state.chain}] Executed (attempt {state.attempts}): {result}")
return
except Exception as e:
state.error = str(e)
print(f"[{state.chain}] Attempt {state.attempts} failed: {e}")
if state.attempts < self.max_attempts:
await asyncio.sleep(self.retry_delay_ms * state.attempts / 1000)
state.status = ExecutionStatus.FAILED
async def _send_execute(self, action: Dict) -> str:
await asyncio.sleep(2)
return f"tx_{action['chain']}_{int(time.time() * 1000)}"
async def execute(self) -> ExecutionReport:
start_time = time.time()
if self.execution_mode == "sequential":
sorted_actions = sorted(self.states.values(), key=lambda s: s.action.get("order", 0))
for state in sorted_actions:
await self._execute_single(state)
if state.status == ExecutionStatus.FAILED:
break
elif self.execution_mode == "parallel":
await asyncio.gather(*[self._execute_single(state) for state in self.states.values()])
else:
for state in self.states.values():
await self._execute_single(state)
all_confirmed = all(s.status == ExecutionStatus.CONFIRMED for s in self.states.values())
any_failed = any(s.status == ExecutionStatus.FAILED for s in self.states.values())
return ExecutionReport(
proposal_id=list(self.states.keys())[0],
overall_status="success" if all_confirmed else "failed" if any_failed else "partial",
states=list(self.states.values()),
start_time=start_time,
end_time=time.time(),
)
5. IBC集成
5.1 IBC 连接建立
5.1.1 连接概述
MSG Chain 与目标链之间的 IBC 连接是跨链治理同步的基础设施:
IBC 连接层次结构:
Layer 4: dApp 层 - 跨链治理 / 地址解析 / 代币转移 / 合约调用
Layer 3: 应用层 - ICS-20 | ICS-27 | 治理中间件 | 自定义
转账 | 跨链账户 | (治理同步) | 应用
Layer 2: 传输层 - 数据包 | 确认/超时 | 序列号 | 通道管理
Layer 1: 轻客户端层 - Tendermint LC | Dilithium-5 LC (自定义)
5.1.2 CLI 连接建立
# X-MSG-Stub=true: 建立 IBC 连接
# ---- 阶段 1: 创建 IBC 轻客户端 ----
# 在 MSG Chain 上创建 Cosmos Hub 的轻客户端
msgd tx ibc client create \
--client-type tendermint \
--chain-id cosmoshub-4 \
--node https://rpc.cosmoshub-4.com:26657 \
--trusting-period 336h \
--unbonding-period 504h \
--max-clock-drift 5s \
--from gov-relayer-key \
--gas-prices 1000000000umsg \
--chain-id msg-chain-1
# 在 Cosmos Hub 上创建 MSG Chain 的轻客户端
gaiad tx ibc client create \
--client-type tendermint \
--chain-id msg-chain-1 \
--node https://rpc.msgchain.org:26657 \
--trusting-period 336h \
--unbonding-period 504h \
--max-clock-drift 5s \
--from gov-relayer-key \
--gas-prices 0.025uatom \
--chain-id cosmoshub-4
# ---- 阶段 2: Connection 握手 ----
# MSG Chain 发起连接
msgd tx ibc connection open-init \
--client-id 07-tendermint-0 \
--counterparty-client-id 07-tendermint-0 \
--from gov-relayer-key \
--gas-prices 1000000000umsg \
--chain-id msg-chain-1
# Cosmos Hub 响应连接
gaiad tx ibc connection open-try \
--connection-id connection-0 \
--client-id 07-tendermint-0 \
--counterparty-client-id 07-tendermint-0 \
--counterparty-connection-id connection-0 \
--from gov-relayer-key \
--gas-prices 0.025uatom \
--chain-id cosmoshub-4
# MSG Chain 确认
msgd tx ibc connection open-ack \
--connection-id connection-0 \
--counterparty-connection-id connection-0 \
--version 1 \
--from gov-relayer-key \
--gas-prices 1000000000umsg \
--chain-id msg-chain-1
# Cosmos Hub 最终确认
gaiad tx ibc connection open-confirm \
--connection-id connection-0 \
--counterparty-connection-id connection-0 \
--from gov-relayer-key \
--gas-prices 0.025uatom \
--chain-id cosmoshub-4
# ---- 阶段 3: Channel 创建 ----
# 创建治理通道 (有序通道)
msgd tx ibc channel open-init \
--port governance \
--channel-ordering ordered \
--connection-id connection-0 \
--version "gov-ics-v1" \
--from gov-relayer-key \
--gas-prices 1000000000umsg \
--chain-id msg-chain-1
# Cosmos Hub 创建对应通道
gaiad tx ibc channel open-try \
--port governance \
--channel-ordering ordered \
--connection-id connection-0 \
--counterparty-port governance \
--counterparty-channel-id channel-0 \
--version "gov-ics-v1" \
--from gov-relayer-key \
--gas-prices 0.025uatom \
--chain-id cosmoshub-4
5.2 ICS-20 代币转移
用于跨链治理中的代币分配和费用支付。详细内容请参考《跨链桥接与IBC指南.md》。
# X-MSG-Stub=true: 治理转账通道配置
# 创建治理专用的转账通道
msgd tx ibc channel open-init \
--port transfer \
--channel-ordering unordered \
--connection-id connection-0 \
--version "ics20-1" \
--from gov-relayer-key \
--gas-prices 1000000000umsg \
--chain-id msg-chain-1
5.3 ICS-27 跨链账户
ICS-27 (Interchain Accounts) 允许 MSG Chain 的控制账户在 Cosmos Hub 上执行交易。
ICS-27 跨链账户流程:
MSG Chain (Controller) Cosmos Hub (Host)
| |
| 1. register_interchain_account |
| ---------------------------------->|
| 2. 创建 ICA |
| <---------------------------------|
| 3. submit_tx { msgs: [...] } |
| ---------------------------------->|
| 4. 执行交易 |
| 5. 确认结果 |
| <---------------------------------|
5.3.1 TypeScript ICA 客户端
// X-MSG-Stub=true: interchain-accounts.ts
import { SigningStargateClient } from "@cosmjs/stargate";
interface ICARegistrationResult {
channelId: string;
interchainAccountAddress: string;
connectionId: string;
}
class InterchainAccountClient {
private controllerClient: SigningStargateClient;
private controllerAddress: string;
constructor(controllerClient: SigningStargateClient, controllerAddress: string) {
this.controllerClient = controllerClient;
this.controllerAddress = controllerAddress;
}
async registerAccount(connectionId: string, owner: string): Promise<ICARegistrationResult> {
const registerMsg = {
typeUrl: "/ibc.applications.interchain_accounts.controller.v1.MsgRegisterInterchainAccount",
value: { owner, connectionId, version: "ics27-1" },
};
const fee = { amount: [{ denom: "umsg", amount: "50000" }], gas: "300000" };
const result = await this.controllerClient.signAndBroadcast(
this.controllerAddress, [registerMsg], fee, "Register ICA"
);
return { channelId: "ica-channel-0", interchainAccountAddress: "cosmos1ica...", connectionId };
}
async submitGovernanceTx(channelId: string, remoteMsgs: Array<{ typeUrl: string; value: Uint8Array }>): Promise<string> {
const submitMsg = {
typeUrl: "/ibc.applications.interchain_accounts.controller.v1.MsgSendTx",
value: {
owner: this.controllerAddress,
channelId,
messages: remoteMsgs,
timeoutDuration: { seconds: 3600, nanos: 0 },
timeoutTimestamp: BigInt(Math.floor(Date.now() / 1000) + 3600) * BigInt(1_000_000_000),
},
};
const fee = { amount: [{ denom: "umsg", amount: "50000" }], gas: "500000" };
const result = await this.controllerClient.signAndBroadcast(
this.controllerAddress, [submitMsg], fee, "Submit ICA governance tx"
);
return result.transactionHash;
}
}
export { InterchainAccountClient, ICARegistrationResult };
5.4 自定义治理中间件
5.4.1 治理中间件架构
发送方 (MSG Chain) 接收方 (Cosmos Hub)
| |
| Governance App IBC 数据包 |
| Governance Middleware -----------> |
| IBC Core |
| |
| IBC Core |
| Governance Middleware |
| Governance App |
5.4.2 TypeScript 治理中间件
// X-MSG-Stub=true: governance-middleware.ts
import { toUtf8, fromUtf8 } from "@cosmjs/encoding";
import { createHash } from "crypto";
interface GovernancePacket {
proposalId: string;
sourceChain: string;
actions: Array<{ chain: string; msg: Record<string, unknown> }>;
timestamp: number;
nonce: number;
proposerSignature: string;
}
interface GovernanceAcknowledgement {
status: "accepted" | "rejected" | "pending";
proposalId: string;
targetChain: string;
executionTxHash?: string;
error?: string;
verified: boolean;
}
class GovernanceMiddleware {
private readonly VERSION = "gov-ics-v1";
validatePacket(packet: GovernancePacket, expectedSourceChain: string): boolean {
if (packet.sourceChain !== expectedSourceChain) {
console.error(`Source chain mismatch: ${packet.sourceChain} != ${expectedSourceChain}`);
return false;
}
if (!packet.proposalId) { console.error("Missing proposal_id"); return false; }
if (!packet.actions || packet.actions.length === 0) { console.error("No actions"); return false; }
if (!packet.proposerSignature) { console.error("Missing signature"); return false; }
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - packet.timestamp) > 3600) {
console.error("Packet timestamp is too old or in the future");
return false;
}
return true;
}
verifyProposalSignature(packet: GovernancePacket, expectedSigner: string): boolean {
const message = this.buildSignMessage(packet);
const hash = createHash("sha256").update(message).digest("hex");
return true; // placeholder for Dilithium-5 verify
}
buildSignMessage(packet: GovernancePacket): Uint8Array {
return toUtf8(JSON.stringify({
proposalId: packet.proposalId,
sourceChain: packet.sourceChain,
actions: packet.actions,
timestamp: packet.timestamp,
nonce: packet.nonce,
}));
}
createAcknowledgement(status: "accepted" | "rejected" | "pending", proposalId: string,
targetChain: string, options?: { executionTxHash?: string; error?: string }): GovernanceAcknowledgement {
return { status, proposalId, targetChain, executionTxHash: options?.executionTxHash, error: options?.error, verified: status === "accepted" };
}
}
export { GovernanceMiddleware, GovernancePacket, GovernanceAcknowledgement };
5.5 跨链查询
跨链查询流程:
MSG Chain Cosmos Hub
| |
| 1. 构建 IBC 查询数据包 |
| 2. IBC 数据包经 Relayer 转发 |
| -----------------------------> |
| 3. Cosmos Hub 接收并执行查询 |
| 4. 构造响应 |
| 5. IBC 确认回执 |
| <----------------------------- |
| 6. 解析查询结果 |
5.5.1 TypeScript 跨链查询
// X-MSG-Stub=true: cross-chain-query.ts
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
interface CrossChainQueryRequest {
chainId: string;
contractAddress: string;
query: Record<string, unknown>;
timeoutBlocks: number;
}
interface CrossChainQueryResponse {
success: boolean;
data?: Record<string, unknown>;
error?: string;
blockHeight: number;
}
class CrossChainQuerier {
private client: CosmWasmClient;
private sourceChain: string;
private ibcQueryContract: string;
constructor(client: CosmWasmClient, sourceChain: string, ibcQueryContract: string) {
this.client = client;
this.sourceChain = sourceChain;
this.ibcQueryContract = ibcQueryContract;
}
async queryRemoteContract(
targetChain: string, targetContract: string,
queryMsg: Record<string, unknown>,
channelId: string, timeoutBlocks: number = 5000
): Promise<CrossChainQueryResponse> {
const ibcQueryPacket = {
cross_chain_query: {
query_type: "wasm_contract",
chain_id: targetChain,
contract_address: targetContract,
query: queryMsg,
},
};
try {
const result = await this.client.queryContractSmart(this.ibcQueryContract, {
simulate_ibc_query: {
target_chain: targetChain,
target_contract: targetContract,
query: queryMsg,
},
});
return { success: true, data: result, blockHeight: 0 };
} catch (e: any) {
return { success: false, error: e.message, blockHeight: 0 };
}
}
async batchQueryRemote(queries: CrossChainQueryRequest[]): Promise<Map<string, CrossChainQueryResponse>> {
const results = new Map<string, CrossChainQueryResponse>();
for (const query of queries) {
const key = `${query.chainId}:${query.contractAddress}`;
const result = await this.queryRemoteContract(query.chainId, query.contractAddress, query.query, "channel-0", query.timeoutBlocks);
results.set(key, result);
}
return results;
}
}
export { CrossChainQuerier, CrossChainQueryRequest, CrossChainQueryResponse };
6. 多链部署工具
6.1 msg-chain-devkit CLI 架构
msg-chain-devkit multi-chain 命令树:
msg-chain-devkit multi-chain
deploy 部署合约到多链
wasm 部署 WASM 合约
instance 实例化合约
all 一键完整部署
manifest 部署清单管理
init 初始化清单
validate 验证清单
add-chain 添加链
remove-chain 移除链
address-map 地址映射管理
register 注册跨链地址
resolve 解析跨链地址
list 列出所有映射
verify 验证一致性
governance 治理同步
propose 提交多链提案
vote 投票
relay 中继提案到目标链
status 查看同步状态
verify 部署验证
all 验证所有链
chain 验证单链
contract 验证合约
config 配置管理
show 显示配置
set 设置配置
export 导出配置
import 导入配置
6.2 TypeScript CLI 实现
// X-MSG-Stub=true: cli-commands.ts
import { Command } from "commander";
import { MultiChainDeployment } from "./deployment-manifest";
import { MultiChainGovernanceProposal } from "./governance-proposal";
interface CLIOptions {
manifest: string;
chain?: string;
contract?: string;
wasm?: string;
admin?: string;
mnemonic?: string;
}
class MultiChainCLI {
private program: Command;
constructor() {
this.program = new Command();
this.setupCommands();
}
private setupCommands(): void {
this.program
.name("msg-chain-devkit")
.description("MSG Chain Multi-Chain Development Kit")
.version("1.0.0");
// Deploy commands
const deploy = this.program.command("deploy");
deploy
.command("all")
.description("Deploy contract to all configured chains")
.requiredOption("--wasm <path>", "Path to WASM file")
.requiredOption("--manifest <path>", "Path to manifest file")
.option("--admin <address>", "Contract admin address")
.action(async (options) => {
await this.deployAll(options);
});
deploy
.command("wasm")
.description("Upload WASM to a specific chain")
.requiredOption("--chain <id>", "Chain ID")
.requiredOption("--wasm <path>", "Path to WASM file")
.requiredOption("--manifest <path>", "Path to manifest file")
.action(async (options) => {
await this.uploadWasm(options);
});
deploy
.command("instance")
.description("Instantiate contract on a chain")
.requiredOption("--chain <id>", "Chain ID")
.requiredOption("--code-id <number>", "Code ID")
.requiredOption("--manifest <path>", "Path to manifest file")
.requiredOption("--label <text>", "Contract label")
.option("--admin <address>", "Admin address")
.action(async (options) => {
await this.instantiate(options);
});
// Manifest commands
const manifest = this.program.command("manifest");
manifest
.command("init")
.description("Initialize a new deployment manifest")
.requiredOption("--output <path>", "Output path")
.action(async (options) => {
await this.initManifest(options);
});
manifest
.command("validate")
.description("Validate a deployment manifest")
.requiredOption("--manifest <path>", "Path to manifest")
.action(async (options) => {
await this.validateManifest(options);
});
manifest
.command("add-chain")
.description("Add a chain to the manifest")
.requiredOption("--manifest <path>", "Path to manifest")
.requiredOption("--chain-id <id>", "Chain ID")
.requiredOption("--rpc <url>", "RPC URL")
.action(async (options) => {
await this.addChain(options);
});
// Address map commands
const addrMap = this.program.command("address-map");
addrMap
.command("register")
.description("Register a cross-chain address mapping")
.requiredOption("--manifest <path>", "Path to manifest")
.requiredOption("--chain <id>", "Target chain ID")
.requiredOption("--contract <name>", "Contract name")
.requiredOption("--address <addr>", "Contract address")
.action(async (options) => {
await this.registerAddress(options);
});
addrMap
.command("resolve")
.description("Resolve a cross-chain contract address")
.requiredOption("--manifest <path>", "Path to manifest")
.requiredOption("--chain <id>", "Target chain ID")
.requiredOption("--contract <name>", "Contract name")
.action(async (options) => {
await this.resolveAddress(options);
});
addrMap
.command("verify")
.description("Verify cross-chain address consistency")
.requiredOption("--manifest <path>", "Path to manifest")
.action(async (options) => {
await this.verifyAddresses(options);
});
// Governance commands
const gov = this.program.command("governance");
gov
.command("propose")
.description("Submit a multi-chain governance proposal")
.requiredOption("--manifest <path>", "Path to manifest")
.requiredOption("--proposal <path>", "Path to proposal JSON")
.action(async (options) => {
await this.submitProposal(options);
});
gov
.command("status")
.description("Check cross-chain governance status")
.requiredOption("--manifest <path>", "Path to manifest")
.requiredOption("--proposal-id <id>", "Proposal ID")
.action(async (options) => {
await this.checkGovernanceStatus(options);
});
// Verify commands
const verify = this.program.command("verify");
verify
.command("all")
.description("Verify deployment on all chains")
.requiredOption("--manifest <path>", "Path to manifest")
.action(async (options) => {
await this.verifyAll(options);
});
verify
.command("chain")
.description("Verify deployment on a specific chain")
.requiredOption("--manifest <path>", "Path to manifest")
.requiredOption("--chain <id>", "Chain ID")
.action(async (options) => {
await this.verifyChain(options);
});
}
private async deployAll(options: CLIOptions): Promise<void> {
console.log(`Deploying ${options.wasm} to all chains in ${options.manifest}...`);
// Implementation would use MultiChainDeployer
console.log("Deployment complete.");
}
private async uploadWasm(options: CLIOptions & { chain: string }): Promise<void> {
console.log(`Uploading ${options.wasm} to ${options.chain}...`);
console.log("Upload complete.");
}
private async instantiate(options: CLIOptions & { chain: string; codeId: string; label: string }): Promise<void> {
console.log(`Instantiating code ${options.codeId} on ${options.chain}...`);
console.log("Instantiation complete.");
}
private async initManifest(options: { output: string }): Promise<void> {
const defaultManifest = {
project: { name: "My Multi-Chain Project", version: "1.0.0", repository: "" },
deployments: [],
};
const fs = require("fs");
fs.writeFileSync(options.output, JSON.stringify(defaultManifest, null, 2));
console.log(`Initialized manifest at ${options.output}`);
}
private async validateManifest(options: { manifest: string }): Promise<void> {
const manifest = new MultiChainDeployment(options.manifest);
const errors = manifest.validate();
if (errors.length === 0) {
console.log("Manifest is valid.");
} else {
console.error("Manifest errors:");
errors.forEach((e) => console.error(` - ${e}`));
}
}
private async addChain(options: { manifest: string; chainId: string; rpc: string }): Promise<void> {
console.log(`Adding chain ${options.chainId} to manifest...`);
console.log("Chain added.");
}
private async registerAddress(options: { manifest: string; chain: string; contract: string; address: string }): Promise<void> {
console.log(`Registering ${options.chain}/${options.contract} -> ${options.address}...`);
console.log("Address registered.");
}
private async resolveAddress(options: { manifest: string; chain: string; contract: string }): Promise<void> {
console.log(`Resolving ${options.chain}/${options.contract}...`);
}
private async verifyAddresses(options: { manifest: string }): Promise<void> {
console.log("Verifying cross-chain address consistency...");
}
private async submitProposal(options: { manifest: string; proposal: string }): Promise<void> {
const fs = require("fs");
const proposalData = JSON.parse(fs.readFileSync(options.proposal, "utf-8"));
const proposal = MultiChainGovernanceProposal.fromJSON(JSON.stringify(proposalData));
const validation = proposal.validate();
if (!validation.valid) {
console.error("Proposal validation errors:");
validation.errors.forEach((e) => console.error(` - ${e}`));
return;
}
console.log("Submitting multi-chain governance proposal...");
console.log("Proposal submitted.");
}
private async checkGovernanceStatus(options: { manifest: string; proposalId: string }): Promise<void> {
console.log(`Checking status for proposal ${options.proposalId}...`);
}
private async verifyAll(options: { manifest: string }): Promise<void> {
console.log("Verifying deployment on all chains...");
}
private async verifyChain(options: { manifest: string; chain: string }): Promise<void> {
console.log(`Verifying deployment on ${options.chain}...`);
}
run(argv: string[]): void {
this.program.parse(argv);
}
}
export { MultiChainCLI };
6.3 Python CLI 实现
"""
X-MSG-Stub=true: cli_commands.py
"""
import json
import os
import sys
import argparse
from typing import Dict, List, Optional
from deployment_manifest import ManifestManager
from governance_proposal import MultiChainProposal
class MultiChainCLI:
def __init__(self):
self.parser = argparse.ArgumentParser(
description="MSG Chain Multi-Chain Development Kit"
)
self._setup_parsers()
def _setup_parsers(self):
subparsers = self.parser.add_subparsers(dest="command")
# Deploy
deploy_parser = subparsers.add_parser("deploy", help="Deploy contracts")
deploy_sub = deploy_parser.add_subparsers(dest="deploy_command")
all_parser = deploy_sub.add_parser("all", help="Deploy to all chains")
all_parser.add_argument("--wasm", required=True)
all_parser.add_argument("--manifest", required=True)
all_parser.add_argument("--admin", default="")
wasm_parser = deploy_sub.add_parser("wasm", help="Upload WASM")
wasm_parser.add_argument("--chain", required=True)
wasm_parser.add_argument("--wasm", required=True)
wasm_parser.add_argument("--manifest", required=True)
# Manifest
manifest_parser = subparsers.add_parser("manifest", help="Manifest management")
manifest_sub = manifest_parser.add_subparsers(dest="manifest_command")
init_parser = manifest_sub.add_parser("init", help="Initialize manifest")
init_parser.add_argument("--output", required=True)
validate_parser = manifest_sub.add_parser("validate", help="Validate manifest")
validate_parser.add_argument("--manifest", required=True)
# Address map
addr_parser = subparsers.add_parser("address-map", help="Address mapping")
addr_sub = addr_parser.add_subparsers(dest="addr_command")
reg_parser = addr_sub.add_parser("register", help="Register address")
reg_parser.add_argument("--manifest", required=True)
reg_parser.add_argument("--chain", required=True)
reg_parser.add_argument("--contract", required=True)
reg_parser.add_argument("--address", required=True)
resolve_parser = addr_sub.add_parser("resolve", help="Resolve address")
resolve_parser.add_argument("--manifest", required=True)
resolve_parser.add_argument("--chain", required=True)
resolve_parser.add_argument("--contract", required=True)
# Governance
gov_parser = subparsers.add_parser("governance", help="Governance")
gov_sub = gov_parser.add_subparsers(dest="gov_command")
prop_parser = gov_sub.add_parser("propose", help="Submit proposal")
prop_parser.add_argument("--manifest", required=True)
prop_parser.add_argument("--proposal", required=True)
status_parser = gov_sub.add_parser("status", help="Check status")
status_parser.add_argument("--manifest", required=True)
status_parser.add_argument("--proposal-id", required=True)
# Verify
verify_parser = subparsers.add_parser("verify", help="Verify deployments")
verify_sub = verify_parser.add_subparsers(dest="verify_command")
all_v_parser = verify_sub.add_parser("all", help="Verify all chains")
all_v_parser.add_argument("--manifest", required=True)
def run(self, argv: Optional[List[str]] = None):
args = self.parser.parse_args(argv)
if args.command == "deploy":
if args.deploy_command == "all":
self._deploy_all(args)
elif args.deploy_command == "wasm":
self._upload_wasm(args)
elif args.command == "manifest":
if args.manifest_command == "init":
self._init_manifest(args)
elif args.manifest_command == "validate":
self._validate_manifest(args)
elif args.command == "address-map":
if args.addr_command == "register":
self._register_address(args)
elif args.addr_command == "resolve":
self._resolve_address(args)
elif args.command == "governance":
if args.gov_command == "propose":
self._submit_proposal(args)
elif args.gov_command == "status":
self._check_status(args)
elif args.command == "verify":
if args.verify_command == "all":
self._verify_all(args)
else:
self.parser.print_help()
def _deploy_all(self, args):
print(f"Deploying {args.wasm} to all chains in {args.manifest}...")
print("Deployment complete.")
def _upload_wasm(self, args):
print(f"Uploading {args.wasm} to {args.chain}...")
print("Upload complete.")
def _init_manifest(self, args):
manifest = {
"project": {"name": "My Multi-Chain Project", "version": "1.0.0", "repository": ""},
"deployments": [],
}
with open(args.output, "w") as f:
json.dump(manifest, f, indent=2)
print(f"Initialized manifest at {args.output}")
def _validate_manifest(self, args):
manager = ManifestManager(args.manifest)
chains = manager.get_all_addresses()
if chains:
print("Manifest is valid. Chains:", list(chains.keys()))
else:
print("Manifest has no deployments.")
def _register_address(self, args):
print(f"Registered {args.chain}/{args.contract} -> {args.address}")
def _resolve_address(self, args):
print(f"Resolved {args.chain}/{args.contract}")
def _submit_proposal(self, args):
with open(args.proposal, "r") as f:
data = json.load(f)
proposal = MultiChainProposal(**data)
valid, errors, warnings = proposal.validate()
if valid:
print("Proposal submitted successfully.")
else:
print("Validation errors:", errors)
def _check_status(self, args):
print(f"Checking proposal {args.proposal_id}...")
def _verify_all(self, args):
print("Verifying all chains...")
def main():
cli = MultiChainCLI()
cli.run()
if __name__ == "__main__":
main()
6.4 执行策略: 顺序 vs 并行
// X-MSG-Stub=true: execution-strategy.ts
interface ExecutionStrategy {
type: "sequential" | "parallel" | "weighted";
description: string;
estimatedTime: string;
riskLevel: "low" | "medium" | "high";
}
const executionStrategies: Record<string, ExecutionStrategy> = {
sequential: {
type: "sequential",
description: "Execute on one chain at a time. Each chain must complete before the next begins.",
estimatedTime: "N * block_time * confirmations",
riskLevel: "low",
},
parallel: {
type: "parallel",
description: "Execute on all chains simultaneously. Fastest but highest gas cost.",
estimatedTime: "block_time * confirmations",
riskLevel: "medium",
},
weighted: {
type: "weighted",
description: "Execute on primary chain first, then parallel on remaining chains.",
estimatedTime: "2 * block_time * confirmations",
riskLevel: "medium",
},
};
function suggestExecutionStrategy(
chainCount: number,
contractUpgrade: boolean,
totalValue: string
): ExecutionStrategy {
if (contractUpgrade && chainCount > 3) {
return executionStrategies.sequential;
}
if (chainCount <= 2 && !contractUpgrade) {
return executionStrategies.parallel;
}
return executionStrategies.weighted;
}
export { ExecutionStrategy, executionStrategies, suggestExecutionStrategy };
7. 跨链安全
7.1 重放攻击防护
7.1.1 链 ID 分离
每笔跨链交易签名必须包含独立链 ID,防止在不同链之间重放:
跨链重放攻击场景:
攻击者截获 MSG Chain 上的签名交易:
{ nonce: 42, action: "transfer", amount: "1000" }
尝试在 Cosmos Hub 上重放:
Cosmos Hub 检查: chain_id 不匹配
msg-chain-1 签名的消息包含 "chain_id: msg-chain-1"
Cosmos Hub 期望: "chain_id: cosmoshub-4"
-> 重放失败
// X-MSG-Stub=true: replay-protection.ts
import { createHash } from "crypto";
interface SignedMessage {
chainId: string;
nonce: string;
payload: Record<string, unknown>;
signature: string;
}
class ReplayProtection {
verifyMessage(
message: SignedMessage,
expectedChainId: string
): boolean {
if (message.chainId !== expectedChainId) {
console.error(
`Chain ID mismatch: expected ${expectedChainId}, got ${message.chainId}`
);
return false;
}
const messageHash = this.hashMessage(message);
// Verify Dilithium-5 signature
return true;
}
hashMessage(message: Omit<SignedMessage, "signature">): string {
const data = `${message.chainId}:${message.nonce}:${JSON.stringify(message.payload)}`;
return createHash("sha256").update(data).digest("hex");
}
signMessage(
chainId: string,
nonce: string,
payload: Record<string, unknown>,
privateKey: string
): SignedMessage {
const message = { chainId, nonce, payload, signature: "" };
const hash = this.hashMessage(message);
// Sign with Dilithium-5
message.signature = `sig_${hash}_${privateKey.slice(0, 8)}`;
return message;
}
}
export { ReplayProtection, SignedMessage };
7.1.2 Nonce 管理
每条链上的每个账户都需要维护独立的 nonce 计数器:
// X-MSG-Stub=true: nonce-management in CosmWasm
use cosmwasm_schema::cw_serde;
use cw_storage_plus::Map;
use cosmwasm_std::StdResult;
/// Maps (chain_id, sender) -> nonce
pub const CHAIN_NONCES: Map<(&str, &str), u64> = Map::new("chain_nonces");
/// Verify and increment nonce for cross-chain messages
pub fn verify_and_increment_nonce(
storage: &mut dyn cosmwasm_std::Storage,
chain_id: &str,
sender: &str,
provided_nonce: u64,
) -> Result<(), CrossChainError> {
let key = (chain_id, sender);
let expected_nonce = CHAIN_NONCES
.may_load(storage, key)?
.unwrap_or(0);
if provided_nonce != expected_nonce {
return Err(CrossChainError::InvalidNonce {
expected: expected_nonce,
provided: provided_nonce,
});
}
CHAIN_NONCES.save(storage, key, &(expected_nonce + 1))?;
Ok(())
}
#[cw_serde]
pub enum CrossChainError {
InvalidNonce { expected: u64, provided: u64 },
ReplayDetected { message_id: String },
}
7.2 不同链 ID 的签名隔离
每条链的处理消息时必须验证 chain_id 匹配:
// X-MSG-Stub=true: chain-id-validation.ts
const VALID_CHAIN_IDS = ["msg-chain-1", "cosmoshub-4", "osmosis-1", "neutron-1", "stride-1"];
function validateChainId(chainId: string): boolean {
return VALID_CHAIN_IDS.includes(chainId);
}
function assertCorrectChain(chainId: string, expectedPrefix: string): void {
const prefixMap: Record<string, string> = {
"msg-chain-1": "msg",
"cosmoshub-4": "cosmos",
"osmosis-1": "osmo",
"neutron-1": "neutron",
"stride-1": "stride",
};
const expectedPrefix_ = prefixMap[chainId];
if (!expectedPrefix_) {
throw new Error(`Unknown chain ID: ${chainId}`);
}
}
function verifyCrossChainMessage(
message: { chainId: string; sender: string; payload: unknown },
expectedChainId: string
): boolean {
if (message.chainId !== expectedChainId) {
console.error(
`Chain ID forgery detected! Message claims ${message.chainId}, expected ${expectedChainId}`
);
return false;
}
const prefix = message.sender.split("1")[0];
const expectedPrefix = expectedChainId === "msg-chain-1" ? "msg" : "cosmos";
if (prefix !== expectedPrefix) {
console.error(
`Address prefix mismatch: ${prefix} != ${expectedPrefix}`
);
return false;
}
return true;
}
export { validateChainId, assertCorrectChain, verifyCrossChainMessage };
7.3 IBC Relayer 信任假设
IMS Chain 的 IBC 实现不信任 Relayer —— Relayer 只能延迟或审查消息,不能篡改:
| 威胁 | 是否可能 | 影响 | 缓解措施 |
|---|---|---|---|
| Relayer 窃取资金 | 否 | 无 | IBC 数据包不可伪造 |
| Relayer 篡改消息 | 否 | 无 | 轻客户端验证签名 |
| Relayer 延迟消息 | 是 | 中等 | 超时机制 + 多 Relayer |
| Relayer 审查消息 | 是 | 低 | 多 Relayer 并行 |
| Relayer 伪造确认 | 否 | 无 | 链上验证 |
// X-MSG-Stub=true: relayer-trust.ts
interface RelayerHealth {
address: string;
lastActive: number;
packetsRelayed: number;
packetsPending: number;
averageLatencyMs: number;
isActive: boolean;
}
class RelayerMonitor {
private relayers: Map<string, RelayerHealth> = new Map();
private readonly TIMEOUT_THRESHOLD_MS = 300000; // 5 min
registerRelayer(address: string): void {
this.relayers.set(address, {
address,
lastActive: Date.now(),
packetsRelayed: 0,
packetsPending: 0,
averageLatencyMs: 0,
isActive: true,
});
}
updateHealth(address: string, latencyMs: number): void {
const relayer = this.relayers.get(address);
if (relayer) {
relayer.lastActive = Date.now();
relayer.packetsRelayed++;
relayer.averageLatencyMs =
(relayer.averageLatencyMs * (relayer.packetsRelayed - 1) + latencyMs) /
relayer.packetsRelayed;
relayer.isActive = true;
}
}
getActiveRelayers(): RelayerHealth[] {
const now = Date.now();
return Array.from(this.relayers.values()).filter(
(r) => now - r.lastActive < this.TIMEOUT_THRESHOLD_MS
);
}
isPacketTimedOut(sentAt: number, timeoutMs: number): boolean {
return Date.now() - sentAt > timeoutMs;
}
getRecommendedRelayers(): string[] {
return this.getActiveRelayers()
.sort((a, b) => a.averageLatencyMs - b.averageLatencyMs)
.slice(0, 3)
.map((r) => r.address);
}
}
export { RelayerMonitor, RelayerHealth };
7.4 跨链治理法定人数
多链治理需要跨链投票协调才能达到有效法定人数:
跨链法定人数策略:
方案 1: 主链投票 (推荐)
投票只在 MSG Chain 上进行
通过后 IBC 中继到其他链执行
优点: 简单、一致
缺点: 其他链验证者没有直接投票权
方案 2: 多链独立投票 + 聚合
各链独立投票
治理合约聚合所有链的结果
需要跨链通信聚合投票数据
方案 3: 加权跨链投票
投票权重 = 质押在各链上的总代币量
例如: MSG Chain 1000 万, Cosmos Hub 500 万
权重比例: 2:1
// X-MSG-Stub=true: cross-chain-quorum.ts
interface CrossChainQuorumConfig {
strategy: "single_chain" | "aggregate" | "weighted";
mainChain: string;
threshold: number;
quorum: number;
chainWeights?: Record<string, number>;
}
const defaultQuorumConfig: CrossChainQuorumConfig = {
strategy: "single_chain",
mainChain: "msg-chain-1",
threshold: 0.5,
quorum: 0.4,
};
function calculateAggregateQuorum(
chainResults: Map<string, { totalVotingPower: number; votedFor: number; passed: boolean }>,
config: CrossChainQuorumConfig
): { passed: boolean; totalTurnout: number; approvalRate: number } {
if (config.strategy === "single_chain") {
const main = chainResults.get(config.mainChain);
if (!main) return { passed: false, totalTurnout: 0, approvalRate: 0 };
return {
passed: main.passed,
totalTurnout: main.votedFor / main.totalVotingPower,
approvalRate: main.votedFor / main.totalVotingPower,
};
}
if (config.strategy === "aggregate") {
let totalFor = 0;
let totalPower = 0;
for (const result of chainResults.values()) {
totalFor += result.votedFor;
totalPower += result.totalVotingPower;
}
const turnout = totalFor / totalPower;
return {
passed: turnout >= config.quorum,
totalTurnout: turnout,
approvalRate: totalFor / totalPower,
};
}
if (config.strategy === "weighted" && config.chainWeights) {
let weightedFor = 0;
let totalWeight = 0;
for (const [chain, result] of chainResults) {
const weight = config.chainWeights[chain] || 1;
weightedFor += result.votedFor * weight;
totalWeight += result.totalVotingPower * weight;
}
const rate = weightedFor / totalWeight;
return {
passed: rate >= config.threshold,
totalTurnout: rate,
approvalRate: rate,
};
}
return { passed: false, totalTurnout: 0, approvalRate: 0 };
}
export { CrossChainQuorumConfig, calculateAggregateQuorum, defaultQuorumConfig };
7.5 安全最佳实践清单
跨链部署安全清单:
基础安全:
[ ] 所有跨链消息包含 chain_id 字段
[ ] Nonce 按 (chain_id, sender) 独立管理
[ ] Dilithium-5 签名验签全覆盖
[ ] 不同链使用不同地址前缀
合约安全:
[ ] 地址映射合约只有 admin 可写
[ ] 治理提案有 valid 窗口期
[ ] 跨链消息有超时退款机制
[ ] 暂停机制可紧急停止跨链操作
Relayer 安全:
[ ] 最少 3 个独立 Relayer 运营商
[ ] Relayer 密钥使用 HSM 管理
[ ] 监控 Relayer 活跃度
[ ] Relayer 自动故障切换
治理安全:
[ ] 多链提案需要验证所有 target chain 存在
[ ] 提案投票需要跨链 quorum
[ ] 顺序执行时串联依赖校验
[ ] 失败时自动回滚
8. 治理同步示例
8.1 场景: 部署和升级多链代币
本示例演示完整的跨链治理同步流程:在 MSG Chain 和 Cosmos Hub 上部署 MSG 代币合约,然后通过跨链治理提案升级合约。
8.1.1 Step 1: 多链部署代币合约
// X-MSG-Stub=true: step1-deploy-token.ts
import { MultiChainDeployer } from "./deploy-multichain";
async function deployTokenOnMultipleChains() {
const deployer = new MultiChainDeployer("./deployment-manifest.yaml");
const mnemonic = process.env.DEPLOYER_MNEMONIC!;
// Connect to both chains
await deployer.connectChain("msg-chain-1", mnemonic);
await deployer.connectChain("cosmoshub-4", mnemonic);
// Deploy token contract on MSG Chain
await deployer.deployToChain(
"msg-chain-1",
"./artifacts/msg_token_v1.wasm",
"token",
{
name: "MSG Token",
symbol: "MSG",
decimals: 18,
initial_balances: [
{
address: "msg1treasury...",
amount: "1000000000000000000000000",
},
],
},
"MSG Token v1",
"msg1admin..."
);
// Deploy token contract on Cosmos Hub
await deployer.deployToChain(
"cosmoshub-4",
"./artifacts/msg_token_v1.wasm",
"token",
{
name: "MSG Token",
symbol: "msg.MSG",
decimals: 18,
initial_balances: [
{
address: "cosmos1treasury...",
amount: "1000000000000000000000000",
},
],
},
"MSG Token (Cosmos) v1",
"cosmos1admin..."
);
console.log("Token deployed on both chains.");
}
deployTokenOnMultipleChains().catch(console.error);
部署结果:
MSG Chain:
Token 合约: msg1tokenv1...
Code ID: 5
管理员: msg1admin...
Cosmos Hub:
Token 合约: cosmos1tokenv1...
Code ID: 12
管理员: cosmos1admin...
8.1.2 Step 2: 注册跨链地址映射
// X-MSG-Stub=true: step2-register-addresses.ts
import { CrossChainResolver, ChainEndpoint } from "./cross-chain-resolver";
async function registerAddressMappings() {
// Register addresses on MSG Chain's address map
const resolver = new CrossChainResolver([
{
chainId: "msg-chain-1",
rpc: "https://rpc.msgchain.org:26657",
addressMapContract: "msg1addressmap...",
},
]);
// In production, this would be a governance execute or admin tx
console.log("Registering cross-chain addresses...");
// Register Cosmos Hub token on MSG Chain's map
// await resolver.registerAddress(
// "msg-chain-1", "cosmoshub-4", "token", "cosmos1tokenv1..."
// );
console.log("Address mappings registered.");
}
registerAddressMappings().catch(console.error);
8.1.3 Step 3: 创建跨链升级提案
// X-MSG-Stub=true: step3-create-upgrade-proposal.ts
import { MultiChainGovernanceProposal } from "./governance-proposal";
function createUpgradeProposal() {
const proposal = MultiChainGovernanceProposal.createUpgradeProposal(
"Upgrade MSG Token to v2 with Cross-Chain Support",
"This proposal upgrades the MSG Token contract to version 2.0.0 across MSG Chain and Cosmos Hub. " +
"New features include: cross-chain transfer capabilities, improved fee distribution, " +
"and enhanced security with batch transfer limits.",
"msg1authoraddress...",
[
{
chain: "msg-chain-1",
contractAddress: "msg1tokenv1...",
newCodeId: 8,
migrateMsg: {
upgrade_version: "2.0.0",
new_feature: "cross_chain_transfer",
fee_bps: 30,
max_batch_size: 100,
},
},
{
chain: "cosmoshub-4",
contractAddress: "cosmos1tokenv1...",
newCodeId: 18,
migrateMsg: {
upgrade_version: "2.0.0",
new_feature: "cross_chain_transfer",
fee_bps: 30,
max_batch_size: 100,
},
},
]
);
// Validate
const validation = proposal.validate();
if (!validation.valid) {
console.error("Validation errors:", validation.errors);
return;
}
// Save proposal
const fs = require("fs");
fs.writeFileSync(
"./proposals/upgrade-token-v2.json",
proposal.toJSON()
);
console.log("Proposal created:", proposal.toJSON());
}
createUpgradeProposal();
8.1.4 Step 4: 提交提案并投票
# X-MSG-Stub=true: 使用 CLI 提交提案
# 提交提案到 MSG Chain
msg-chain-devkit multi-chain governance propose \
--manifest ./deployment-manifest.yaml \
--proposal ./proposals/upgrade-token-v2.json
# 投票 (在 MSG Chain 上)
msgd tx gov vote 1 yes \
--from validator-1 \
--gas-prices 1000000000umsg \
--chain-id msg-chain-1
msgd tx gov vote 1 yes \
--from validator-2 \
--gas-prices 1000000000umsg \
--chain-id msg-chain-1
# 查询提案状态
msgd query gov proposal 1 \
--node https://rpc.msgchain.org:26657
8.1.5 Step 5: IBC 中继治理消息
// X-MSG-Stub=true: step5-relay-governance.ts
import { GovernanceRelayer } from "./governance-relay";
import { MultiChainGovernanceProposal } from "./governance-proposal";
async function relayGovernanceProposal() {
const proposal = MultiChainGovernanceProposal.fromJSON(
require("fs").readFileSync("./proposals/upgrade-token-v2.json", "utf-8")
);
const relayer = new GovernanceRelayer({
sourceChain: "msg-chain-1",
sourceRpc: "https://rpc.msgchain.org:26657",
targetChain: "cosmoshub-4",
targetRpc: "https://rpc.cosmoshub-4.com:26657",
governanceContract: "msg1governance...",
});
await relayer.connect(process.env.RELAYER_MNEMONIC!);
const results = await relayer.execute(proposal, "1000000000000000000000");
for (const result of results) {
console.log(
`Relay ${result.status}: ${result.targetTxHash || result.sourceTxHash}`
);
}
}
relayGovernanceProposal().catch(console.error);
8.1.6 Step 6: 验证升级
// X-MSG-Stub=true: step6-verify-upgrade.ts
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
async function verifyUpgrade() {
const msgClient = await CosmWasmClient.connect("https://rpc.msgchain.org:26657");
const cosmosClient = await CosmWasmClient.connect("https://rpc.cosmoshub-4.com:26657");
// Verify MSG Chain token upgrade
const msgContract = await msgClient.getContract("msg1tokenv1...");
console.log(`MSG Chain Token: code_id=${msgContract.codeId}, admin=${msgContract.admin}`);
const msgUpgraded = msgContract.codeId === 8;
console.log(`MSG Chain upgrade: ${msgUpgraded ? "SUCCESS" : "FAILED"}`);
// Verify Cosmos Hub token upgrade
const cosmosContract = await cosmosClient.getContract("cosmos1tokenv1...");
console.log(`Cosmos Hub Token: code_id=${cosmosContract.codeId}, admin=${cosmosContract.admin}`);
const cosmosUpgraded = cosmosContract.codeId === 18;
console.log(`Cosmos Hub upgrade: ${cosmosUpgraded ? "SUCCESS" : "FAILED"}`);
if (msgUpgraded && cosmosUpgraded) {
console.log("Cross-chain governance upgrade completed successfully!");
} else {
console.error("Cross-chain governance upgrade failed.");
}
}
verifyUpgrade().catch(console.error);
8.2 完整端到端自动化脚本
// X-MSG-Stub=true: e2e-governance-upgrade.ts
// 完整的端到端跨链治理升级自动化脚本
import { MultiChainDeployer } from "./deploy-multichain";
import { MultiChainGovernanceProposal } from "./governance-proposal";
import { GovernanceRelayer } from "./governance-relay";
import { ExecutionCoordinator } from "./execution-coordinator";
import { DeploymentVerifier } from "./verify-deployment";
import { CrossChainResolver } from "./cross-chain-resolver";
const MANIFEST_PATH = "./deployment-manifest.yaml";
const MNEMONIC = process.env.DEPLOYER_MNEMONIC || "";
const WASM_V1 = "./artifacts/msg_token_v1.wasm";
const WASM_V2 = "./artifacts/msg_token_v2.wasm";
async function e2eCrossChainGovernance() {
console.log("=== Step 1: Deploy Token v1 on both chains ===");
const deployer = new MultiChainDeployer(MANIFEST_PATH);
await deployer.connectChain("msg-chain-1", MNEMONIC);
await deployer.connectChain("cosmoshub-4", MNEMONIC);
const msgResult = await deployer.deployToChain(
"msg-chain-1", WASM_V1, "token",
{ name: "MSG Token", symbol: "MSG", decimals: 18, initial_balances: [] },
"MSG Token v1", "msg1admin..."
);
console.log(`MSG Chain token: ${msgResult.address} (code_id: ${msgResult.codeId})`);
const cosmosResult = await deployer.deployToChain(
"cosmoshub-4", WASM_V1, "token",
{ name: "MSG Token", symbol: "msg.MSG", decimals: 18, initial_balances: [] },
"MSG Token v1", "cosmos1admin..."
);
console.log(`Cosmos Hub token: ${cosmosResult.address} (code_id: ${cosmosResult.codeId})`);
console.log("\n=== Step 2: Verify deployment ===");
const verifier = new DeploymentVerifier(MANIFEST_PATH);
const verifyResults = await verifier.verifyAll();
const allDeployed = verifyResults.every((r) => r.exists);
if (!allDeployed) throw new Error("Deployment verification failed");
console.log("\n=== Step 3: Register cross-chain addresses ===");
// In production: register via address-map contract
console.log("\n=== Step 4: Create upgrade proposal ===");
const proposal = MultiChainGovernanceProposal.createUpgradeProposal(
"Upgrade MSG Token v2",
"Upgrade token with cross-chain support",
"msg1author...",
[
{ chain: "msg-chain-1", contractAddress: msgResult.address, newCodeId: 8, migrateMsg: { version: "2.0.0" } },
{ chain: "cosmoshub-4", contractAddress: cosmosResult.address, newCodeId: 18, migrateMsg: { version: "2.0.0" } },
]
);
const validation = proposal.validate();
if (!validation.valid) throw new Error("Invalid proposal: " + validation.errors.join(", "));
console.log("Proposal created and validated.");
console.log(`Target chains: ${proposal.chains.join(", ")}`);
console.log("\n=== Step 5: Execute proposal on MSG Chain ===");
const relayer = new GovernanceRelayer({
sourceChain: "msg-chain-1", sourceRpc: "https://rpc.msgchain.org:26657",
targetChain: "cosmoshub-4", targetRpc: "https://rpc.cosmoshub-4.com:26657",
governanceContract: "msg1governance...",
});
await relayer.connect(MNEMONIC);
console.log("\n=== Step 6: Relay and execute on target chain ===");
const relayResults = await relayer.execute(proposal, "1000000000000000000000");
console.log("\n=== Step 7: Coordinate execution ===");
const coordinator = new ExecutionCoordinator(proposal);
const report = await coordinator.execute();
console.log(`Execution status: ${report.overallStatus}`);
console.log("\n=== Step 8: Final verification ===");
const finalVerify = await verifier.verifyAll();
const upgradeSuccess = finalVerify.every((r) => r.exists && r.codeMatch);
console.log("\n========================================");
console.log(`Cross-chain governance: ${upgradeSuccess ? "SUCCESS" : "FAILED"}`);
console.log("========================================");
return { proposal, report, relayResults, finalVerify };
}
e2eCrossChainGovernance().catch(console.error);
8.3 Python 端到端示例
"""
X-MSG-Stub=true: e2e_governance_upgrade.py
"""
import json
import asyncio
from deployment_manifest import ManifestManager
from governance_proposal import MultiChainProposal, create_upgrade_proposal
from execution_coordinator import ExecutionCoordinator
from governance_relay import GovernanceRelayClient
async def e2e_cross_chain_governance():
print("=== Cross-Chain Governance End-to-End Demo ===")
# Step 1: Create manifest
print("\n[Step 1] Initializing deployment manifest...")
manager = ManifestManager("./deployment-manifest.yaml")
chains = manager.get_all_addresses()
print(f"Configured chains: {list(chains.keys())}")
# Step 2: Create upgrade proposal
print("\n[Step 2] Creating upgrade proposal...")
proposal = create_upgrade_proposal(
title="Upgrade MSG Token v2 Across Chains",
description="Upgrade token contract with cross-chain support",
author="msg1author...",
upgrades=[
{
"chain": "msg-chain-1",
"contract_address": "msg1tokenv1...",
"new_code_id": 8,
"migrate_msg": {"version": "2.0.0", "feature": "cross_chain"},
},
{
"chain": "cosmoshub-4",
"contract_address": "cosmos1tokenv1...",
"new_code_id": 18,
"migrate_msg": {"version": "2.0.0", "feature": "cross_chain"},
},
],
)
valid, errors, warnings = proposal.validate()
if not valid:
print(f"Validation errors: {errors}")
return
print(f"Proposal created: {proposal.title}")
print(f"Target chains: {proposal.chains}")
print(f"Actions: {len(proposal.actions)}")
print(f"Estimated execution time: {proposal.estimate_execution_time()}")
# Step 3: Relayer setup
print("\n[Step 3] Setting up governance relay...")
relayer = GovernanceRelayClient(
source_chain="msg-chain-1",
source_rpc="https://rpc.msgchain.org:26657",
target_chain="cosmoshub-4",
target_rpc="https://rpc.cosmoshub-4.com:26657",
governance_contract="msg1governance...",
)
# Step 4: Execute coordination
print("\n[Step 4] Coordinating execution...")
coordinator = ExecutionCoordinator(
actions=[
{
"chain": a.chain,
"order": a.order,
"msg": a.msg,
"proposal_id": proposal.title,
}
for a in proposal.actions
],
execution_mode="sequential",
max_attempts=3,
)
report = await coordinator.execute()
print(f"Execution status: {report.overall_status}")
for state in report.states:
print(f" {state.chain}: {state.status.value} (attempts: {state.attempts})")
# Summary
print("\n=== Summary ===")
print(f"Proposal: {proposal.title}")
print(f"Chains: {', '.join(proposal.chains)}")
print(f"Result: {report.overall_status.upper()}")
return report
if __name__ == "__main__":
asyncio.run(e2e_cross_chain_governance())
9. 附录
A.1 快速参考卡
| 命令 | 用途 |
|---|---|
msg-chain-devkit multi-chain deploy all --manifest manifest.yaml --wasm contract.wasm |
一键部署到所有链 |
msg-chain-devkit multi-chain manifest validate --manifest manifest.yaml |
验证部署清单 |
msg-chain-devkit multi-chain address-map register --chain cosmoshub-4 --contract token --address cosmos1... |
注册跨链地址 |
msg-chain-devkit multi-chain governance propose --manifest manifest.yaml --proposal proposal.json |
提交多链治理提案 |
msg-chain-devkit multi-chain verify all --manifest manifest.yaml |
验证所有链的部署 |
A.2 IBC 通道分配
| 端口 | 用途 | 通道类型 | 优先级 |
|---|---|---|---|
transfer |
ICS-20 代币转账 | UNORDERED | P0 |
governance |
治理消息中继 | ORDERED | P0 |
wasm.{contract} |
CosmWasm 合约 IBC | 自定义 | P1 |
icahost |
ICS-27 跨链账户 | ORDERED | P1 |
icacontroller |
ICS-27 控制端 | ORDERED | P1 |
A.3 术语表
| 术语 | 英文 | 定义 |
|---|---|---|
| 轻客户端 | Light Client | 运行在链上的区块头验证代码 |
| 中继者 | Relayer | 转发 IBC 数据包的基础设施节点 |
| 连接 | Connection | IBC 两层的抽象连接 |
| 通道 | Channel | IBC 数据传输通道 |
| 治理同步 | Governance Sync | 跨链治理提案的多链协调执行 |
| 部署清单 | Deployment Manifest | 记录多链部署配置的 YAML 文件 |
| 地址映射 | Address Mapping | 跨链合约地址的关联映射 |
A.4 相关文档
| 文档 | 路径 | 说明 |
|---|---|---|
| IBC 跨链桥接指南 | 跨链桥接与IBC指南.md |
IBC 协议基础、Relayer 配置 |
| CosmWasm 合约模板 | CosmWasm合约模板库.md |
CosmWasm 合约开发 |
| msg-chain-devkit CLI | msg-chain-devkit CLI工具.md |
CLI 工具完整文档 |
| 治理实战手册 | DAO治理实战手册.md |
单链治理流程 |
| 安全审计清单 | 安全审计清单与常见漏洞.md |
智能合约安全指南 |
A.5 YAML/JSON Schema 验证
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Multi-Chain Deployment Manifest",
"type": "object",
"required": ["project", "deployments"],
"properties": {
"project": {
"type": "object",
"required": ["name", "version"],
"properties": {
"name": { "type": "string" },
"version": { "type": "string" },
"repository": { "type": "string" }
}
},
"deployments": {
"type": "array",
"items": {
"type": "object",
"required": ["chain", "network", "chain_id", "rpc", "contracts"],
"properties": {
"chain": { "type": "string", "pattern": "^[a-z0-9-]+$" },
"network": { "type": "string", "enum": ["mainnet", "testnet"] },
"chain_id": { "type": "string" },
"rpc": { "type": "string", "format": "uri" },
"grpc": { "type": "string", "format": "uri" },
"contracts": {
"type": "object",
"patternProperties": {
"^[a-z_]+$": {
"type": "object",
"properties": {
"address": { "type": "string" },
"code_id": { "type": "integer" },
"admin": { "type": "string" }
}
}
}
},
"ibc": {
"type": "object",
"properties": {
"connection_id": { "type": "string" },
"channels": {
"type": "object",
"patternProperties": {
"^[a-z_]+$": { "type": "string" }
}
}
}
}
}
}
}
}
}
A.6 常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 部署失败: gas exceeded | WASM 文件过大 | 优化合约大小,增加 gas 限额 |
| 地址解析失败 | 尚未注册地址映射 | 执行 address-map register 命令 |
| 治理提案验证失败 | 提案 JSON 格式错误 | 使用 GovernanceProposal.validate() 检查 |
| IBC 中继超时 | Relayer 未运行 | 检查 Relayer 状态并重新启动 |
| 跨链执行不一致 | 部分链执行失败 | 使用 verify all 检查差异 |
| 签名验证失败 | 链 ID 不匹配 | 确认消息 chain_id 与目标链一致 |
本文档中所有标记
X-MSG-Stub=true的内容均为规划中的多链功能,尚未在主网上实现。实际可用的多链部署和治理同步功能请参考 MSG Chain 官方文档和主网状态。
