dApp Docs/Formal Contract Schemas 类型安全合约交互指南
Development reference. Not independently verified for production.

MSG Chain Formal Contract Schemas — 类型安全合约交互指南

数据来源:MSG Chain 代码库核实

主网状态: No-Go — 当前 MSGChain 主网裁决为 No-Go,以下内容反映代码实际状态,不代表生产可用。

目录

  1. 概述
  2. Formal Contracts Schema格式
  3. RPC Methods Schema
  4. 从Schema生成类型安全客户端
  5. 运行时验证
  6. 与SDK集成
  7. GraphQL集成
  8. 验证工具与CI
  9. 安全性考虑
  10. 完整工作流示例
  11. 附录

1. 概述

1.1 为什么AI Agent需要类型安全

MSG Chain是一条专为AI Agent网络设计的第一层区块链,链上运行着36+个CosmWasm智能合约。当AI Agent需要与链上合约交互时——无论是注册身份、发起微支付、还是执行跨Agent通信——任何消息格式错误都可能导致:

类型安全(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}

例如:

关键约束: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 为什么需要运行时验证

即使有了编译时类型安全,运行时验证仍然至关重要:

  1. 链上状态变化:合约可能被迁移或更新,schema可能变化
  2. 动态数据:某些字段(如地址、金额)在编译时无法完全验证
  3. 防御深度:多层验证减少漏洞风险
  4. 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文件,使其验证通过但实际上发送了恶意消息。

防护策略:

  1. Schema签名:使用cosmos多签钱包对formal_contracts.json进行签名,客户端验证签名后再加载
  2. 内容寻址:通过IPFS或内容哈希引用schema,确保内容完整性
  3. 链上验证:客户端从链上genesis_registry或专用SchemaProvider合约获取最新schema
  4. 固定版本:在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. 相关资源


文档版本: 1.0.0
链版本: MSG Chain mainnet (msg-chain-1)
合约数量: 36 CosmWasm v1
Schema规范: JSON Schema Draft 2020-12