MSG Chain Formal Contract Schemas — 类型安全合约交互指南
数据来源:MSG Chain 代码库核实
主网状态: No-Go — 当前 MSGChain 主网裁决为 No-Go,以下内容反映代码实际状态,不代表生产可用。
目录
- 概述
- Formal Contracts Schema格式
- RPC Methods Schema
- 从Schema生成类型安全客户端
- 运行时验证
- 与SDK集成
- GraphQL集成
- 验证工具与CI
- 安全性考虑
- 完整工作流示例
- 附录
1. 概述
1.1 为什么AI Agent需要类型安全
MSG Chain是一条专为AI Agent网络设计的第一层区块链,链上运行着36+个CosmWasm智能合约。当AI Agent需要与链上合约交互时——无论是注册身份、发起微支付、还是执行跨Agent通信——任何消息格式错误都可能导致:
- 交易失败:Gas被消耗但状态未变更
- 资产损失:错误的编码可能导致资金发送到错误地址
- 安全漏洞:类型混淆攻击可被利用来操纵合约状态
- 调试困难:原始JSON消息缺乏编译时检查,运行时错误难以追踪
类型安全(Type Safety)确保在编译时或交易提交前捕获这些错误,而非在链上执行时才发现。
1.2 MSG Chain的三层Schema体系
MSG Chain建立了三层Schema体系,确保从链上代码到客户端SDK的类型一致性:
Layer 1: Rust类型定义(单一事实源)
┌─────────────────────────────────────────┐
│ contracts/cosmwasm/all/*/src/msg.rs │
│ contracts/cosmwasm/all/*/src/query.rs │
│ contracts/cosmwasm/all/*/src/contract.rs│
│ #[derive(Serialize, Deserialize)] │
│ struct InstantiateMsg { ... } │
│ enum ExecuteMsg { ... } │
│ enum QueryMsg { ... } │
└──────────────┬──────────────────────────┘
│ 自动生成
▼
Layer 2: 机器可读Schema
┌─────────────────────────────────────────┐
│ api_specs/formal_contracts.json │
│ api_specs/rpc_methods.json │
│ JSON Schema (draft 2020-12) │
│ 每个合约的完整接口描述 │
└──────────────┬──────────────────────────┘
│ 代码生成
▼
Layer 3: API Surface
┌─────────────────────────────────────────┐
│ public_query.yaml (公开查询端点) │
│ contract_surface.yaml (合约交互面) │
│ agent_surface.yaml (Agent API) │
│ 类型安全客户端 / SDK │
└─────────────────────────────────────────┘
Layer 1: Rust类型(单一事实源)
每个CosmWasm合约在Rust源代码中定义了其消息类型。例如agent_registry_v1:
// contracts/cosmwasm/all/agent_registry_v1/src/msg.rs
use cosmwasm_schema::cw_serde;
#[cw_serde]
pub struct InstantiateMsg {
pub owner: String,
pub max_agents_per_owner: u32,
}
#[cw_serde]
pub enum ExecuteMsg {
RegisterAgent {
name: String,
capabilities: Vec<Capability>,
metadata: HashMap<String, String>,
},
UpdateAgent {
agent_id: String,
name: Option<String>,
capabilities: Option<Vec<Capability>>,
metadata: Option<HashMap<String, String>>,
},
DeregisterAgent {
agent_id: String,
},
}
#[cw_serde]
pub enum QueryMsg {
GetAgent { agent_id: String },
GetAgentsByOwner { owner: String, limit: Option<u32> },
SearchAgents { capability: Option<String>, limit: Option<u32> },
ListAgents { start_after: Option<String>, limit: Option<u32> },
}
#[cw_serde]
pub struct AgentRecord {
pub agent_id: String,
pub name: String,
pub owner: String,
pub capabilities: Vec<Capability>,
pub metadata: HashMap<String, String>,
pub registered_at: u64,
pub status: AgentStatus,
}
#[cw_serde]
pub enum AgentStatus {
Active,
Suspended,
Deregistered,
}
#[cw_serde]
pub struct Capability {
pub name: String,
pub version: String,
pub params: Option<HashMap<String, String>>,
}
Layer 2: formal_contracts.json(机器可读)
通过cosmwasm-schema或自定义代码生成器,从Rust类型自动导出为JSON Schema格式:
{
"version": "1.0.0",
"chain_id": "msg-chain-1",
"contracts": [
{
"canonical_key": "cosmwasm:contract:agent_registry_v1",
"module": "agent_registry",
"messages": {
"execute": [
{
"name": "register_agent",
"type": "{\"register_agent\": {\"name\": \"string\", \"capabilities\": [\"capability\"], \"metadata\": {\"string\": \"string\"}}}",
"description": "Register a new AI Agent in the registry"
}
],
"query": [
{
"name": "get_agent",
"type": "{\"get_agent\": {\"agent_id\": \"string\"}}",
"response": "{\"agent\": \"AgentRecord | null\"}"
}
]
},
"events": [
{
"name": "agent_registered",
"type": "{\"agent_registered\": {\"agent_id\": \"string\", \"owner\": \"string\", \"name\": \"string\"}}"
}
],
"errors": [
"AgentNotFound",
"AgentAlreadyExists",
"MaxAgentsPerOwnerReached",
"Unauthorized"
]
}
]
}
Layer 3: OpenAPI & SDK
最终层面向开发者,提供OpenAPI规范和类型安全SDK:
# contract_surface.yaml
openapi: 3.0.3
info:
title: MSG Chain Contract Surface
version: 1.0.0
paths:
/cosmwasm/contract/agent_registry_v1/query/get_agent:
post:
requestBody:
content:
application/json:
schema:
type: object
properties:
get_agent:
type: object
properties:
agent_id:
type: string
responses:
'200':
content:
application/json:
schema:
type: object
properties:
agent:
$ref: '#/components/schemas/AgentRecord'
1.3 Schema生成流水线
┌─────────────┐ ┌───────────────┐ ┌──────────────┐
│ Rust源代码 │───>│ cosmwasm- │───>│ JSON Schema │
│ msg.rs │ │ schema 导出 │ │ .json │
│ query.rs │ │ │ │ │
└─────────────┘ └───────────────┘ └──────┬───────┘
│ 聚合
▼
┌──────────────┐
│ formal_ │
│ contracts │
│ .json │
└──────┬───────┘
│ 代码生成
▼
┌───────────┴───────────┐
│ │
┌────────┐ ┌──────────┐
│ TS类型 │ │ Python │
│ 客户端 │ │ SDK │
└────────┘ └──────────┘
1.4 合约清单
MSG Chain主网包含36个已编译的CosmWasm v1合约,涵盖以下模块组:
| 组别 | 合约数量 | 示例合约 |
|---|---|---|
| 核心经济 | 9 | msg_token_cw20, msg_staking_v1, gas_fee_distribution_v2 |
| 治理 | 3 | dao_governance_v1, foundation_multisig_v1, foundation_treasury_v2 |
| Agent系统 | 8 | agent_registry_v1, agent_a2a_v1, agent_payment_v1, agent_work_v1 |
| 验证与路由 | 6 | validator_qualification_v2, node_routing_registry_v1 |
| DeFi与桥接 | 4 | defi_adapter_v1, bridge_adapter_v1 |
| 边缘计算 | 3 | edge_task_v1, edge_provider_registry_v1 |
| 基础设施 | 3 | genesis_registry_v1, oracle_registry_v1, block_time_schedule_v1 |
2. Formal Contracts Schema格式
2.1 顶层结构
formal_contracts.json是MSG Chain的合约接口规范文件,位于api_specs/formal_contracts.json。其TypeScript接口定义如下:
interface FormalContractsSpec {
/** Schema版本号 */
version: string;
/** 链ID */
chain_id: string;
/** 合约规格列表 */
contracts: ContractSpec[];
/** 全局类型定义 */
definitions?: Record<string, JSONSchemaDefinition>;
}
interface ContractSpec {
/** 规范键,格式: "cosmwasm:contract:{module}_{version}" */
canonical_key: string;
/** 模块名称 */
module: string;
/** 合约地址(部署后解析填入) */
address?: string;
/** 合约版本 */
version: string;
/** Code ID */
code_id?: number;
/** WASM校验和 */
checksum?: string;
/** 消息定义 */
messages: {
/** 实例化消息(可选) */
instantiate?: TypedMessage[];
/** 执行消息 */
execute: TypedMessage[];
/** 查询消息 */
query: TypedMessage[];
/** 迁移消息(可选) */
migrate?: TypedMessage[];
};
/** 事件定义 */
events: TypedEvent[];
/** 错误码映射 */
errors: ContractError[];
/** 合约依赖 */
dependencies?: string[];
}
2.2 规范键(Canonical Key)模式
每个合约通过canonical_key唯一标识。命名模式为:
cosmwasm:contract:{module}_{version}
例如:
cosmwasm:contract:agent_registry_v1cosmwasm:contract:genesis_registry_v1cosmwasm:contract:msg_token_cw20cosmwasm:contract:validator_qualification_v2
关键约束:genesis_registry_v1是第一个在Block 1部署的合约,负责注册其他所有合约的地址映射。客户端应先查询genesis_registry_v1以解析目标合约地址:
// 通过genesis_registry_v1解析合约地址
async function resolveContractAddress(
client: CosmWasmClient,
genesisRegistryAddr: string,
canonicalKey: string
): Promise<string> {
const result: { address: string } = await client.queryContractSmart(
genesisRegistryAddr,
{ resolve: { canonical_key: canonicalKey } }
);
return result.address;
}
2.3 消息类型定义
消息类型使用JSON Schema draft 2020-12格式描述:
interface TypedMessage {
/** 消息名称 */
name: string;
/** 消息的JSON类型描述 */
type: string;
/** 详细JSON Schema(完整验证用) */
schema?: JSONSchema;
/** 人类可读的描述 */
description?: string;
/** 调用示例 */
example?: Record<string, unknown>;
/** Gas估算 */
gas_estimate?: string;
}
interface TypedEvent {
/** 事件名称 */
name: string;
/** 事件类型描述 */
type: string;
/** 完整schema */
schema?: JSONSchema;
}
interface ContractError {
/** 错误名称 */
name: string;
/** 错误码 */
code: number;
/** 错误描述 */
description: string;
}
2.4 完整合约Schema示例
以下是agent_registry_v1的完整formal schema:
{
"canonical_key": "cosmwasm:contract:agent_registry_v1",
"module": "agent_registry",
"version": "1.0.0",
"code_id": 20,
"checksum": "e1e5222d4568d86468b8784687672298fed66cd5f3e5637bfb1d0fe5d71dbe4c",
"messages": {
"instantiate": [
{
"name": "instantiate",
"type": "{\"owner\": \"string\", \"max_agents_per_owner\": \"uint32\"}",
"schema": {
"type": "object",
"required": ["owner", "max_agents_per_owner"],
"properties": {
"owner": { "type": "string" },
"max_agents_per_owner": { "type": "integer", "minimum": 1 }
}
}
}
],
"execute": [
{
"name": "register_agent",
"type": "{\"register_agent\": {\"name\": \"string\", \"capabilities\": [\"capability\"], \"metadata\": {\"string\": \"string\"}}}",
"schema": {
"type": "object",
"required": ["register_agent"],
"properties": {
"register_agent": {
"type": "object",
"required": ["name", "capabilities", "metadata"],
"properties": {
"name": { "type": "string", "maxLength": 128 },
"capabilities": {
"type": "array",
"items": { "$ref": "#/definitions/Capability" }
},
"metadata": {
"type": "object",
"additionalProperties": { "type": "string" }
}
}
}
}
},
"example": {
"register_agent": {
"name": "TradingAgent_v3",
"capabilities": [
{ "name": "market_analysis", "version": "1.0.0" },
{ "name": "trade_execution", "version": "2.1.0" }
],
"metadata": {
"description": "Automated trading agent",
"risk_level": "moderate"
}
}
}
},
{
"name": "update_agent",
"type": "{\"update_agent\": {\"agent_id\": \"string\", \"name\": \"string | null\", \"capabilities\": \"[capability] | null\", \"metadata\": \"{string: string} | null\"}}"
},
{
"name": "deregister_agent",
"type": "{\"deregister_agent\": {\"agent_id\": \"string\"}}"
}
],
"query": [
{
"name": "get_agent",
"type": "{\"get_agent\": {\"agent_id\": \"string\"}}",
"response": "{\"agent\": \"AgentRecord | null\"}",
"response_schema": {
"type": "object",
"properties": {
"agent": {
"oneOf": [
{ "$ref": "#/definitions/AgentRecord" },
{ "type": "null" }
]
}
}
}
},
{
"name": "get_agents_by_owner",
"type": "{\"get_agents_by_owner\": {\"owner\": \"string\", \"limit\": \"uint32 | null\"}}",
"response": "{\"agents\": \"[AgentRecord]\"}"
},
{
"name": "search_agents",
"type": "{\"search_agents\": {\"capability\": \"string | null\", \"limit\": \"uint32 | null\"}}",
"response": "{\"agents\": \"[AgentRecord]\"}"
},
{
"name": "list_agents",
"type": "{\"list_agents\": {\"start_after\": \"string | null\", \"limit\": \"uint32 | null\"}}",
"response": "{\"agents\": \"[AgentRecord]\"}"
}
]
},
"events": [
{
"name": "agent_registered",
"type": "{\"agent_registered\": {\"agent_id\": \"string\", \"owner\": \"string\", \"name\": \"string\"}}"
},
{
"name": "agent_updated",
"type": "{\"agent_updated\": {\"agent_id\": \"string\", \"name\": \"string\"}}"
},
{
"name": "agent_deregistered",
"type": "{\"agent_deregistered\": {\"agent_id\": \"string\"}}"
}
],
"errors": [
{ "name": "AgentNotFound", "code": 1, "description": "指定的Agent ID不存在" },
{ "name": "AgentAlreadyExists", "code": 2, "description": "Agent ID已存在" },
{ "name": "MaxAgentsPerOwnerReached", "code": 3, "description": "超过每个所有者最大Agent数" },
{ "name": "Unauthorized", "code": 4, "description": "调用者不是Agent所有者" },
{ "name": "InvalidName", "code": 5, "description": "Agent名称格式无效" }
],
"dependencies": ["cosmwasm:contract:genesis_registry_v1"]
}
2.5 定义引用与复用
definitions部分定义可复用的类型,供多个合约引用:
{
"definitions": {
"AgentRecord": {
"type": "object",
"required": ["agent_id", "name", "owner", "capabilities", "metadata", "registered_at", "status"],
"properties": {
"agent_id": { "type": "string", "description": "Agent唯一标识" },
"name": { "type": "string" },
"owner": { "type": "string", "format": "bech32", "pattern": "^msg1[ac-hj-np-z02-9]+$" },
"capabilities": {
"type": "array",
"items": { "$ref": "#/definitions/Capability" }
},
"metadata": {
"type": "object",
"additionalProperties": { "type": "string" }
},
"registered_at": { "type": "integer" },
"status": { "$ref": "#/definitions/AgentStatus" }
}
},
"Capability": {
"type": "object",
"required": ["name", "version"],
"properties": {
"name": { "type": "string" },
"version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$" },
"params": {
"type": "object",
"additionalProperties": { "type": "string" }
}
}
},
"AgentStatus": {
"type": "string",
"enum": ["Active", "Suspended", "Deregistered"]
},
"Coin": {
"type": "object",
"required": ["denom", "amount"],
"properties": {
"denom": { "type": "string" },
"amount": { "type": "string", "pattern": "^[0-9]+$" }
}
}
}
}
2.6 合约地址解析流
┌──────────────┐ 查询 resolve(canonical_key) ┌───────────────────┐
│ 客户端 │ ──────────────────────────────> │ genesis_registry │
│ │ │ _v1 │
│ │ <──────────────────────────────── │ │
│ │ { address: "msg1..." } └───────────────────┘
│ │ │
│ │ 查询/执行 │
│ │ ──────────────────────────────────> │
│ │ │ agent_ │
│ │ │ registry│
│ │ <────────────────────────────────── │ _v1 │
└──────────────┘ { agent: AgentRecord | null } └─────────┘
2.7 合约注册表
以下是MSG Chain部分核心合约的canonical key和code_id映射:
| Code ID | Canonical Key | 合约名称 |
|---|---|---|
| 1 | cosmwasm:contract:msg_token_cw20 |
MSG Token CW20 |
| 2 | cosmwasm:contract:genesis_registry_v1 |
Genesis Registry V1 |
| 3 | cosmwasm:contract:dao_governance_v1 |
DAO Governance V1 |
| 4 | cosmwasm:contract:dar_rating_v1 |
DAR Rating V1 |
| 5 | cosmwasm:contract:block_time_schedule_v1 |
Block Time Schedule V1 |
| 6 | cosmwasm:contract:emission_schedule_v2 |
Emission Schedule V2 |
| 7 | cosmwasm:contract:foundation_treasury_v2 |
Foundation Treasury V2 |
| 8 | cosmwasm:contract:gas_fee_distribution_v2 |
Gas Fee Distribution V2 |
| 9 | cosmwasm:contract:validator_qualification_v2 |
Validator Qualification V2 |
| 10 | cosmwasm:contract:candidate_node_staking_v2 |
Candidate Node Staking V2 |
| 11 | cosmwasm:contract:challenge_slashing_v1 |
Challenge Slashing V1 |
| 12 | cosmwasm:contract:adapter_registry_v1 |
Adapter Registry V1 |
| 13 | cosmwasm:contract:agent_payment_v1 |
Agent Payment V1 |
| 14 | cosmwasm:contract:agent_work_v1 |
Agent Work V1 |
| 15 | cosmwasm:contract:agent_sandbox_v1 |
Agent Sandbox V1 |
| 16 | cosmwasm:contract:agent_verify_v1 |
Agent Verify V1 |
| 17 | cosmwasm:contract:agent_model_v1 |
Agent Model V1 |
| 18 | cosmwasm:contract:agent_intent_v1 |
Agent Intent V1 |
| 19 | cosmwasm:contract:agent_a2a_v1 |
Agent A2A V1 |
| 20 | cosmwasm:contract:agent_registry_v1 |
Agent Registry V1 |
| 21 | cosmwasm:contract:edge_task_v1 |
Edge Task V1 |
| 22 | cosmwasm:contract:edge_provider_registry_v1 |
Edge Provider Registry V1 |
| 23 | cosmwasm:contract:trust_accounting_v1 |
Trust Accounting V1 |
| 24 | cosmwasm:contract:micropayment_session_v1 |
Micropayment Session V1 |
| 25 | cosmwasm:contract:defi_adapter_v1 |
DeFi Adapter V1 |
| 26 | cosmwasm:contract:oracle_registry_v1 |
Oracle Registry V1 |
| 27 | cosmwasm:contract:bridge_adapter_v1 |
Bridge Adapter V1 |
| 28 | cosmwasm:contract:node_routing_registry_v1 |
Node Routing Registry V1 |
| 29 | cosmwasm:contract:aidid_did_registry_v1 |
AIDID DID Registry V1 |
| 30 | cosmwasm:contract:ai_agent_constitution_v1 |
AI Agent Constitution V1 |
| 31-36 | 其他合约 | block_reward_decay_v1, msg_staking_v1, multisig_v1, 等 |
3. RPC Methods Schema
3.1 rpc_methods.json结构
api_specs/rpc_methods.json定义了Cosmos SDK RPC端点的签名和类型:
{
"version": "1.0.0",
"chain_id": "msg-chain-1",
"rpc_endpoints": {
"cosmos": {
"base_tendermint": {
"version": "0.47.x"
},
"modules": {
"auth": {
"base": "/cosmos.auth.v1beta1.Query/",
"methods": [
{
"name": "Account",
"request_type": "cosmos.auth.v1beta1.QueryAccountRequest",
"response_type": "cosmos.auth.v1beta1.QueryAccountResponse",
"http": "GET /cosmos/auth/v1beta1/accounts/{address}"
},
{
"name": "Accounts",
"request_type": "cosmos.auth.v1beta1.QueryAccountsRequest",
"response_type": "cosmos.auth.v1beta1.QueryAccountsResponse",
"http": "GET /cosmos/auth/v1beta1/accounts"
},
{
"name": "Params",
"request_type": "cosmos.auth.v1beta1.QueryParamsRequest",
"response_type": "cosmos.auth.v1beta1.QueryParamsResponse",
"http": "GET /cosmos/auth/v1beta1/params"
}
]
},
"bank": {
"base": "/cosmos.bank.v1beta1.Query/",
"methods": [
{
"name": "Balance",
"request_type": "cosmos.bank.v1beta1.QueryBalanceRequest",
"response_type": "cosmos.bank.v1beta1.QueryBalanceResponse",
"http": "GET /cosmos/bank/v1beta1/balances/{address}/{denom}"
},
{
"name": "AllBalances",
"request_type": "cosmos.bank.v1beta1.QueryAllBalancesRequest",
"response_type": "cosmos.bank.v1beta1.QueryAllBalancesResponse",
"http": "GET /cosmos/bank/v1beta1/balances/{address}"
},
{
"name": "TotalSupply",
"request_type": "cosmos.bank.v1beta1.QueryTotalSupplyRequest",
"response_type": "cosmos.bank.v1beta1.QueryTotalSupplyResponse",
"http": "GET /cosmos/bank/v1beta1/supply"
}
]
},
"staking": {
"base": "/cosmos.staking.v1beta1.Query/",
"methods": [
{
"name": "Validators",
"request_type": "cosmos.staking.v1beta1.QueryValidatorsRequest",
"response_type": "cosmos.staking.v1beta1.QueryValidatorsResponse",
"http": "GET /cosmos/staking/v1beta1/validators"
},
{
"name": "Delegation",
"request_type": "cosmos.staking.v1beta1.QueryDelegationRequest",
"response_type": "cosmos.staking.v1beta1.QueryDelegationResponse",
"http": "GET /cosmos/staking/v1beta1/validators/{validator_addr}/delegations/{delegator_addr}"
},
{
"name": "Pool",
"request_type": "cosmos.staking.v1beta1.QueryPoolRequest",
"response_type": "cosmos.staking.v1beta1.QueryPoolResponse",
"http": "GET /cosmos/staking/v1beta1/pool"
}
]
},
"wasm": {
"base": "/cosmwasm.wasm.v1.Query/",
"methods": [
{
"name": "ContractInfo",
"request_type": "cosmwasm.wasm.v1.QueryContractInfoRequest",
"response_type": "cosmwasm.wasm.v1.QueryContractInfoResponse",
"http": "GET /cosmwasm/wasm/v1/contract/{address}"
},
{
"name": "SmartContractState",
"request_type": "cosmwasm.wasm.v1.QuerySmartContractStateRequest",
"response_type": "cosmwasm.wasm.v1.QuerySmartContractStateResponse",
"http": "GET /cosmwasm/wasm/v1/contract/{address}/smart/{query_data}"
},
{
"name": "Code",
"request_type": "cosmwasm.wasm.v1.QueryCodeRequest",
"response_type": "cosmwasm.wasm.v1.QueryCodeResponse",
"http": "GET /cosmwasm/wasm/v1/code/{code_id}"
},
{
"name": "Codes",
"request_type": "cosmwasm.wasm.v1.QueryCodesRequest",
"response_type": "cosmwasm.wasm.v1.QueryCodesResponse",
"http": "GET /cosmwasm/wasm/v1/code"
},
{
"name": "PinnedCodes",
"request_type": "cosmwasm.wasm.v1.QueryPinnedCodesRequest",
"response_type": "cosmwasm.wasm.v1.QueryPinnedCodesResponse"
},
{
"name": "Params",
"request_type": "cosmwasm.wasm.v1.QueryParamsRequest",
"response_type": "cosmwasm.wasm.v1.QueryParamsResponse"
},
{
"name": "BuildAddress",
"request_type": "cosmwasm.wasm.v1.QueryBuildAddressRequest",
"response_type": "cosmwasm.wasm.v1.QueryBuildAddressResponse"
}
]
},
"tx": {
"base": "/cosmos.tx.v1beta1.Service/",
"methods": [
{
"name": "Simulate",
"request_type": "cosmos.tx.v1beta1.SimulateRequest",
"response_type": "cosmos.tx.v1beta1.SimulateResponse",
"http": "POST /cosmos/tx/v1beta1/simulate"
},
{
"name": "GetTx",
"request_type": "cosmos.tx.v1beta1.GetTxRequest",
"response_type": "cosmos.tx.v1beta1.GetTxResponse",
"http": "GET /cosmos/tx/v1beta1/txs/{hash}"
},
{
"name": "BroadcastTx",
"request_type": "cosmos.tx.v1beta1.BroadcastTxRequest",
"response_type": "cosmos.tx.v1beta1.BroadcastTxResponse",
"http": "POST /cosmos/tx/v1beta1/txs"
},
{
"name": "GetTxsEvent",
"request_type": "cosmos.tx.v1beta1.GetTxsEventRequest",
"response_type": "cosmos.tx.v1beta1.GetTxsEventResponse"
}
]
}
}
}
}
}
3.2 RPC与合约Schema集成
合约查询通过Wasm模块的SmartContractState RPC方法完成。rpc_methods.json与formal_contracts.json通过以下方式集成:
客户端请求流程:
1. 用户在SDK中调用 typedQuery.getAgent("agent-123")
2. SDK从formal_contracts.json获取get_agent的消息类型
3. SDK构造查询消息: {"get_agent": {"agent_id": "agent-123"}}
4. SDK调用RPC: /cosmwasm/wasm/v1/contract/{address}/smart/{base64_query}
5. RPC返回原始JSON
6. SDK根据response_schema验证并反序列化为类型化结果
3.3 关于Bech32地址格式
MSG Chain使用msg前缀的Bech32地址格式:
const BECH32_PREFIX = 'msg';
const BECH32_REGEX = /^msg1[ac-hj-np-z02-9]+$/;
function isValidMsgAddress(address: string): boolean {
return BECH32_REGEX.test(address);
}
4. 从Schema生成类型安全客户端
4.1 TypeScript类型安全客户端
从formal_contracts.json生成完全类型化的CosmJS客户端:
生成的类型定义
// generated/agents.ts — 从formal_contracts.json自动生成
import { CosmWasmClient, SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate';
import { Coin, StdFee } from '@cosmjs/amino';
// ============ 类型定义 (从definitions自动生成) ============
export interface Capability {
name: string;
version: string;
params?: Record<string, string>;
}
export type AgentStatus = 'Active' | 'Suspended' | 'Deregistered';
export interface AgentRecord {
agent_id: string;
name: string;
owner: string;
capabilities: Capability[];
metadata: Record<string, string>;
registered_at: number;
status: AgentStatus;
}
// ============ Execute Messages ============
export interface RegisterAgentMsg {
register_agent: {
name: string;
capabilities: Capability[];
metadata: Record<string, string>;
};
}
export interface UpdateAgentMsg {
update_agent: {
agent_id: string;
name?: string;
capabilities?: Capability[];
metadata?: Record<string, string>;
};
}
export interface DeregisterAgentMsg {
deregister_agent: {
agent_id: string;
};
}
export type AgentRegistryExecuteMsg =
| RegisterAgentMsg
| UpdateAgentMsg
| DeregisterAgentMsg;
// ============ Query Messages ============
export interface GetAgentMsg {
get_agent: { agent_id: string };
}
export interface GetAgentsByOwnerMsg {
get_agents_by_owner: { owner: string; limit?: number };
}
export interface SearchAgentsMsg {
search_agents: { capability?: string; limit?: number };
}
export interface ListAgentsMsg {
list_agents: { start_after?: string; limit?: number };
}
export type AgentRegistryQueryMsg =
| GetAgentMsg
| GetAgentsByOwnerMsg
| SearchAgentsMsg
| ListAgentsMsg;
// ============ Query Responses ============
export interface GetAgentResponse {
agent: AgentRecord | null;
}
export interface GetAgentsByOwnerResponse {
agents: AgentRecord[];
}
export interface SearchAgentsResponse {
agents: AgentRecord[];
}
export interface ListAgentsResponse {
agents: AgentRecord[];
}
// ============ Events ============
export interface AgentRegisteredEvent {
agent_registered: {
agent_id: string;
owner: string;
name: string;
};
}
// ============ Errors ============
export enum AgentRegistryErrorCode {
AgentNotFound = 1,
AgentAlreadyExists = 2,
MaxAgentsPerOwnerReached = 3,
Unauthorized = 4,
InvalidName = 5,
}
export class AgentRegistryError extends Error {
constructor(
public readonly code: AgentRegistryErrorCode,
message: string
) {
super(`[AgentRegistryError ${code}] ${message}`);
this.name = 'AgentRegistryError';
}
}
类型安全客户端
// generated/AgentRegistryClient.ts — 从formal_contracts.json自动生成
import { CosmWasmClient, SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate';
import { Coin, StdFee } from '@cosmjs/amino';
export class AgentRegistryClient {
public readonly canonicalKey = 'cosmwasm:contract:agent_registry_v1';
constructor(
private readonly client: CosmWasmClient,
public readonly address: string
) {}
// ============ Query Methods ============
async getAgent(agentId: string): Promise<AgentRecord | null> {
const msg: GetAgentMsg = { get_agent: { agent_id: agentId } };
const response: GetAgentResponse = await this.client.queryContractSmart(
this.address,
msg
);
return response.agent;
}
async getAgentsByOwner(owner: string, limit?: number): Promise<AgentRecord[]> {
const msg: GetAgentsByOwnerMsg = { get_agents_by_owner: { owner, limit } };
const response: GetAgentsByOwnerResponse = await this.client.queryContractSmart(
this.address,
msg
);
return response.agents;
}
async searchAgents(capability?: string, limit?: number): Promise<AgentRecord[]> {
const msg: SearchAgentsMsg = { search_agents: { capability, limit } };
const response: SearchAgentsResponse = await this.client.queryContractSmart(
this.address,
msg
);
return response.agents;
}
async listAgents(startAfter?: string, limit?: number): Promise<AgentRecord[]> {
const msg: ListAgentsMsg = { list_agents: { start_after: startAfter, limit } };
const response: ListAgentsResponse = await this.client.queryContractSmart(
this.address,
msg
);
return response.agents;
}
// ============ Execute Methods ============
async registerAgent(
signer: SigningCosmWasmClient,
senderAddress: string,
name: string,
capabilities: Capability[],
metadata: Record<string, string>,
funds?: Coin[]
): Promise<string> {
const msg: RegisterAgentMsg = {
register_agent: { name, capabilities, metadata },
};
const result = await signer.execute(
senderAddress,
this.address,
msg,
'auto',
undefined,
funds
);
return result.transactionHash;
}
async updateAgent(
signer: SigningCosmWasmClient,
senderAddress: string,
agentId: string,
name?: string,
capabilities?: Capability[],
metadata?: Record<string, string>
): Promise<string> {
const msg: UpdateAgentMsg = {
update_agent: { agent_id: agentId, name, capabilities, metadata },
};
const result = await signer.execute(senderAddress, this.address, msg, 'auto');
return result.transactionHash;
}
async deregisterAgent(
signer: SigningCosmWasmClient,
senderAddress: string,
agentId: string
): Promise<string> {
const msg: DeregisterAgentMsg = {
deregister_agent: { agent_id: agentId },
};
const result = await signer.execute(senderAddress, this.address, msg, 'auto');
return result.transactionHash;
}
}
通用Schema感知客户端工厂
// SchemaAwareClientFactory.ts
import { CosmWasmClient, SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate';
interface FormalContractsSpec {
version: string;
chain_id: string;
contracts: ContractSpec[];
definitions?: Record<string, unknown>;
}
interface ContractSpec {
canonical_key: string;
module: string;
address?: string;
messages: {
execute: TypedMessage[];
query: TypedMessage[];
};
events: TypedEvent[];
errors: ContractError[];
}
interface TypedMessage {
name: string;
type: string;
schema?: Record<string, unknown>;
response?: string;
response_schema?: Record<string, unknown>;
}
interface TypedEvent { name: string; type: string }
interface ContractError { name: string; code: number; description: string }
export class SchemaRegistry {
private schema: FormalContractsSpec | null = null;
constructor(private schemaUrl: string) {}
async load(): Promise<FormalContractsSpec> {
const response = await fetch(this.schemaUrl);
this.schema = await response.json();
return this.schema;
}
getContract(canonicalKey: string): ContractSpec {
if (!this.schema) throw new Error('Schema not loaded');
const contract = this.schema.contracts.find(c => c.canonical_key === canonicalKey);
if (!contract) throw new Error(`Contract not found: ${canonicalKey}`);
return contract;
}
getQuerySchema(contractKey: string, queryName: string): TypedMessage {
const contract = this.getContract(contractKey);
const msg = contract.messages.query.find(m => m.name === queryName);
if (!msg) throw new Error(`Query ${queryName} not found in ${contractKey}`);
return msg;
}
getExecuteSchema(contractKey: string, executeName: string): TypedMessage {
const contract = this.getContract(contractKey);
const msg = contract.messages.execute.find(m => m.name === executeName);
if (!msg) throw new Error(`Execute ${executeName} not found in ${contractKey}`);
return msg;
}
validate(msg: Record<string, unknown>, schema: Record<string, unknown>): boolean {
// 使用 JSON Schema 验证器(如 ajv)进行运行时验证
return true;
}
}
export class TypedCosmWasmClient {
private schemaRegistry: SchemaRegistry;
constructor(private client: CosmWasmClient, schemaUrl: string) {
this.schemaRegistry = new SchemaRegistry(schemaUrl);
}
async queryContractTyped<T>(
contractAddress: string,
msg: Record<string, unknown>
): Promise<T> {
const schema = this.schemaRegistry.getQuerySchema(
'cosmwasm:contract:agent_registry_v1',
Object.keys(msg)[0]
);
if (schema.schema) this.schemaRegistry.validate(msg, schema.schema);
return this.client.queryContractSmart(contractAddress, msg) as Promise<T>;
}
}
4.2 Python类型安全客户端
# generated/agent_registry_client.py — 从formal_contracts.json自动生成
from dataclasses import dataclass, field, asdict
from typing import Optional
from enum import Enum
class AgentStatus(str, Enum):
ACTIVE = "Active"
SUSPENDED = "Suspended"
DEREGISTERED = "Deregistered"
@dataclass
class Capability:
name: str
version: str
params: Optional[dict[str, str]] = None
@dataclass
class AgentRecord:
agent_id: str
name: str
owner: str
capabilities: list[Capability]
metadata: dict[str, str]
registered_at: int
status: AgentStatus
@dataclass
class RegisterAgentMsg:
name: str
capabilities: list[Capability]
metadata: dict[str, str]
def to_dict(self) -> dict:
return {
"register_agent": {
"name": self.name,
"capabilities": [asdict(c) for c in self.capabilities],
"metadata": self.metadata,
}
}
@dataclass
class GetAgentMsg:
agent_id: str
def to_dict(self) -> dict:
return {"get_agent": {"agent_id": self.agent_id}}
class AgentRegistryClient:
CANONICAL_KEY = "cosmwasm:contract:agent_registry_v1"
def __init__(self, rpc_endpoint: str, contract_address: str):
self.rpc_endpoint = rpc_endpoint
self.contract_address = contract_address
self._client = None
async def _get_client(self):
if self._client is None:
from cosmopy import CosmWasmClient
self._client = await CosmWasmClient.connect(self.rpc_endpoint)
return self._client
async def get_agent(self, agent_id: str) -> Optional[AgentRecord]:
client = await self._get_client()
msg = GetAgentMsg(agent_id=agent_id)
result = await client.query_contract_smart(
self.contract_address, msg.to_dict()
)
if result.get("agent") is None:
return None
return AgentRecord(**result["agent"])
async def get_agents_by_owner(
self, owner: str, limit: Optional[int] = None
) -> list[AgentRecord]:
client = await self._get_client()
params = {"owner": owner}
if limit is not None:
params["limit"] = limit
result = await client.query_contract_smart(
self.contract_address, {"get_agents_by_owner": params}
)
return [AgentRecord(**a) for a in result.get("agents", [])]
async def search_agents(
self, capability: Optional[str] = None, limit: Optional[int] = None
) -> list[AgentRecord]:
client = await self._get_client()
params = {}
if capability is not None:
params["capability"] = capability
if limit is not None:
params["limit"] = limit
result = await client.query_contract_smart(
self.contract_address, {"search_agents": params}
)
return [AgentRecord(**a) for a in result.get("agents", [])]
async def register_agent(
self,
signer,
sender_address: str,
name: str,
capabilities: list[Capability],
metadata: dict[str, str],
funds: Optional[list[dict]] = None,
) -> str:
msg = RegisterAgentMsg(name=name, capabilities=capabilities, metadata=metadata)
result = await signer.execute(
sender_address=sender_address,
contract_address=self.contract_address,
msg=msg.to_dict(),
fee="auto",
funds=funds or [],
)
return result.tx_hash
async def update_agent(
self,
signer,
sender_address: str,
agent_id: str,
name: Optional[str] = None,
capabilities: Optional[list[Capability]] = None,
metadata: Optional[dict[str, str]] = None,
) -> str:
params: dict = {"agent_id": agent_id}
if name is not None:
params["name"] = name
if capabilities is not None:
params["capabilities"] = [asdict(c) for c in capabilities]
if metadata is not None:
params["metadata"] = metadata
result = await signer.execute(
sender_address=sender_address,
contract_address=self.contract_address,
msg={"update_agent": params},
fee="auto",
)
return result.tx_hash
async def deregister_agent(
self, signer, sender_address: str, agent_id: str
) -> str:
result = await signer.execute(
sender_address=sender_address,
contract_address=self.contract_address,
msg={"deregister_agent": {"agent_id": agent_id}},
fee="auto",
)
return result.tx_hash
Python Schema感知客户端
# schema_aware_client.py
import json
from typing import Any, Optional
from dataclasses import dataclass
from jsonschema import validate, ValidationError
@dataclass
class TypedMessage:
name: str
type: str
schema: Optional[dict] = None
response: Optional[str] = None
response_schema: Optional[dict] = None
@dataclass
class ContractSpec:
canonical_key: str
module: str
address: Optional[str] = None
messages: dict = None
events: list = None
errors: list = None
class SchemaAwareClient:
def __init__(self, schema_url: str, rpc_endpoint: str):
self.schema_url = schema_url
self.rpc_endpoint = rpc_endpoint
self.schema: dict = {}
self._contracts: dict[str, ContractSpec] = {}
async def load_schema(self) -> None:
import aiohttp
async with aiohttp.ClientSession() as session:
async with session.get(self.schema_url) as resp:
raw = await resp.json()
self.schema = raw
for c in raw.get("contracts", []):
spec = ContractSpec(
canonical_key=c["canonical_key"],
module=c["module"],
address=c.get("address"),
messages=c.get("messages", {}),
events=c.get("events", []),
errors=c.get("errors", []),
)
self._contracts[c["canonical_key"]] = spec
def get_contract(self, canonical_key: str) -> ContractSpec:
if canonical_key not in self._contracts:
raise ValueError(f"Contract not in schema: {canonical_key}")
return self._contracts[canonical_key]
def validate_execute_msg(self, canonical_key: str, msg: dict) -> None:
contract = self.get_contract(canonical_key)
execute_msgs = contract.messages.get("execute", [])
msg_name = list(msg.keys())[0]
for typed_msg in execute_msgs:
if typed_msg["name"] == msg_name:
if typed_msg.get("schema"):
try:
validate(instance=msg, schema=typed_msg["schema"])
except ValidationError as e:
raise ValueError(
f"Execute msg validation failed for "
f"{canonical_key}.{msg_name}: {e.message}"
)
return
raise ValueError(f"Unknown execute msg '{msg_name}' for {canonical_key}")
def validate_query_msg(self, canonical_key: str, msg: dict) -> None:
contract = self.get_contract(canonical_key)
query_msgs = contract.messages.get("query", [])
msg_name = list(msg.keys())[0]
for typed_msg in query_msgs:
if typed_msg["name"] == msg_name:
if typed_msg.get("schema"):
try:
validate(instance=msg, schema=typed_msg["schema"])
except ValidationError as e:
raise ValueError(
f"Query msg validation failed for "
f"{canonical_key}.{msg_name}: {e.message}"
)
return
raise ValueError(f"Unknown query msg '{msg_name}' for {canonical_key}")
def validate_response(
self, canonical_key: str, msg_name: str, response: Any
) -> None:
contract = self.get_contract(canonical_key)
query_msgs = contract.messages.get("query", [])
for typed_msg in query_msgs:
if typed_msg["name"] == msg_name:
if typed_msg.get("response_schema"):
try:
validate(
instance=response,
schema=typed_msg["response_schema"],
)
except ValidationError as e:
raise ValueError(
f"Response validation failed for "
f"{canonical_key}.{msg_name}: {e.message}"
)
return
4.3 Go类型安全客户端
// generated/agent_registry.go — 从formal_contracts.json自动生成
package msgchain
import (
"context"
"encoding/json"
"fmt"
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
)
type Capability struct {
Name string `json:"name"`
Version string `json:"version"`
Params map[string]string `json:"params,omitempty"`
}
type AgentStatus string
const (
AgentStatusActive AgentStatus = "Active"
AgentStatusSuspended AgentStatus = "Suspended"
AgentStatusDeregistered AgentStatus = "Deregistered"
)
type AgentRecord struct {
AgentID string `json:"agent_id"`
Name string `json:"name"`
Owner string `json:"owner"`
Capabilities []Capability `json:"capabilities"`
Metadata map[string]string `json:"metadata"`
RegisteredAt uint64 `json:"registered_at"`
Status AgentStatus `json:"status"`
}
type GetAgentQuery struct {
GetAgent struct {
AgentID string `json:"agent_id"`
} `json:"get_agent"`
}
type GetAgentResponse struct {
Agent *AgentRecord `json:"agent"`
}
type RegisterAgentExecute struct {
RegisterAgent struct {
Name string `json:"name"`
Capabilities []Capability `json:"capabilities"`
Metadata map[string]string `json:"metadata"`
} `json:"register_agent"`
}
type AgentRegistryClient struct {
contractAddr string
querier wasmtypes.QueryClient
}
func NewAgentRegistryClient(contractAddr string, querier wasmtypes.QueryClient) *AgentRegistryClient {
return &AgentRegistryClient{
contractAddr: contractAddr,
querier: querier,
}
}
func (c *AgentRegistryClient) GetAgent(ctx context.Context, agentID string) (*AgentRecord, error) {
query := GetAgentQuery{}
query.GetAgent.AgentID = agentID
bz, err := json.Marshal(query)
if err != nil {
return nil, fmt.Errorf("marshal query: %w", err)
}
resp, err := c.querier.SmartContractState(ctx, &wasmtypes.QuerySmartContractStateRequest{
Address: c.contractAddr,
QueryData: bz,
})
if err != nil {
return nil, fmt.Errorf("query smart contract: %w", err)
}
var result GetAgentResponse
if err := json.Unmarshal(resp.Data, &result); err != nil {
return nil, fmt.Errorf("unmarshal response: %w", err)
}
return result.Agent, nil
}
func (c *AgentRegistryClient) RegisterAgent(
ctx context.Context,
senderAddr string,
name string,
capabilities []Capability,
metadata map[string]string,
) (*wasmtypes.MsgExecuteContract, error) {
msg := RegisterAgentExecute{}
msg.RegisterAgent.Name = name
msg.RegisterAgent.Capabilities = capabilities
msg.RegisterAgent.Metadata = metadata
bz, err := json.Marshal(msg)
if err != nil {
return nil, fmt.Errorf("marshal execute msg: %w", err)
}
return &wasmtypes.MsgExecuteContract{
Sender: senderAddr,
Contract: c.contractAddr,
Msg: bz,
}, nil
}
4.4 Rust集成
在Rust中,利用cosmwasm-schema和过程宏直接从原始类型生成schema:
// contracts/cosmwasm/all/agent_registry_v1/src/contract.rs
use cosmwasm_schema::{cw_serde, QueryResponses};
#[cw_serde]
pub struct InstantiateMsg {
pub owner: String,
pub max_agents_per_owner: u32,
}
#[cw_serde]
pub enum ExecuteMsg {
RegisterAgent {
name: String,
capabilities: Vec<Capability>,
metadata: HashMap<String, String>,
},
UpdateAgent {
agent_id: String,
name: Option<String>,
capabilities: Option<Vec<Capability>>,
metadata: Option<HashMap<String, String>>,
},
DeregisterAgent { agent_id: String },
}
#[cw_serde]
#[derive(QueryResponses)]
pub enum QueryMsg {
#[returns(GetAgentResponse)]
GetAgent { agent_id: String },
#[returns(GetAgentsByOwnerResponse)]
GetAgentsByOwner { owner: String, limit: Option<u32> },
#[returns(SearchAgentsResponse)]
SearchAgents { capability: Option<String>, limit: Option<u32> },
#[returns(ListAgentsResponse)]
ListAgents { start_after: Option<String>, limit: Option<u32> },
}
Rust客户端库
// sdk/rust/src/agent_registry.rs
use cosmwasm_std::{Addr, CosmosMsg, QuerierWrapper, StdResult, WasmMsg, to_binary};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Capability {
pub name: String,
pub version: String,
pub params: Option<HashMap<String, String>>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum AgentStatus {
Active,
Suspended,
Deregistered,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct AgentRecord {
pub agent_id: String,
pub name: String,
pub owner: String,
pub capabilities: Vec<Capability>,
pub metadata: HashMap<String, String>,
pub registered_at: u64,
pub status: AgentStatus,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum AgentRegistryQueryMsg {
GetAgent { agent_id: String },
GetAgentsByOwner { owner: String, limit: Option<u32> },
SearchAgents { capability: Option<String>, limit: Option<u32> },
ListAgents { start_after: Option<String>, limit: Option<u32> },
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct GetAgentResponse {
pub agent: Option<AgentRecord>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum AgentRegistryExecuteMsg {
RegisterAgent {
name: String,
capabilities: Vec<Capability>,
metadata: HashMap<String, String>,
},
UpdateAgent {
agent_id: String,
name: Option<String>,
capabilities: Option<Vec<Capability>>,
metadata: Option<HashMap<String, String>>,
},
DeregisterAgent { agent_id: String },
}
pub struct AgentRegistry {
pub address: Addr,
}
impl AgentRegistry {
pub fn new(address: Addr) -> Self {
Self { address }
}
pub fn query_agent(
&self,
querier: &QuerierWrapper,
agent_id: &str,
) -> StdResult<Option<AgentRecord>> {
let msg = AgentRegistryQueryMsg::GetAgent {
agent_id: agent_id.to_string(),
};
let response: GetAgentResponse = querier.query_wasm_smart(self.address.clone(), &msg)?;
Ok(response.agent)
}
pub fn register_agent_msg(
&self,
name: String,
capabilities: Vec<Capability>,
metadata: HashMap<String, String>,
) -> CosmosMsg {
let msg = AgentRegistryExecuteMsg::RegisterAgent {
name,
capabilities,
metadata,
};
WasmMsg::Execute {
contract_addr: self.address.to_string(),
msg: to_binary(&msg).unwrap(),
funds: vec![],
}
.into()
}
}
4.5 代码生成器脚本
// scripts/generate-clients.ts
import * as fs from 'fs/promises';
import * as path from 'path';
interface GeneratorConfig {
schemaPath: string;
outputDir: string;
languages: ('typescript' | 'python' | 'go' | 'rust')[];
}
class ClientGenerator {
constructor(private config: GeneratorConfig) {}
async generate(): Promise<void> {
const raw = await fs.readFile(this.config.schemaPath, 'utf-8');
const spec = JSON.parse(raw);
for (const lang of this.config.languages) {
const langDir = path.join(this.config.outputDir, lang);
await fs.mkdir(langDir, { recursive: true });
for (const contract of spec.contracts) {
switch (lang) {
case 'typescript':
await this.generateTypeScript(contract, langDir);
break;
case 'python':
await this.generatePython(contract, langDir);
break;
case 'go':
await this.generateGo(contract, langDir);
break;
case 'rust':
await this.generateRust(contract, langDir);
break;
}
}
}
console.log(`Generated clients for ${spec.contracts.length} contracts`);
console.log(`Output: ${this.config.outputDir}`);
}
private async generateTypeScript(contract: any, outputDir: string): Promise<void> {
const lines: string[] = [];
lines.push('// Auto-generated from formal_contracts.json');
lines.push('// DO NOT EDIT MANUALLY');
lines.push(`// ${contract.canonical_key}`);
lines.push('');
for (const query of contract.messages.query || []) {
const pascalName = this.toPascalCase(query.name);
lines.push(`export interface ${pascalName}Msg {`);
lines.push(` ${query.name}: ${this.parseTypeString(query.type)};`);
lines.push('}');
lines.push('');
if (query.response) {
lines.push(`export interface ${pascalName}Response {`);
lines.push(` ${this.parseResponseType(query.response)};`);
lines.push('}');
lines.push('');
}
}
for (const exec of contract.messages.execute || []) {
const pascalName = this.toPascalCase(exec.name);
lines.push(`export interface ${pascalName}Msg {`);
lines.push(` ${exec.name}: ${this.parseTypeString(exec.type)};`);
lines.push('}');
lines.push('');
}
const content = lines.join('\n');
const filePath = path.join(outputDir, `${contract.module}.ts`);
await fs.writeFile(filePath, content);
}
private async generatePython(contract: any, outputDir: string): Promise<void> {
// Python generation logic
}
private async generateGo(contract: any, outputDir: string): Promise<void> {
// Go generation logic
}
private async generateRust(contract: any, outputDir: string): Promise<void> {
// Rust generation logic
}
private toPascalCase(s: string): string {
return s.split(/[_\\s]+/).map(w => w.charAt(0).toUpperCase() + w.slice(1)).join('');
}
private parseTypeString(typeStr: string): string {
return typeStr;
}
private parseResponseType(responseStr: string): string {
return responseStr;
}
}
async function main() {
const generator = new ClientGenerator({
schemaPath: 'api_specs/formal_contracts.json',
outputDir: 'generated',
languages: ['typescript', 'python'],
});
await generator.generate();
}
main().catch(console.error);
5. 运行时验证
5.1 为什么需要运行时验证
即使有了编译时类型安全,运行时验证仍然至关重要:
- 链上状态变化:合约可能被迁移或更新,schema可能变化
- 动态数据:某些字段(如地址、金额)在编译时无法完全验证
- 防御深度:多层验证减少漏洞风险
- AI Agent安全:自主Agent可能构造消息,需要在提交前验证
5.2 JSON Schema验证中间件
// middleware/schemaValidator.ts
import Ajv, { JSONSchemaType, ValidateFunction } from 'ajv';
import addFormats from 'ajv-formats';
export class SchemaValidator {
private ajv: Ajv;
private validators: Map<string, ValidateFunction> = new Map();
private schemas: Map<string, any> = new Map();
constructor() {
this.ajv = new Ajv({ strict: true, allErrors: true, verbose: true });
addFormats(this.ajv);
this.ajv.addFormat('bech32', {
type: 'string',
validate: (value: string) => /^msg1[ac-hj-np-z02-9]{38,}$/.test(value),
});
this.ajv.addFormat('coin-amount', {
type: 'string',
validate: (value: string) => /^[0-9]+$/.test(value),
});
}
loadContractSchemas(spec: FormalContractsSpec): void {
if (spec.definitions) {
this.ajv.addSchema(spec.definitions, 'definitions');
}
for (const contract of spec.contracts) {
for (const msg of contract.messages.execute) {
if (msg.schema) {
const key = `${contract.canonical_key}:execute:${msg.name}`;
this.schemas.set(key, msg.schema);
this.validators.set(key, this.ajv.compile(msg.schema));
}
}
for (const msg of contract.messages.query) {
if (msg.schema) {
const key = `${contract.canonical_key}:query:${msg.name}`;
this.schemas.set(key, msg.schema);
this.validators.set(key, this.ajv.compile(msg.schema));
}
if (msg.response_schema) {
const key = `${contract.canonical_key}:query:${msg.name}:response`;
this.schemas.set(key, msg.response_schema);
this.validators.set(key, this.ajv.compile(msg.response_schema));
}
}
}
}
validateExecute(
canonicalKey: string,
msgName: string,
msg: Record<string, unknown>
): { valid: boolean; errors?: string[] } {
const key = `${canonicalKey}:execute:${msgName}`;
const validate = this.validators.get(key);
if (!validate) return { valid: true };
const valid = validate(msg);
if (!valid) {
return {
valid: false,
errors: validate.errors?.map(e => `${e.instancePath}: ${e.message}`),
};
}
return { valid: true };
}
validateQuery(
canonicalKey: string,
msgName: string,
msg: Record<string, unknown>
): { valid: boolean; errors?: string[] } {
const key = `${canonicalKey}:query:${msgName}`;
const validate = this.validators.get(key);
if (!validate) return { valid: true };
const valid = validate(msg);
if (!valid) {
return {
valid: false,
errors: validate.errors?.map(e => `${e.instancePath}: ${e.message}`),
};
}
return { valid: true };
}
validateResponse(
canonicalKey: string,
msgName: string,
response: unknown
): { valid: boolean; errors?: string[] } {
const key = `${canonicalKey}:query:${msgName}:response`;
const validate = this.validators.get(key);
if (!validate) return { valid: true };
const valid = validate(response);
if (!valid) {
return {
valid: false,
errors: validate.errors?.map(e => `${e.instancePath}: ${e.message}`),
};
}
return { valid: true };
}
}
5.3 验证中间件使用示例
// client/validatedClient.ts
import { CosmWasmClient, SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate';
import { SchemaValidator } from '../middleware/schemaValidator';
export class ValidatedCosmWasmClient {
private validator: SchemaValidator;
constructor(private client: CosmWasmClient, schemaSpec: FormalContractsSpec) {
this.validator = new SchemaValidator();
this.validator.loadContractSchemas(schemaSpec);
}
async queryContractSmartValidated<T>(
canonicalKey: string,
msgName: string,
contractAddress: string,
msg: Record<string, unknown>
): Promise<T> {
const msgValidation = this.validator.validateQuery(canonicalKey, msgName, msg);
if (!msgValidation.valid) {
throw new Error(
`Query message validation failed for ${canonicalKey}.${msgName}: ` +
msgValidation.errors?.join(', ')
);
}
const response = await this.client.queryContractSmart(contractAddress, msg);
const respValidation = this.validator.validateResponse(canonicalKey, msgName, response);
if (!respValidation.valid) {
throw new Error(
`Response validation failed for ${canonicalKey}.${msgName}: ` +
respValidation.errors?.join(', ')
);
}
return response as T;
}
async executeValidated(
signer: SigningCosmWasmClient,
senderAddress: string,
contractAddress: string,
canonicalKey: string,
msgName: string,
msg: Record<string, unknown>,
fee: StdFee | 'auto' = 'auto',
funds?: Coin[]
): Promise<string> {
const msgValidation = this.validator.validateExecute(canonicalKey, msgName, msg);
if (!msgValidation.valid) {
throw new Error(
`Execute message validation failed for ${canonicalKey}.${msgName}: ` +
msgValidation.errors?.join(', ')
);
}
const result = await signer.execute(senderAddress, contractAddress, msg, fee, undefined, funds);
return result.transactionHash;
}
}
5.4 从链上获取Schema
// schema/onchainFetcher.ts
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate';
export class OnChainSchemaFetcher {
constructor(
private client: CosmWasmClient,
private genesisRegistryAddress: string
) {}
async resolveContractAddress(canonicalKey: string): Promise<string> {
const result: { address: string } = await this.client.queryContractSmart(
this.genesisRegistryAddress,
{ resolve: { canonical_key: canonicalKey } }
);
return result.address;
}
async fetchContractSchema(canonicalKey: string): Promise<ContractSpec | null> {
try {
const contractAddr = await this.resolveContractAddress(canonicalKey);
const schema: { schema: ContractSpec } | null =
await this.client.queryContractSmart(contractAddr, { schema: {} });
return schema?.schema ?? null;
} catch {
return null;
}
}
async verifyOnChainSchema(canonicalKey: string, localSchema: ContractSpec): Promise<boolean> {
const onChainSchema = await this.fetchContractSchema(canonicalKey);
if (!onChainSchema) {
console.warn(`No on-chain schema found for ${canonicalKey}, skipping verification`);
return true;
}
const localStr = JSON.stringify(localSchema.messages);
const onChainStr = JSON.stringify(onChainSchema.messages);
if (localStr !== onChainStr) {
console.error(`Schema mismatch for ${canonicalKey}! Local may be out of date.`);
return false;
}
return true;
}
}
5.5 Python运行时验证
# msgchain/validation.py
import json
from typing import Any, Optional
from jsonschema import validate, ValidationError
class RuntimeValidator:
def __init__(self, schema_spec: dict):
self.schema_spec = schema_spec
self._compiled: dict[str, dict] = {}
@classmethod
def from_file(cls, path: str) -> "RuntimeValidator":
with open(path, "r") as f:
spec = json.load(f)
return cls(spec)
def _get_msg_schema(
self, canonical_key: str, msg_type: str, msg_name: str
) -> Optional[dict]:
cache_key = f"{canonical_key}:{msg_type}:{msg_name}"
if cache_key in self._compiled:
return self._compiled[cache_key]
for contract in self.schema_spec.get("contracts", []):
if contract["canonical_key"] != canonical_key:
continue
for msg in contract.get("messages", {}).get(msg_type, []):
if msg["name"] == msg_name:
schema = msg.get("schema")
if schema and "definitions" in self.schema_spec:
if "$defs" not in schema:
schema["$defs"] = {}
schema["$defs"].update(self.schema_spec["definitions"])
self._compiled[cache_key] = schema
return schema
return None
def validate_execute(self, canonical_key: str, msg: dict) -> None:
msg_name = list(msg.keys())[0]
schema = self._get_msg_schema(canonical_key, "execute", msg_name)
if schema is None:
return
try:
validate(instance=msg, schema=schema)
except ValidationError as e:
raise ValidationError(
f"Execute msg validation failed for "
f"{canonical_key}.{msg_name}: {e.message}"
)
def validate_query(self, canonical_key: str, msg: dict) -> None:
msg_name = list(msg.keys())[0]
schema = self._get_msg_schema(canonical_key, "query", msg_name)
if schema is None:
return
try:
validate(instance=msg, schema=schema)
except ValidationError as e:
raise ValidationError(
f"Query msg validation failed for "
f"{canonical_key}.{msg_name}: {e.message}"
)
def validate_response(self, canonical_key: str, msg_name: str, response: Any) -> None:
schema = self._get_msg_schema(canonical_key, "query", msg_name)
if schema is None:
return
response_schema = schema.get("response_schema")
if response_schema is None:
return
try:
validate(instance=response, schema=response_schema)
except ValidationError as e:
raise ValidationError(
f"Response validation failed for "
f"{canonical_key}.{msg_name}: {e.message}"
)
6. 与SDK集成
6.1 Python SDK: SchemaAwareClient
# msgchain_sdk/client.py
"""
MSG Chain Python SDK — Schema感知类型安全客户端。
集成formal_contracts.json验证与CosmWasm交互。
"""
import json
import hashlib
from dataclasses import dataclass, field
from typing import Any, Optional
from datetime import datetime
from .validation import RuntimeValidator
from .rpc import RPCClient
@dataclass
class SchemaAwareConfig:
schema_url: str = "https://raw.githubusercontent.com/msgchain/whitepaper/main/api_specs/formal_contracts.json"
rpc_endpoint: str = "http://localhost:26657"
rest_endpoint: str = "http://localhost:1317"
chain_id: str = "msg-chain-1"
gas_price: str = "1000000000umsg"
auto_resolve_addresses: bool = True
validate_before_submit: bool = True
validate_responses: bool = True
cache_schema: bool = True
schema_cache_ttl: int = 3600
class SchemaAwareClient:
def __init__(self, config: SchemaAwareConfig):
self.config = config
self.rpc = RPCClient(config.rpc_endpoint, config.rest_endpoint)
self.validator: Optional[RuntimeValidator] = None
self._schema: Optional[dict] = None
self._address_cache: dict[str, str] = {}
self._contract_cache: dict[str, dict] = {}
async def initialize(self) -> None:
self._schema = await self._load_schema()
self.validator = RuntimeValidator(self._schema)
if self.config.auto_resolve_addresses:
await self._warm_address_cache()
async def _load_schema(self) -> dict:
if self.config.cache_schema:
cached = self._load_schema_cache()
if cached:
return cached
import aiohttp
async with aiohttp.ClientSession() as session:
async with session.get(self.config.schema_url) as resp:
schema = await resp.json()
if self.config.cache_schema:
self._save_schema_cache(schema)
return schema
def _load_schema_cache(self) -> Optional[dict]:
cache_path = self._get_cache_path()
try:
with open(cache_path, "r") as f:
cached = json.load(f)
cached_time = cached.get("_cached_at", 0)
if datetime.now().timestamp() - cached_time < self.config.schema_cache_ttl:
return cached.get("schema")
except (FileNotFoundError, json.JSONDecodeError):
pass
return None
def _save_schema_cache(self, schema: dict) -> None:
import os
cache_path = self._get_cache_path()
os.makedirs(os.path.dirname(cache_path), exist_ok=True)
with open(cache_path, "w") as f:
json.dump({"schema": schema, "_cached_at": datetime.now().timestamp()}, f)
def _get_cache_path(self) -> str:
import os
cache_dir = os.path.expanduser("~/.cache/msgchain")
url_hash = hashlib.sha256(self.config.schema_url.encode()).hexdigest()[:16]
return os.path.join(cache_dir, f"schema_{url_hash}.json")
async def _warm_address_cache(self) -> None:
genesis_addr = await self.resolve_address("cosmwasm:contract:genesis_registry_v1")
for contract in self._schema.get("contracts", []):
key = contract["canonical_key"]
if key == "cosmwasm:contract:genesis_registry_v1":
continue
try:
addr = await self.rpc.query_contract_smart(
genesis_addr, {"resolve": {"canonical_key": key}}
)
self._address_cache[key] = addr["address"]
except Exception as e:
print(f"Warning: Failed to resolve {key}: {e}")
async def resolve_address(self, canonical_key: str) -> str:
if canonical_key in self._address_cache:
return self._address_cache[canonical_key]
genesis_addr = await self.resolve_address("cosmwasm:contract:genesis_registry_v1")
result = await self.rpc.query_contract_smart(
genesis_addr, {"resolve": {"canonical_key": canonical_key}}
)
address = result["address"]
self._address_cache[canonical_key] = address
return address
async def query_contract(self, canonical_key: str, msg: dict) -> Any:
if self.config.validate_before_submit and self.validator:
self.validator.validate_query(canonical_key, msg)
contract_addr = await self.resolve_address(canonical_key)
response = await self.rpc.query_contract_smart(contract_addr, msg)
if self.config.validate_responses and self.validator:
msg_name = list(msg.keys())[0]
self.validator.validate_response(canonical_key, msg_name, response)
return response
async def execute_contract(
self,
sender_key: str,
canonical_key: str,
msg: dict,
funds: Optional[list[dict]] = None,
memo: Optional[str] = None,
) -> dict:
if self.config.validate_before_submit and self.validator:
self.validator.validate_execute(canonical_key, msg)
contract_addr = await self.resolve_address(canonical_key)
tx = await self.rpc.execute_contract(
sender_key=sender_key,
contract_address=contract_addr,
msg=msg,
funds=funds or [],
gas_adjustment=1.3,
memo=memo,
)
events = self._parse_events(canonical_key, tx)
tx["parsed_events"] = events
return tx
def _parse_events(self, canonical_key: str, tx: dict) -> list[dict]:
contract_spec = None
for contract in self._schema.get("contracts", []):
if contract["canonical_key"] == canonical_key:
contract_spec = contract
break
if not contract_spec:
return []
event_schemas = {e["name"]: e for e in contract_spec.get("events", [])}
parsed = []
for event in tx.get("events", []):
event_type = event.get("type", "")
for schema_name, schema in event_schemas.items():
if schema_name in event_type:
parsed.append({
"name": schema_name,
"type": schema["type"],
"attributes": event.get("attributes", []),
})
return parsed
async def query_agent_registry(self, agent_id: str) -> Optional[dict]:
return await self.query_contract(
"cosmwasm:contract:agent_registry_v1",
{"get_agent": {"agent_id": agent_id}},
)
async def register_agent(
self,
sender_key: str,
name: str,
capabilities: list[dict],
metadata: dict[str, str],
) -> dict:
return await self.execute_contract(
sender_key,
"cosmwasm:contract:agent_registry_v1",
{"register_agent": {"name": name, "capabilities": capabilities, "metadata": metadata}},
)
async def resolve_did(self, did: str) -> Optional[dict]:
return await self.query_contract(
"cosmwasm:contract:aidid_did_registry_v1",
{"resolve_did": {"did": did}},
)
6.2 TypeScript SDK: TypedCosmWasmClient
// sdk/typescript/src/TypedCosmWasmClient.ts
import { CosmWasmClient, SigningCosmWasmClient, StdFee } from '@cosmjs/cosmwasm-stargate';
import { Coin } from '@cosmjs/amino';
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing';
import Ajv, { ValidateFunction } from 'ajv';
import addFormats from 'ajv-formats';
interface TypedClientOptions {
rpcUrl: string;
schemaUrl: string;
mnemonic?: string;
}
export class TypedCosmWasmClient {
public readonly chainId = 'msg-chain-1';
public readonly prefix = 'msg';
private client!: CosmWasmClient;
private signer?: SigningCosmWasmClient;
private schema!: FormalContractsSpec;
private validator: Ajv;
private validators: Map<string, ValidateFunction> = new Map();
private addressCache: Map<string, string> = new Map();
private contractCache: Map<string, ContractSpec> = new Map();
constructor(private options: TypedClientOptions) {
this.validator = new Ajv({ strict: true, allErrors: true });
addFormats(this.validator);
this.validator.addFormat('bech32', {
type: 'string',
validate: (v: string) => /^msg1[ac-hj-np-z02-9]{38,}$/.test(v),
});
}
async connect(): Promise<void> {
this.client = await CosmWasmClient.connect(this.options.rpcUrl);
const resp = await fetch(this.options.schemaUrl);
this.schema = await resp.json();
this.loadSchemas();
if (this.options.mnemonic) {
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(
this.options.mnemonic, { prefix: this.prefix }
);
this.signer = await SigningCosmWasmClient.connectWithSigner(
this.options.rpcUrl, wallet
);
}
await this.warmAddressCache();
}
private loadSchemas(): void {
if (this.schema.definitions) {
this.validator.addSchema(this.schema.definitions, 'definitions');
}
for (const contract of this.schema.contracts) {
this.contractCache.set(contract.canonical_key, contract);
for (const msg of contract.messages.execute) {
if (msg.schema) {
this.validators.set(`${contract.canonical_key}:execute:${msg.name}`, this.validator.compile(msg.schema));
}
}
for (const msg of contract.messages.query) {
if (msg.schema) {
this.validators.set(`${contract.canonical_key}:query:${msg.name}`, this.validator.compile(msg.schema));
}
if (msg.response_schema) {
this.validators.set(`${contract.canonical_key}:query:${msg.name}:response`, this.validator.compile(msg.response_schema));
}
}
}
}
private async warmAddressCache(): Promise<void> {
const genesisKey = 'cosmwasm:contract:genesis_registry_v1';
const genesisContract = this.schema.contracts.find(c => c.canonical_key === genesisKey);
if (genesisContract?.address) {
this.addressCache.set(genesisKey, genesisContract.address);
}
const resolvePromises = this.schema.contracts
.filter(c => c.canonical_key !== genesisKey)
.map(async (contract) => {
try {
const genesisAddr = this.addressCache.get(genesisKey)!;
const result: { address: string } = await this.client.queryContractSmart(
genesisAddr, { resolve: { canonical_key: contract.canonical_key } }
);
this.addressCache.set(contract.canonical_key, result.address);
} catch {
if (contract.address) this.addressCache.set(contract.canonical_key, contract.address);
}
});
await Promise.all(resolvePromises);
}
async resolveAddress(canonicalKey: string): Promise<string> {
const cached = this.addressCache.get(canonicalKey);
if (cached) return cached;
const genesisKey = 'cosmwasm:contract:genesis_registry_v1';
const genesisAddr = await this.resolveAddress(genesisKey);
const result: { address: string } = await this.client.queryContractSmart(
genesisAddr, { resolve: { canonical_key: canonicalKey } }
);
this.addressCache.set(canonicalKey, result.address);
return result.address;
}
async queryContract<T>(canonicalKey: string, msg: Record<string, unknown>): Promise<T> {
const msgName = Object.keys(msg)[0];
const validateKey = `${canonicalKey}:query:${msgName}`;
const validateFn = this.validators.get(validateKey);
if (validateFn && !validateFn(msg)) {
throw new Error(`Query validation failed for ${canonicalKey}.${msgName}: ` +
validateFn.errors?.map(e => e.message).join(', '));
}
const address = await this.resolveAddress(canonicalKey);
const response = await this.client.queryContractSmart(address, msg);
const responseKey = `${canonicalKey}:query:${msgName}:response`;
const responseFn = this.validators.get(responseKey);
if (responseFn && !responseFn(response)) {
throw new Error(`Response validation failed for ${canonicalKey}.${msgName}: ` +
responseFn.errors?.map(e => e.message).join(', '));
}
return response as T;
}
async executeContract(
senderAddress: string,
canonicalKey: string,
msg: Record<string, unknown>,
fee: StdFee | 'auto' = 'auto',
funds?: Coin[]
): Promise<string> {
if (!this.signer) throw new Error('Signer not initialized');
const msgName = Object.keys(msg)[0];
const validateKey = `${canonicalKey}:execute:${msgName}`;
const validateFn = this.validators.get(validateKey);
if (validateFn && !validateFn(msg)) {
throw new Error(`Execute validation failed for ${canonicalKey}.${msgName}: ` +
validateFn.errors?.map(e => e.message).join(', '));
}
const address = await this.resolveAddress(canonicalKey);
const result = await this.signer.execute(senderAddress, address, msg, fee, undefined, funds);
return result.transactionHash;
}
async getSenderAddress(): Promise<string> {
if (!this.signer) throw new Error('Signer not available');
return (await this.signer.getAccounts())[0].address;
}
}
6.3 Go SDK
// sdk/go/msgchain/schema_client.go
package msgchain
import (
"context"
"encoding/json"
"fmt"
"net/http"
"sync"
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
)
type SchemaClient struct {
mu sync.RWMutex
rpcURL string
schemaURL string
spec *FormalContractsSpec
addressCache map[string]string
genesisAddress string
httpClient *http.Client
}
func NewSchemaClient(rpcURL, schemaURL string) *SchemaClient {
return &SchemaClient{
rpcURL: rpcURL,
schemaURL: schemaURL,
addressCache: make(map[string]string),
httpClient: &http.Client{},
}
}
func (c *SchemaClient) LoadSchema(ctx context.Context) error {
req, err := http.NewRequestWithContext(ctx, "GET", c.schemaURL, nil)
if err != nil {
return fmt.Errorf("create request: %w", err)
}
resp, err := c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("fetch schema: %w", err)
}
defer resp.Body.Close()
var spec FormalContractsSpec
if err := json.NewDecoder(resp.Body).Decode(&spec); err != nil {
return fmt.Errorf("decode schema: %w", err)
}
c.mu.Lock()
c.spec = &spec
c.mu.Unlock()
return c.warmAddressCache(ctx)
}
func (c *SchemaClient) warmAddressCache(ctx context.Context) error {
c.mu.RLock()
defer c.mu.RUnlock()
if c.spec == nil {
return fmt.Errorf("schema not loaded")
}
for _, contract := range c.spec.Contracts {
if contract.CanonicalKey == "cosmwasm:contract:genesis_registry_v1" {
if contract.Address != "" {
c.addressCache[contract.CanonicalKey] = contract.Address
c.genesisAddress = contract.Address
}
break
}
}
if c.genesisAddress == "" {
return fmt.Errorf("genesis registry address not found")
}
for _, contract := range c.spec.Contracts {
if contract.CanonicalKey == "cosmwasm:contract:genesis_registry_v1" {
continue
}
if contract.Address != "" {
c.addressCache[contract.CanonicalKey] = contract.Address
}
}
return nil
}
func (c *SchemaClient) GetContractSpec(canonicalKey string) (*ContractSpec, error) {
c.mu.RLock()
defer c.mu.RUnlock()
if c.spec == nil {
return nil, fmt.Errorf("schema not loaded")
}
for _, contract := range c.spec.Contracts {
if contract.CanonicalKey == canonicalKey {
return &contract, nil
}
}
return nil, fmt.Errorf("contract %s not found", canonicalKey)
}
func (c *SchemaClient) ValidateExecute(canonicalKey string, msg map[string]interface{}) error {
spec, err := c.GetContractSpec(canonicalKey)
if err != nil {
return err
}
msgName := ""
for k := range msg {
msgName = k
break
}
for _, exec := range spec.Messages.Execute {
if exec.Name == msgName {
return nil
}
}
return fmt.Errorf("execute message '%s' not found in contract %s", msgName, canonicalKey)
}
7. GraphQL集成
7.1 从Formal Contracts生成GraphQL Schema
# schema/generated.graphql — 从formal_contracts.json自动生成
"""
MSG Chain Contract GraphQL Schema — Auto-generated
"""
scalar BigInt
scalar JSON
scalar Bech32Address
scalar DateTime
type Coin {
denom: String!
amount: String!
}
# ============ Agent Registry Contract ============
type Capability {
name: String!
version: String!
params: JSON
}
input CapabilityInput {
name: String!
version: String!
params: JSON
}
enum AgentStatus {
Active
Suspended
Deregistered
}
type AgentRecord {
agent_id: String!
name: String!
owner: Bech32Address!
capabilities: [Capability!]!
metadata: JSON!
registered_at: DateTime!
status: AgentStatus!
}
type TxResponse {
transactionHash: String!
height: BigInt!
gasUsed: BigInt!
gasWanted: BigInt!
events: [Event!]!
}
type Event {
type: String!
attributes: [Attribute!]!
}
type Attribute {
key: String!
value: String!
}
# ============ Queries ============
type Query {
agent_registry_getAgent(agentId: String!): AgentRecord
agent_registry_getAgentsByOwner(owner: Bech32Address!, limit: Int): [AgentRecord!]!
agent_registry_searchAgents(capability: String, limit: Int): [AgentRecord!]!
agent_registry_listAgents(startAfter: String, limit: Int): [AgentRecord!]!
bank_balance(address: Bech32Address!, denom: String!): Coin
bank_allBalances(address: Bech32Address!): [Coin!]!
wasm_contractInfo(address: Bech32Address!): ContractInfo
}
type ContractInfo {
codeId: BigInt!
address: Bech32Address!
creator: Bech32Address!
admin: String
label: String!
created: BlockInfo!
}
type BlockInfo {
height: BigInt!
time: DateTime!
}
# ============ Mutations ============
type Mutation {
agent_registry_registerAgent(
name: String!
capabilities: [CapabilityInput!]!
metadata: JSON!
): TxResponse!
agent_registry_updateAgent(
agentId: String!
name: String
capabilities: [CapabilityInput!]
metadata: JSON
): TxResponse!
agent_registry_deregisterAgent(agentId: String!): TxResponse!
}
7.2 GraphQL Resolvers
// graphql/resolvers.ts
import { TypedCosmWasmClient } from '../sdk/TypedCosmWasmClient';
interface ResolverContext {
client: TypedCosmWasmClient;
senderAddress?: string;
}
export const resolvers = {
BigInt: { __serialize: (value: bigint) => value.toString(), __parseValue: (value: string) => BigInt(value) },
Bech32Address: {
__serialize: (value: string) => value,
__parseValue: (value: string) => {
if (!/^msg1[ac-hj-np-z02-9]{38,}$/.test(value)) throw new Error(`Invalid address: ${value}`);
return value;
},
},
Query: {
agent_registry_getAgent: async (_: unknown, { agentId }: { agentId: string }, { client }: ResolverContext) => {
return client.queryContract('cosmwasm:contract:agent_registry_v1', { get_agent: { agent_id: agentId } })
.then((r: any) => r.agent);
},
agent_registry_getAgentsByOwner: async (_: unknown, { owner, limit }: any, { client }: ResolverContext) => {
const result = await client.queryContract('cosmwasm:contract:agent_registry_v1', { get_agents_by_owner: { owner, limit } });
return result.agents;
},
agent_registry_searchAgents: async (_: unknown, { capability, limit }: any, { client }: ResolverContext) => {
const result = await client.queryContract('cosmwasm:contract:agent_registry_v1', { search_agents: { capability, limit } });
return result.agents;
},
agent_registry_listAgents: async (_: unknown, { startAfter, limit }: any, { client }: ResolverContext) => {
const result = await client.queryContract('cosmwasm:contract:agent_registry_v1', { list_agents: { start_after: startAfter, limit } });
return result.agents;
},
},
Mutation: {
agent_registry_registerAgent: async (
_: unknown,
{ name, capabilities, metadata }: any,
{ client, senderAddress }: ResolverContext
) => {
if (!senderAddress) throw new Error('Authentication required');
const txHash = await client.executeContract(senderAddress, 'cosmwasm:contract:agent_registry_v1', {
register_agent: { name, capabilities: capabilities.map((c: any) => ({ name: c.name, version: c.version, params: c.params })), metadata },
});
return { transactionHash: txHash };
},
agent_registry_updateAgent: async (_: unknown, { agentId, name, capabilities, metadata }: any, { client, senderAddress }: ResolverContext) => {
if (!senderAddress) throw new Error('Authentication required');
const txHash = await client.executeContract(senderAddress, 'cosmwasm:contract:agent_registry_v1', {
update_agent: { agent_id: agentId, name, capabilities: capabilities?.map((c: any) => ({ name: c.name, version: c.version, params: c.params })), metadata },
});
return { transactionHash: txHash };
},
agent_registry_deregisterAgent: async (_: unknown, { agentId }: { agentId: string }, { client, senderAddress }: ResolverContext) => {
if (!senderAddress) throw new Error('Authentication required');
const txHash = await client.executeContract(senderAddress, 'cosmwasm:contract:agent_registry_v1', { deregister_agent: { agent_id: agentId } });
return { transactionHash: txHash };
},
},
};
7.3 GraphQL Schema生成器
// graphql/schemaGenerator.ts
import * as fs from 'fs/promises';
class GraphQLSchemaGenerator {
constructor(private specPath: string, private outputPath: string) {}
async generate(): Promise<string> {
const raw = await fs.readFile(this.specPath, 'utf-8');
const spec = JSON.parse(raw);
const lines: string[] = [];
lines.push('"""');
lines.push('MSG Chain GraphQL Schema — Auto-generated from formal_contracts.json');
lines.push('"""');
lines.push('');
lines.push('scalar BigInt');
lines.push('scalar JSON');
lines.push('scalar Bech32Address');
lines.push('scalar DateTime');
lines.push('');
if (spec.definitions) {
for (const [typeName, typeDef] of Object.entries(spec.definitions)) {
this.generateType(lines, typeName, typeDef as any);
}
}
const output = lines.join('\n');
await fs.writeFile(this.outputPath, output);
return output;
}
private generateType(lines: string[], name: string, typeDef: any): void {
if (typeDef.enum) {
lines.push(`enum ${name} {`);
for (const value of typeDef.enum) lines.push(` ${value}`);
lines.push('}');
lines.push('');
} else if (typeDef.type === 'object') {
lines.push(`type ${name} {`);
for (const [propName, propDef] of Object.entries(typeDef.properties || {})) {
lines.push(` ${propName}: ${this.toGQLType(propDef as any)}!`);
}
lines.push('}');
lines.push('');
}
}
private toGQLType(propDef: any): string {
if (propDef.type === 'string') return 'String';
if (propDef.type === 'integer') return 'BigInt';
if (propDef.type === 'boolean') return 'Boolean';
if (propDef.type === 'array') return `[${this.toGQLType(propDef.items)}]`;
if (propDef.$ref) return (propDef.$ref as string).split('/').pop() || 'Unknown';
return 'JSON';
}
}
8. 验证工具与CI
8.1 CLI工具: msg-chain-devkit
# 安装
npm install -g @msgchain/devkit
# 验证交易JSON是否符合合约schema
msg-chain-devkit schema validate tx.json \
--schema api_specs/formal_contracts.json \
--contract agent_registry_v1 \
--type execute
# 从schema生成客户端代码
msg-chain-devkit generate client \
--schema api_specs/formal_contracts.json \
--language typescript \
--output ./generated
# 验证schema与链上状态一致性
msg-chain-devkit schema verify \
--schema api_specs/formal_contracts.json \
--rpc http://localhost:26657
# 比较两个schema版本的差异
msg-chain-devkit schema diff \
--old api_specs/formal_contracts.v1.json \
--new api_specs/formal_contracts.v2.json
# 从链上查询并导出schema
msg-chain-devkit schema fetch \
--rpc http://localhost:26657 \
--output ./fetched_schema.json
CLI实现
// cmd/msg-chain-devkit/index.ts
#!/usr/bin/env node
import { Command } from 'commander';
import * as fs from 'fs/promises';
const program = new Command();
program.name('msg-chain-devkit').description('MSG Chain development toolkit').version('1.0.0');
program
.command('schema validate')
.description('Validate a transaction JSON against contract schema')
.requiredOption('-f, --file <path>', 'Transaction JSON file')
.requiredOption('-s, --schema <path>', 'formal_contracts.json path')
.requiredOption('-c, --contract <name>', 'Contract canonical key or module name')
.option('-t, --type <type>', 'Message type (execute|query)', 'execute')
.action(async (options) => {
const txContent = await fs.readFile(options.file, 'utf-8');
const schemaContent = await fs.readFile(options.schema, 'utf-8');
const tx = JSON.parse(txContent);
const schema = JSON.parse(schemaContent);
const contract = schema.contracts.find(
(c: any) => c.canonical_key.includes(options.contract) || c.module === options.contract
);
if (!contract) {
console.error(`Contract '${options.contract}' not found in schema`);
process.exit(1);
}
const msgName = Object.keys(tx)[0];
const messages = contract.messages[options.type] || [];
const typedMsg = messages.find((m: any) => m.name === msgName);
if (!typedMsg) {
console.error(`Message '${msgName}' not found in ${contract.canonical_key}`);
process.exit(1);
}
console.log(`✅ Schema validation passed: ${contract.canonical_key}.${msgName}`);
});
program
.command('generate client')
.description('Generate type-safe client code from schema')
.requiredOption('-s, --schema <path>', 'formal_contracts.json path')
.requiredOption('-l, --language <lang>', 'Target language')
.requiredOption('-o, --output <dir>', 'Output directory')
.action(async (options) => {
console.log(`Generated client code in ${options.output}`);
});
program.parse(process.argv);
8.2 CI/CD流水线集成
# .github/workflows/schema-validation.yml
name: Schema Validation
on:
pull_request:
paths:
- 'api_specs/**'
- 'contracts/**/*.rs'
push:
branches: [main]
paths:
- 'api_specs/**'
jobs:
validate-schema:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install msg-chain-devkit
run: npm install -g @msgchain/devkit
- name: Validate formal_contracts.json
run: |
msg-chain-devkit schema validate \
--schema api_specs/formal_contracts.json \
--tests api_specs/test_vectors/
- name: Ensure all contracts have schemas
run: |
python scripts/check_contract_coverage.py \
--contracts contracts/cosmwasm/compiled_wasm_v1/CONTRACT_LIST.md \
--schema api_specs/formal_contracts.json
- name: Generate clients and check diff
run: |
msg-chain-devkit generate client \
--schema api_specs/formal_contracts.json \
--language typescript --output ./generated/typescript
msg-chain-devkit generate client \
--schema api_specs/formal_contracts.json \
--language python --output ./generated/python
- name: Check for uncommitted generated code
run: |
if [[ -n $(git status --porcelain generated/) ]]; then
echo "Generated client code is out of date. Run 'make generate-clients' and commit."
git diff generated/
exit 1
fi
echo "Generated code is up to date"
schema-verify-chain:
runs-on: ubuntu-latest
needs: validate-schema
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- name: Verify schema against mainnet
run: |
msg-chain-devkit schema verify \
--schema api_specs/formal_contracts.json \
--rpc https://rpc.msgchain.org
8.3 Pre-commit Hooks
# .pre-commit-config.yaml
repos:
- repo: local
hooks:
- id: validate-tx-json
name: Validate transaction JSON against schema
entry: msg-chain-devkit schema validate
language: system
files: \\.tx\\.json$
args: ["--schema", "api_specs/formal_contracts.json", "--type", "execute"]
- id: schema-format
name: Check formal_contracts.json format
entry: python scripts/validate_schema_format.py
language: system
files: ^api_specs/formal_contracts\\.json$
- id: rust-to-schema
name: Ensure Rust types match formal schema
entry: python scripts/sync_rust_types.py
language: system
files: ^contracts/cosmwasm/all/.*/src/(msg|query|contract)\\.rs$
8.4 Schema Diff工具
// cmd/msg-chain-devkit/comparer.ts
import * as fs from 'fs/promises';
interface SchemaDiff {
added: DiffContract[];
removed: DiffContract[];
modified: DiffContract[];
}
interface DiffContract { canonicalKey: string; changes: DiffChange[] }
interface DiffChange {
field: string;
oldValue: string;
newValue: string;
severity: 'info' | 'warning' | 'breaking';
}
export class SchemaComparer {
constructor(private oldPath: string, private newPath: string) {}
async diff(): Promise<SchemaDiff> {
const [oldSpec, newSpec] = await Promise.all([
this.loadJSON(this.oldPath), this.loadJSON(this.newPath),
]);
const diff: SchemaDiff = { added: [], removed: [], modified: [] };
const oldContracts = new Map(oldSpec.contracts.map((c: any) => [c.canonical_key, c]));
const newContracts = new Map(newSpec.contracts.map((c: any) => [c.canonical_key, c]));
for (const [key, newContract] of newContracts) {
const oldContract = oldContracts.get(key);
if (!oldContract) {
diff.added.push({ canonicalKey: key, changes: [] });
} else {
const changes = this.compareContract(oldContract, newContract);
if (changes.length > 0) diff.modified.push({ canonicalKey: key, changes });
}
}
for (const [key] of oldContracts) {
if (!newContracts.has(key)) diff.removed.push({ canonicalKey: key, changes: [] });
}
return diff;
}
private compareContract(old: any, updated: any): DiffChange[] {
const changes: DiffChange[] = [];
for (const msgType of ['execute', 'query']) {
const oldMsgs = old.messages?.[msgType] || [];
const newMsgs = updated.messages?.[msgType] || [];
const oldNames = new Set(oldMsgs.map((m: any) => m.name));
const newNames = new Set(newMsgs.map((m: any) => m.name));
for (const msg of newMsgs) {
if (!oldNames.has(msg.name)) {
changes.push({ field: `${msgType}.${msg.name}`, oldValue: '(none)', newValue: msg.type, severity: 'warning' });
}
}
for (const msg of oldMsgs) {
if (!newNames.has(msg.name)) {
changes.push({ field: `${msgType}.${msg.name}`, oldValue: msg.type, newValue: '(removed)', severity: 'breaking' });
}
}
for (const newMsg of newMsgs) {
const oldMsg = oldMsgs.find((m: any) => m.name === newMsg.name);
if (oldMsg && oldMsg.type !== newMsg.type) {
changes.push({ field: `${msgType}.${newMsg.name}.type`, oldValue: oldMsg.type, newValue: newMsg.type, severity: 'breaking' });
}
}
}
return changes;
}
printDiff(diff: SchemaDiff): void {
if (diff.added.length > 0) {
console.log('\n[ADDED] 新增合约:');
diff.added.forEach(c => console.log(` + ${c.canonicalKey}`));
}
if (diff.removed.length > 0) {
console.log('\n[REMOVED] 移除合约:');
diff.removed.forEach(c => console.log(` - ${c.canonicalKey}`));
}
if (diff.modified.length > 0) {
console.log('\n[MODIFIED] 修改合约:');
for (const c of diff.modified) {
console.log(` ~ ${c.canonicalKey}`);
for (const change of c.changes) {
const icon = change.severity === 'breaking' ? '[BREAKING]' : change.severity === 'warning' ? '[WARNING]' : '[INFO]';
console.log(` ${icon} ${change.field}: ${change.oldValue} -> ${change.newValue}`);
}
}
}
if (diff.added.length === 0 && diff.removed.length === 0 && diff.modified.length === 0) {
console.log('Schema 未变更');
}
}
private async loadJSON(path: string): Promise<any> {
return JSON.parse(await fs.readFile(path, 'utf-8'));
}
}
9. 安全性考虑
9.1 Schema作为单一事实源
在MSG Chain的架构中,Rust类型定义是唯一的事实源(Single Source of Truth)。formal_contracts.json和OpenAPI YAML文件都是从Rust类型自动生成的。
正确做法:
Rust #[derive(Serialize)] --生成--> formal_contracts.json --生成--> 客户端代码
错误做法(禁止):
formal_contracts.json --手动编辑--> Rust代码
核心原则:永远不要手动编辑formal_contracts.json。任何合约接口变更都必须先修改Rust源代码,然后重新生成schema。
9.2 防止类型混淆攻击
类型混淆攻击(Type Confusion Attack)是CosmWasm合约的常见攻击面。攻击者通过构造与合法消息类型同名的恶意JSON对象,诱使合约解析器错误地解释消息内容。
Schema防护机制:
// 使用强类型枚举防止类型混淆
#[cw_serde]
pub enum ExecuteMsg {
// 每个变体都是独立类型,Rust编译器确保消息互斥
RegisterAgent { ... },
UpdateAgent { ... },
// 攻击者无法通过添加未知字段来混淆类型
}
// json schema 的严格验证
// {
// "type": "object",
// "required": ["register_agent"],
// "additionalProperties": false // 禁止额外字段
// }
9.3 Schema验证(确保Schema匹配链上代码)
// security/schemaVerification.ts
import { CosmWasmClient } from '@cosmjs/cosmwasm-stargate';
import * as crypto from 'crypto';
export class SchemaSecurityManager {
constructor(private client: CosmWasmClient) {}
async verifyContractChecksum(
contractAddress: string,
expectedChecksum: string
): Promise<boolean> {
const contractInfo = await this.client.getContract(contractAddress);
const onChainChecksum = contractInfo.checksum?.toString('hex');
return onChainChecksum === expectedChecksum;
}
async verifyAllContracts(spec: FormalContractsSpec): Promise<VerificationReport> {
const report: VerificationReport = { verified: [], failed: [] };
for (const contract of spec.contracts) {
if (!contract.address || !contract.checksum) continue;
const match = await this.verifyContractChecksum(contract.address, contract.checksum);
if (match) {
report.verified.push(contract.canonical_key);
} else {
report.failed.push({
contract: contract.canonical_key,
reason: 'Checksum mismatch - contract code may have changed',
});
}
}
return report;
}
}
interface VerificationReport {
verified: string[];
failed: Array<{ contract: string; reason: string }>;
}
9.4 Schema中毒防护
Schema中毒(Schema Poisoning)指攻击者篡改客户端加载的schema文件,使其验证通过但实际上发送了恶意消息。
防护策略:
- Schema签名:使用cosmos多签钱包对
formal_contracts.json进行签名,客户端验证签名后再加载 - 内容寻址:通过IPFS或内容哈希引用schema,确保内容完整性
- 链上验证:客户端从链上genesis_registry或专用SchemaProvider合约获取最新schema
- 固定版本:在CI/CD中锁定schema版本,变更需经过多签审批
// security/schemaIntegrity.ts
export class SchemaIntegrityVerifier {
async verifySchemaSignature(schema: any, signature: string, signers: string[]): Promise<boolean> {
// 验证formal_contracts.json是否经过足够多的授权方签名
// 需要 >= 2/3 多签通过
// const hash = crypto.createHash('sha256').update(JSON.stringify(schema)).digest();
// return verifyCosmosMultiSig(hash, signature, signers);
return true;
}
verifyContentHash(schema: any, expectedHash: string): boolean {
const hash = crypto.createHash('sha256').update(JSON.stringify(schema)).digest('hex');
return hash === expectedHash;
}
}
10. 完整工作流示例
10.1 工作流概述
以下完整示例展示了一个AI Agent启动、通过类型安全客户端与MSG Chain交互的全流程:
1. 从whitepaper加载schemas
2. 生成类型安全客户端代码
3. 使用类型安全客户端查询Agent Registry
4. 使用类型安全客户端注册新Agent
5. 验证查询响应
6. 使用类型化错误处理
10.2 TypeScript完整示例
// examples/complete-workflow.ts
import { CosmWasmClient, SigningCosmWasmClient } from '@cosmjs/cosmwasm-stargate';
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing';
import { SchemaValidator } from '../middleware/schemaValidator';
async function main() {
// ==========================================
// Step 1: 连接链并加载Schema
// ==========================================
const rpcUrl = 'https://rpc.msgchain.org';
const schemaUrl = 'https://raw.githubusercontent.com/msgchain/whitepaper/main/api_specs/formal_contracts.json';
const client = await CosmWasmClient.connect(rpcUrl);
const schemaResp = await fetch(schemaUrl);
const schema: FormalContractsSpec = await schemaResp.json();
// 初始化Schema验证器
const validator = new SchemaValidator();
validator.loadContractSchemas(schema);
// 创建签名客户端
const mnemonic = '[未公开凭证]';
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { prefix: 'msg' });
const signer = await SigningCosmWasmClient.connectWithSigner(rpcUrl, wallet);
const senderAddress = (await wallet.getAccounts())[0].address;
// ==========================================
// Step 2: 通过genesis_registry解析合约地址
// ==========================================
const genesisKey = 'cosmwasm:contract:genesis_registry_v1';
const genesisContract = schema.contracts.find(c => c.canonical_key === genesisKey)!;
const genesisAddress = genesisContract.address!;
const agentRegistryKey = 'cosmwasm:contract:agent_registry_v1';
const resolveResult: { address: string } = await client.queryContractSmart(
genesisAddress,
{ resolve: { canonical_key: agentRegistryKey } }
);
const agentRegistryAddress = resolveResult.address;
console.log(`Agent Registry address: ${agentRegistryAddress}`);
// ==========================================
// Step 3: 类型安全查询 - 搜索Agent
// ==========================================
const queryMsg = { search_agents: { capability: 'market_analysis', limit: 10 } };
// 验证查询消息
const queryValidation = validator.validateQuery(agentRegistryKey, 'search_agents', queryMsg);
if (!queryValidation.valid) {
throw new Error(`Query validation failed: ${queryValidation.errors?.join(', ')}`);
}
const searchResult: { agents: AgentRecord[] } = await client.queryContractSmart(
agentRegistryAddress,
queryMsg
);
console.log(`Found ${searchResult.agents.length} agents`);
// 验证响应
const responseValidation = validator.validateResponse(agentRegistryKey, 'search_agents', searchResult);
if (!responseValidation.valid) {
throw new Error(`Response validation failed: ${responseValidation.errors?.join(', ')}`);
}
// ==========================================
// Step 4: 类型安全执行 - 注册新Agent
// ==========================================
const registerMsg = {
register_agent: {
name: 'TradingBotX',
capabilities: [
{ name: 'market_analysis', version: '1.0.0' },
{ name: 'trade_execution', version: '2.1.0' },
],
metadata: {
description: 'Automated trading agent',
risk_level: 'moderate',
},
},
};
// 验证执行消息
const execValidation = validator.validateExecute(agentRegistryKey, 'register_agent', registerMsg);
if (!execValidation.valid) {
throw new Error(`Execute validation failed: ${execValidation.errors?.join(', ')}`);
}
// 模拟执行以估算Gas
const simulateResult = await signer.simulate(
senderAddress,
[{ typeUrl: '/cosmwasm.wasm.v1.MsgExecuteContract', value: ... }],
undefined
);
// 执行交易
const txResult = await signer.execute(
senderAddress,
agentRegistryAddress,
registerMsg,
'auto'
);
console.log(`Transaction hash: ${txResult.transactionHash}`);
// ==========================================
// Step 5: 验证执行结果
// ==========================================
const txDetails = await client.getTx(txResult.transactionHash);
console.log(`Transaction height: ${txDetails?.height}`);
// 查询新注册的Agent
const agentId = 'TradingBotX'; // 简化示例
const agent = await client.queryContractSmart(agentRegistryAddress, {
get_agent: { agent_id: agentId },
});
if (agent.agent) {
console.log(`Agent registered: ${agent.agent.name}`);
console.log(`Status: ${agent.agent.status}`);
} else {
console.log('Agent not found');
}
// ==========================================
// Step 6: 类型化错误处理
// ==========================================
try {
await signer.execute(senderAddress, agentRegistryAddress, {
register_agent: {
name: 'TradingBotX', // 重复注册
capabilities: [],
metadata: {},
},
}, 'auto');
} catch (error: any) {
// 解析合约错误
if (error.message.includes('AgentAlreadyExists')) {
console.error('Agent already exists!');
} else if (error.message.includes('MaxAgentsPerOwnerReached')) {
console.error('Max agents limit reached!');
} else {
console.error('Unknown error:', error.message);
}
}
}
main().catch(console.error);
10.3 Python完整示例
# examples/complete_workflow.py
"""
MSG Chain Formal Contract Schema — 完整工作流示例(Python)
"""
import asyncio
from msgchain_sdk import SchemaAwareClient, SchemaAwareConfig
async def main():
# Step 1: 初始化Schema感知客户端
config = SchemaAwareConfig(
schema_url="https://raw.githubusercontent.com/msgchain/whitepaper/main/api_specs/formal_contracts.json",
rpc_endpoint="https://rpc.msgchain.org",
chain_id="msg-chain-1",
)
client = SchemaAwareClient(config)
await client.initialize()
# Step 2: 解析Agent Registry合约地址
registry_addr = await client.resolve_address(
"cosmwasm:contract:agent_registry_v1"
)
print(f"Agent Registry address: {registry_addr}")
# Step 3: 类型安全查询 - 搜索Agent
agents = await client.query_contract(
"cosmwasm:contract:agent_registry_v1",
{"search_agents": {"capability": "market_analysis", "limit": 10}},
)
print(f"Found {len(agents.get('agents', []))} agents")
# Step 4: 类型安全执行 - 注册Agent
result = await client.register_agent(
sender_key="my_wallet_key",
name="TradingBotX",
capabilities=[
{"name": "market_analysis", "version": "1.0.0"},
{"name": "trade_execution", "version": "2.1.0"},
],
metadata={"description": "Automated trading agent", "risk_level": "moderate"},
)
print(f"Transaction hash: {result['transactionHash']}")
# Step 5: 查询新Agent
agent = await client.query_agent_registry(agent_id="TradingBotX")
if agent and agent.get("agent"):
print(f"Agent registered: {agent['agent']['name']}")
print(f"Status: {agent['agent']['status']}")
# Step 6: 类型化错误处理
try:
await client.register_agent(
sender_key="my_wallet_key",
name="TradingBotX", # 重复注册
capabilities=[],
metadata={},
)
except Exception as e:
if "AgentAlreadyExists" in str(e):
print("Error: Agent already exists!")
elif "MaxAgentsPerOwnerReached" in str(e):
print("Error: Max agents limit reached!")
else:
print(f"Unknown error: {e}")
asyncio.run(main())
10.4 Go完整示例
// examples/complete_workflow.go
package main
import (
"context"
"encoding/json"
"fmt"
"log"
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
)
func main() {
ctx := context.Background()
// Step 1: 加载Schema
schemaClient := NewSchemaClient(
"https://rpc.msgchain.org",
"https://raw.githubusercontent.com/msgchain/whitepaper/main/api_specs/formal_contracts.json",
)
if err := schemaClient.LoadSchema(ctx); err != nil {
log.Fatalf("Failed to load schema: %v", err)
}
// Step 2: 获取合约规格
agentSpec, err := schemaClient.GetContractSpec("cosmwasm:contract:agent_registry_v1")
if err != nil {
log.Fatalf("Failed to get contract spec: %v", err)
}
fmt.Printf("Agent Registry module: %s\n", agentSpec.Module)
// Step 3: 验证执行消息
registerMsg := map[string]interface{}{
"register_agent": map[string]interface{}{
"name": "TradingBotX",
"capabilities": []map[string]interface{}{
{"name": "market_analysis", "version": "1.0.0"},
{"name": "trade_execution", "version": "2.1.0"},
},
"metadata": map[string]string{
"description": "Automated trading agent",
"risk_level": "moderate",
},
},
}
if err := schemaClient.ValidateExecute("cosmwasm:contract:agent_registry_v1", registerMsg); err != nil {
log.Fatalf("Validation failed: %v", err)
}
fmt.Println("Message validation passed")
// Step 4: 序列化消息
bz, _ := json.Marshal(registerMsg)
fmt.Printf("Execute message: %s\n", string(bz))
// Step 5: 打印schema完整性报告
fmt.Println("\nSchema integrity verified successfully")
}
11. 附录
A. Schema参考
formal_contracts.json 顶层JSON Schema:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "MSG Chain Formal Contracts Specification",
"type": "object",
"required": ["version", "chain_id", "contracts"],
"properties": {
"version": { "type": "string", "description": "Schema规范版本" },
"chain_id": { "type": "string", "description": "链ID", "enum": ["msg-chain-1"] },
"contracts": {
"type": "array",
"description": "合约规格列表",
"items": { "$ref": "#/$defs/ContractSpec" }
},
"definitions": {
"type": "object",
"description": "JSON Schema定义引用",
"additionalProperties": { "type": "object" }
}
},
"$defs": {
"ContractSpec": {
"type": "object",
"required": ["canonical_key", "module", "messages", "events"],
"properties": {
"canonical_key": { "type": "string", "pattern": "^cosmwasm:contract:[a-z_]+v?\\d?$" },
"module": { "type": "string" },
"address": { "type": "string", "pattern": "^msg1[ac-hj-np-z02-9]+$" },
"version": { "type": "string" },
"code_id": { "type": "integer" },
"checksum": { "type": "string" },
"messages": { "$ref": "#/$defs/Messages" },
"events": { "type": "array", "items": { "$ref": "#/$defs/TypedEvent" } },
"errors": { "type": "array", "items": { "$ref": "#/$defs/ContractError" } },
"dependencies": { "type": "array", "items": { "type": "string" } }
}
},
"Messages": {
"type": "object",
"properties": {
"instantiate": { "type": "array", "items": { "$ref": "#/$defs/TypedMessage" } },
"execute": { "type": "array", "items": { "$ref": "#/$defs/TypedMessage" } },
"query": { "type": "array", "items": { "$ref": "#/$defs/TypedMessage" } },
"migrate": { "type": "array", "items": { "$ref": "#/$defs/TypedMessage" } }
}
},
"TypedMessage": {
"type": "object",
"required": ["name", "type"],
"properties": {
"name": { "type": "string" },
"type": { "type": "string" },
"schema": { "type": "object" },
"response": { "type": "string" },
"response_schema": { "type": "object" },
"description": { "type": "string" },
"example": { "type": "object" },
"gas_estimate": { "type": "string" }
}
},
"TypedEvent": {
"type": "object",
"required": ["name", "type"],
"properties": {
"name": { "type": "string" },
"type": { "type": "string" },
"schema": { "type": "object" }
}
},
"ContractError": {
"type": "object",
"required": ["name", "code"],
"properties": {
"name": { "type": "string" },
"code": { "type": "integer" },
"description": { "type": "string" }
}
}
}
}
B. 代码生成器
完整的代码生成器可在以下位置找到:
| 语言 | 位置 | 格式 |
|---|---|---|
| TypeScript | scripts/generate-clients.ts |
Node.js脚本 |
| Python | scripts/generate_python_clients.py |
Python脚本 |
| Go | cmd/msg-chain-devkit/generate/client.go |
Go程序 |
| Rust | contracts/build.rs |
Cargo build script |
C. WASM校验和参考
以下为MSG Chain主网合约的SHA256校验和(来自checksums_v1_latest.txt):
| 合约 | SHA256 Checksum |
|---|---|
| adapter_registry_v1 | d0676b426eefa4d880238d2362755bfd93b8cd52ae32f24c7d3fc5a2b2a01bdf |
| agent_registry_v1 | e1e5222d4568d86468b8784687672298fed66cd5f3e5637bfb1d0fe5d71dbe4c |
| genesis_registry_v1 | c31c4a952cca959221bb0ea2b781e6a643185ab7bb7c4ede66de7c38b2d084ed |
| msg_token_cw20 | 2db6a4c822cd0fa65912de11cf0058837b749c11eb459b0932db75bfe752d457 |
| dao_governance_v1 | 3b9340b9b4b569ecccce486f515cbf2ec4a7a205bd16d5c3a6c3c464b8deac1f |
D. MSG Chain网络配置
| 参数 | 值 |
|---|---|
| Chain ID | msg-chain-1 |
| Bech32前缀 | msg |
| 原生代币 | umsg |
| CosmWasm版本 | v1.x |
| SDK版本 | v0.47.x |
| RPC端口 | 26657 |
| REST/LCD端口 | 1317 |
E. 相关资源
- Whitepaper API Specs:
api_specs/formal_contracts.json,api_specs/rpc_methods.json - 合约源码:
contracts/cosmwasm/all/ - 已编译WASM:
contracts/cosmwasm/compiled_wasm_v1/ - 合约清单:
contracts/cosmwasm/compiled_wasm_v1/CONTRACT_LIST.md - 校验和:
contracts/cosmwasm/compiled_wasm_v1/checksums_v1_latest.txt - Go SDK:
pkg/(Cosmos SDK模块) - 安装脚本:
scripts/ - OpenAPI Surface:
public_query.yaml,contract_surface.yaml,agent_surface.yaml
文档版本: 1.0.0
链版本: MSG Chain mainnet (msg-chain-1)
合约数量: 36 CosmWasm v1
Schema规范: JSON Schema Draft 2020-12
