AI Agent 合约间调用与组合模式指南 — 在 MSG Chain 上编排多合约协作
版本: v1.0.0
链ID:msg-chain-1
Bech32 前缀:msg
核心合约:agent_registry_v1(Code ID 20),aidid_did_registry_v1(Code ID 29),agent_payment_v1(Code ID 21),agent_a2a_v1(Code ID 19),agent_mpc_v1(Code ID 22),ai_agent_constitution_v1(Code ID 23),dao_governance_v1(Code ID 24)⚠️ No-Go Disclaimer: MSGChain 主网裁决为 No-Go。本文件所有内容反映的是开发阶段的技术设计,不代表主网未独立核验上线状态。生产部署状态请以白皮书为准:https://msgchain.org/whitepaper/
目录
1. 概述
1.1 为什么合约组合对 AI Agent 至关重要
在 MSG Chain 上,AI Agent 不是孤立的合约。一个有用的 AI Agent 通常需要组合多个链上服务:
- 身份服务:通过
aidid_did_registry_v1解析 Agent 的 DID 公钥 - 注册服务:通过
agent_registry_v1发现其他 Agent 的能力和端点 - 支付服务:通过
agent_payment_v1处理服务调用时的代币结算 - 通信服务:通过
agent_a2a_v1与其他 Agent 交换消息 - 多签服务:通过
agent_mpc_v1实现联合决策 - 宪章服务:通过
ai_agent_constitution_v1约束 Agent 的行为范围 - 治理服务:通过
dao_governance_v1参与链上自治
没有合约组合,每个 Agent 就需要重新实现全部基础设施 —— 这在 CosmWasm 的沙箱模型中既低效也不安全。
1.2 CosmWasm 合约间调用模式
CosmWasm 提供两种跨合约交互机制:
| 模式 | 机制 | 适用场景 |
|---|---|---|
| 同步查询 | WasmQuery::Smart / WasmQuery::Raw |
读取状态,计算约束 |
| 异步消息 | WasmMsg::Execute |
写入操作,状态变更 |
同步查询在合约执行过程中立即返回结果,适合验证条件、获取配置等只读操作。
异步消息通过 Response 的 messages 字段发出,在当前合约执行完成后由运行时依次处理。
关键区别:
同步查询:
Contract A --query--> Contract B --response--> Contract A (继续执行)
异步消息:
Contract A --execute msg--> Contract B (A 执行结束,B 开始)
Contract A 无权访问 B 的执行结果
1.3 Registry 发现 vs 直接寻址
AI Agent 在需要与其他合约交互时,有两种获取目标地址的方式:
直接寻址
合约代码中硬编码目标地址,或由调用方通过 InstantiateMsg / ExecuteMsg 传入。
// 硬编码地址 -- 不推荐
const PAYMENT_ADDR: &str = "msg1...";
// 由上层传入地址 -- 推荐但需调用方知道确切地址
pub fn execute_with_payment(
deps: DepsMut,
payment_addr: String,
amount: Uint128,
) -> StdResult<Response> {
// 直接使用传入地址
}
优点:零查询开销,延迟最低
缺点:地址变更需要合约升级,不适合动态发现
Registry 发现
通过 agent_registry_v1 或 DAO 的 canonical key 解析机制查找地址。
// 通过 canonical key 解析 -- 推荐
pub fn resolve_and_call(
deps: Deps,
canonical_key: &str,
) -> StdResult<String> {
// agent_registry_v1 负责将 "agent_payment_v1" 解析为实际地址
}
优点:地址可变、支持多版本、支持蓝绿部署
缺点:每次调用有一次额外查询开销
| 场景 | 推荐模式 |
|---|---|
| Agent 自己管理的子合约 | 直接寻址(地址存储在 Agent 状态中) |
| 系统级基础设施合约 | Registry 发现 |
| 动态发现的合作伙伴 Agent | Registry + 能力过滤 |
| 高频调用的热路径 | 缓存 Registry 结果 |
1.4 合约全景
+----------------------------------+
| AI Agent Contract |
| +----------+ +----------+ |
| | State | | Logic | |
| | Storage | | Engine | |
| +----------+ +-----+----+ |
+--------------------+--------------+
|
+-------------------------------+-------------------------------+
| | |
+--------v--------+ +----------v----------+ +------------v-----+
| Registry Layer | | DID Auth Layer | | Payment Layer |
| agent_registry | | aidid_did_registry | | agent_payment |
+-----------------+ +---------------------+ +------------------+
| | |
+--------v--------+ +----------v----------+ +------------v-----+
| Comms Layer | | Governance Layer | | Security Layer |
| agent_a2a_v1 | | dao_governance_v1 | | agent_mpc_v1 |
+-----------------+ +---------------------+ +------------------+
| | |
+--------v--------+ +----------v----------+ +------------v-----+
| Constitution | | CW4626 Vaults | | CW20 Tokens |
| ai_agent_const | | Tokenized Vaults | | Agent Tokens |
+-----------------+ +---------------------+ +------------------+
2. 跨合约调用基础
2.1 CosmWasm 消息模型
CosmWasm 合约通过 CosmosMsg 枚举与世界交互。跨合约调用使用 WasmMsg 变体:
use cosmwasm_std::{
to_binary, WasmMsg, CosmosMsg, QueryRequest, WasmQuery,
Deps, DepsMut, Env, MessageInfo, Response, StdResult,
StdError, Binary, Addr, Uint128, Coin, BankMsg,
};
// WasmMsg 完整枚举
// WasmMsg::Execute { contract_addr, msg, funds }
// -> 调用目标合约的 Execute 入口
// WasmMsg::Instantiate { admin, code_id, msg, funds, label }
// -> 创建新合约实例
// WasmMsg::Migrate { contract_addr, new_code_id, msg }
// -> 迁移合约到新代码
// WasmMsg::UpdateAdmin { contract_addr, admin }
// -> 变更合约管理员
// WasmMsg::ClearAdmin { contract_addr }
// -> 移除合约管理员
// WasmMsg::StoreCode { wasm_byte_code }
// -> 上传新合约代码(仅特权角色)
2.2 基础跨合约执行调用
// 基础跨合约执行 -- Agent 调用 Registry
//
// 场景:AgentA 向 agent_registry_v1 注册自身
// 流程:AgentA 合约 -> WasmMsg::Execute -> agent_registry_v1
pub fn register_agent_via_call(
deps: DepsMut,
env: Env,
info: MessageInfo,
registry_addr: String,
agent_id: String,
metadata: Option<String>,
) -> StdResult<Response> {
// 构造目标合约的 Execute 消息
let register_msg = RegistryExecuteMsg::Register {
agent_id: agent_id.clone(),
metadata,
};
// 序列化为二进制
let msg_binary = to_binary(®ister_msg)
.map_err(|e| StdError::generic_err(format!("Serialization error: {}", e)))?;
Ok(Response::new()
.add_attribute("action", "register_agent")
.add_attribute("agent_id", &agent_id)
.add_attribute("registry_addr", ®istry_addr)
.add_message(WasmMsg::Execute {
contract_addr: registry_addr,
msg: msg_binary,
funds: vec![],
}))
}
// 带资金的跨合约调用 -- Agent 支付
//
// 场景:AgentA 向 AgentB 支付服务费用
// 流程:AgentA 合约 -> WasmMsg::Execute + funds -> agent_payment_v1
pub fn pay_agent_service(
deps: DepsMut,
payment_addr: String,
recipient: String,
amount: Uint128,
denom: String,
memo: Option<String>,
) -> StdResult<Response> {
let pay_msg = PaymentExecuteMsg::Transfer {
recipient,
amount,
denom: denom.clone(),
memo,
};
let coin = Coin {
denom,
amount,
};
Ok(Response::new()
.add_attribute("action", "pay_agent_service")
.add_message(WasmMsg::Execute {
contract_addr: payment_addr,
msg: to_binary(&pay_msg)?,
funds: vec![coin],
}))
}
2.3 同步查询其它合约状态
// 同步查询 -- 查询 DID 公钥
//
// 场景:验证某个 Agent 的 DID 身份
// 流程:调用方合约 -> WasmQuery::Smart -> aidid_did_registry_v1
pub fn query_did_public_key(
deps: Deps,
did_registry_addr: String,
did_id: String,
) -> StdResult<String> {
let query_msg = DidQuery::ResolveDid {
did: did_id.clone(),
};
let result: DidResponse = deps.querier.query(&QueryRequest::Wasm(
WasmQuery::Smart {
contract_addr: did_registry_addr,
msg: to_binary(&query_msg)?,
}
))?;
Ok(result.public_key)
}
// 批量查询多个合约 -- 组合多个查询
//
// 场景:验证 Agent 身份 + 检查其声誉 + 查询价格
// 流程:调用方 -> 三次 WasmQuery::Smart -> 三个不同合约
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct AgentServiceInfo {
pub agent_id: String,
pub did_pub_key: String,
pub reputation_score: u64,
pub price_per_call: Uint128,
}
pub fn query_agent_service_info(
deps: Deps,
registry_addr: String,
did_registry_addr: String,
payment_addr: String,
agent_id: String,
) -> StdResult<AgentServiceInfo> {
// 1. 查询 Registry 获取 Agent 元数据
let registry_query = RegistryQuery::GetAgent {
agent_id: agent_id.clone(),
};
let agent_info: AgentResponse = deps.querier.query(&QueryRequest::Wasm(
WasmQuery::Smart {
contract_addr: registry_addr,
msg: to_binary(®istry_query)?,
}
))?;
// 2. 查询 DID Registry 获取公钥
let did_query = DidQuery::ResolveDid {
did: agent_info.did.clone(),
};
let did_info: DidResponse = deps.querier.query(&QueryRequest::Wasm(
WasmQuery::Smart {
contract_addr: did_registry_addr,
msg: to_binary(&did_query)?,
}
))?;
// 3. 查询 Payment 合约获取价格
let price_query = PaymentQuery::GetServicePrice {
provider: agent_id.clone(),
};
let price_info: PriceResponse = deps.querier.query(&QueryRequest::Wasm(
WasmQuery::Smart {
contract_addr: payment_addr,
msg: to_binary(&price_query)?,
}
))?;
Ok(AgentServiceInfo {
agent_id,
did_pub_key: did_info.public_key,
reputation_score: agent_info.reputation,
price_per_call: price_info.amount,
})
}
2.4 接收回调(Submessage 模式)
当需要知道跨合约调用是否成功以及其结果时,使用 SubMsg:
use cosmwasm_std::{
SubMsg, SubMsgResult, Reply, ReplyOn,
};
// 使用 SubMsg 接收跨合约调用结果
//
// 场景:AgentA 通过 agent_payment_v1 转账后,根据结果更新状态
// 流程:AgentA -> SubMsg(WasmMsg::Execute) -> agent_payment_v1
// agent_payment_v1 -> Reply -> AgentA.execute_reply()
pub const PAYMENT_REPLY_ID: u64 = 1;
pub fn transfer_with_reply(
deps: DepsMut,
payment_addr: String,
to: String,
amount: Uint128,
) -> StdResult<Response> {
let transfer_msg = PaymentExecuteMsg::Transfer {
recipient: to.clone(),
amount,
denom: "uusage".to_string(),
memo: None,
};
let execute = WasmMsg::Execute {
contract_addr: payment_addr,
msg: to_binary(&transfer_msg)?,
funds: vec![],
};
let sub_msg = SubMsg {
id: PAYMENT_REPLY_ID,
msg: execute.into(),
gas_limit: None,
reply_on: ReplyOn::Success,
};
Ok(Response::new()
.add_attribute("action", "transfer_with_reply")
.add_attribute("to", &to)
.add_submessage(sub_msg))
}
#[entry_point]
pub fn reply(deps: DepsMut, env: Env, msg: Reply) -> StdResult<Response> {
match msg.id {
PAYMENT_REPLY_ID => handle_payment_reply(deps, env, msg),
id => Err(StdError::generic_err(format!("Unknown reply id: {}", id))),
}
}
fn handle_payment_reply(
deps: DepsMut,
_env: Env,
msg: Reply,
) -> StdResult<Response> {
match msg.result {
SubMsgResult::Ok(_response) => {
Ok(Response::new()
.add_attribute("action", "payment_reply")
.add_attribute("status", "success"))
}
SubMsgResult::Err(err) => {
Ok(Response::new()
.add_attribute("action", "payment_reply")
.add_attribute("status", "failed")
.add_attribute("error", err))
}
}
}
2.5 错误传播与部分失败
跨合约调用的错误处理需要特别注意:
// 错误传播策略
// 策略一:全部或全不(原子性)
// 如果子调用失败,整个交易回滚
pub fn atomic_composition(
deps: DepsMut,
registry_addr: String,
payment_addr: String,
agent_id: String,
amount: Uint128,
) -> StdResult<Response> {
let query = RegistryQuery::Exists {
agent_id: agent_id.clone(),
};
let exists: bool = deps.querier.query(&QueryRequest::Wasm(
WasmQuery::Smart {
contract_addr: registry_addr,
msg: to_binary(&query)?,
}
))?;
if !exists {
return Err(StdError::generic_err("Agent not registered"));
}
Ok(Response::new()
.add_message(WasmMsg::Execute {
contract_addr: payment_addr,
msg: to_binary(&PaymentExecuteMsg::Transfer {
recipient: agent_id,
amount,
denom: "uusage".to_string(),
memo: None,
})?,
funds: vec![],
}))
}
// 策略二:容错模式(SubMsg 中处理错误)
// 使用 ReplyOn::Always 处理成功和失败两种情况
pub fn fault_tolerant_composition(
deps: DepsMut,
a2a_addr: String,
target_agent: String,
message: String,
) -> StdResult<Response> {
let send_msg = A2AExecuteMsg::SendMessage {
recipient: target_agent.clone(),
payload: message,
priority: None,
};
let sub_msg = SubMsg {
id: 2,
msg: WasmMsg::Execute {
contract_addr: a2a_addr,
msg: to_binary(&send_msg)?,
funds: vec![],
}.into(),
gas_limit: None,
reply_on: ReplyOn::Always,
};
Ok(Response::new()
.add_attribute("action", "send_message_fault_tolerant")
.add_submessage(sub_msg))
}
2.6 Gas 核算与限制
// Gas 管理与限制
//
// 在 MSG Chain 上,Wasm 执行消耗 gas,跨合约调用会增加 gas 消耗。
// 合约开发者需要关注以下 gas 因素:
// 1. SubMsg gas_limit -- 限制子调用的最大 gas 消耗
pub fn bounded_subcall(
deps: DepsMut,
target: String,
) -> StdResult<Response> {
let msg = WasmMsg::Execute {
contract_addr: target,
msg: to_binary(&SomeExecuteMsg::HeavyComputation {})?,
funds: vec![],
};
let sub_msg = SubMsg {
id: 3,
msg: msg.into(),
gas_limit: Some(500_000),
reply_on: ReplyOn::Error,
};
Ok(Response::new()
.add_submessage(sub_msg))
}
// 2. 查询操作也消耗 gas
// 每次 WasmQuery::Smart 调用都有 gas 成本
// 批量查询时建议合并查询请求以减少开销
// 3. 预估 gas 消耗公式(近似)
// total_gas = 基础执行_gas + Sum(子调用_gas)
// 其中每个子调用至少消耗 20_000 gas(调用开销)
2.7 完整消息类型定义
为了使跨合约调用工作,调用方必须知道目标合约的消息类型。以下典型系统合约的消息定义:
// agent_registry_v1 -- Execute 消息
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum RegistryExecuteMsg {
Register {
agent_id: String,
metadata: Option<String>,
capabilities: Vec<String>,
endpoint: Option<String>,
pricing: Option<String>,
},
Update {
agent_id: String,
metadata: Option<String>,
capabilities: Option<Vec<String>>,
endpoint: Option<String>,
pricing: Option<String>,
},
Deregister {
agent_id: String,
},
AddReputation {
agent_id: String,
score_delta: i64,
reason: Option<String>,
},
}
// agent_registry_v1 -- Query 消息
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum RegistryQuery {
GetAgent { agent_id: String },
ListAgents { start_after: Option<String>, limit: Option<u32> },
FindByCapability { capability: String, limit: Option<u32> },
Exists { agent_id: String },
Resolve { key: String },
Status { agent_id: String },
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct AgentResponse {
pub agent_id: String,
pub did: String,
pub metadata: Option<String>,
pub capabilities: Vec<String>,
pub endpoint: Option<String>,
pub reputation: u64,
pub status: String,
pub registered_at: u64,
pub last_updated: u64,
}
// agent_payment_v1 -- Execute 消息
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum PaymentExecuteMsg {
Transfer { recipient: String, amount: Uint128, denom: String, memo: Option<String> },
SetServicePrice { service_id: String, amount: Uint128, denom: String },
Withdraw { amount: Option<Uint128>, denom: Option<String> },
Escrow { recipient: String, amount: Uint128, condition: String },
ReleaseEscrow { escrow_id: String },
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum PaymentQuery {
Balance { address: String },
GetServicePrice { provider: String },
GetEscrow { escrow_id: String },
}
// agent_a2a_v1 -- Execute 消息
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum A2AExecuteMsg {
SendMessage { recipient: String, payload: String, priority: Option<u8> },
OpenChannel { peer: String, capacity: Option<u32> },
CloseChannel { channel_id: String },
Acknowledge { message_id: String },
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum A2AQuery {
GetChannel { channel_id: String },
ListMessages { channel_id: String, start_after: Option<u64>, limit: Option<u32> },
PendingMessages { recipient: String },
}
// agent_mpc_v1 -- Execute 消息
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum MpcExecuteMsg {
ProposeTransaction { target: String, msg: Binary, description: String, expires_at: u64 },
Sign { proposal_id: String },
ExecuteSigned { proposal_id: String },
AddSigner { signer: String, weight: u64 },
RemoveSigner { signer: String },
ChangeThreshold { new_threshold: u64 },
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum MpcQuery {
GetProposal { proposal_id: String },
ListProposals { status: Option<String>, start_after: Option<u64>, limit: Option<u32> },
GetSigners {},
GetThreshold {},
}
// ai_agent_constitution_v1 -- Execute 消息
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum ConstitutionExecuteMsg {
SetConstitution { preamble: String, articles: Vec<ConstitutionArticle> },
AmendConstitution { article_index: u32, new_text: String, rationale: String },
CheckAction { action: String, context: String },
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct ConstitutionArticle {
pub title: String,
pub content: String,
pub category: String,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum ConstitutionQuery {
GetConstitution {},
CheckAction { action: String, context: String },
GetArticle { index: u32 },
}
// dao_governance_v1 -- Execute 消息
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum DaoExecuteMsg {
Propose { title: String, description: String, msgs: Vec<CosmosMsg> },
Vote { proposal_id: u64, vote: VoteChoice },
Execute { proposal_id: u64 },
Close { proposal_id: u64 },
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub enum VoteChoice { Yes, No, Abstain, Veto }
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum DaoQuery {
GetProposal { proposal_id: u64 },
ListProposals { start_after: Option<u64>, limit: Option<u32>, status: Option<String> },
GetVote { proposal_id: u64, voter: String },
GetConfig {},
}
2.8 CwCall 封装辅助函数
为减少重复代码,可以封装通用的跨合约调用辅助函数:
// 通用跨合约调用辅助库
pub mod cw_call {
use cosmwasm_std::{
to_binary, Addr, Binary, Deps, DepsMut, QuerierWrapper,
QueryRequest, StdResult, WasmMsg, WasmQuery,
};
pub fn execute_msg<T: serde::Serialize>(
contract_addr: impl Into<String>,
msg: &T,
) -> StdResult<WasmMsg> {
Ok(WasmMsg::Execute {
contract_addr: contract_addr.into(),
msg: to_binary(msg)?,
funds: vec![],
})
}
pub fn execute_msg_with_funds<T: serde::Serialize>(
contract_addr: impl Into<String>,
msg: &T,
funds: Vec<Coin>,
) -> StdResult<WasmMsg> {
Ok(WasmMsg::Execute {
contract_addr: contract_addr.into(),
msg: to_binary(msg)?,
funds,
})
}
pub fn smart_query<T: serde::Serialize, U: serde::de::DeserializeOwned>(
querier: &QuerierWrapper,
contract_addr: impl Into<String>,
query_msg: &T,
) -> StdResult<U> {
querier.query(&QueryRequest::Wasm(WasmQuery::Smart {
contract_addr: contract_addr.into(),
msg: to_binary(query_msg)?,
}))
}
pub fn raw_query<T: serde::de::DeserializeOwned>(
querier: &QuerierWrapper,
contract_addr: impl Into<String>,
key: &[u8],
) -> StdResult<Option<T>> {
let result: Option<Binary> = querier.query(&QueryRequest::Wasm(
WasmQuery::Raw {
contract_addr: contract_addr.into(),
key: key.into(),
}
))?;
match result {
Some(data) => Ok(Some(serde_json::from_slice(&data)?)),
None => Ok(None),
}
}
}
// 使用示例:
// let msg = cw_call::execute_msg(&payment_addr, &PaymentExecuteMsg::Transfer { ... })?;
// let info: AgentResponse = cw_call::smart_query(&deps.querier, ®istry_addr, &query)?;
3. 工厂模式与合约实例化
3.1 工厂模式概述
工厂模式是合约组合中最基础也是最重要的模式之一。一个"工厂"合约负责创建(实例化)其他合约的实例,并跟踪所有已创建的实例。
在 MSG Chain 的 AI Agent 场景中,工厂模式用于:
- Agent 工厂:为每个用户或组织创建专属 Agent 实例
- Vault 工厂:为每个策略创建独立的 CW4626 金库
- DAO 工厂:为每个社区创建独立的治理合约
- 代币工厂:为每个 Agent 创建专属 CW20 代币
用户 -- AgentFactory.create_instance()
|
+---> AgentInstance#1 (用户A)
+---> AgentInstance#2 (用户B)
+---> AgentInstance#3 (用户C)
|
+---> 存储 instance_addr -> owner 映射
3.2 最小工厂合约实现
use cosmwasm_std::{
to_binary, Addr, Coin, CosmosMsg, Deps, DepsMut, Env,
MessageInfo, Order, Response, StdResult, Storage,
Uint128, WasmMsg,
};
use cw_storage_plus::{Item, Map};
// 状态定义
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct FactoryConfig {
pub instance_code_id: u64,
pub creation_fee: Coin,
pub admin: String,
pub public_creation: bool,
pub max_instances: u32,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct InstanceInfo {
pub address: Addr,
pub creator: Addr,
pub created_at: u64,
pub label: String,
pub active: bool,
}
pub const CONFIG: Item<FactoryConfig> = Item::new("config");
pub const INSTANCES: Map<u32, InstanceInfo> = Map::new("instances");
pub const CREATOR_INSTANCES: Map<&Addr, Vec<u32>> = Map::new("creator_instances");
pub const NEXT_INSTANCE_ID: Item<u32> = Item::new("next_id");
// 工厂的消息定义
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum FactoryExecuteMsg {
CreateInstance {
label: String,
instantiate_msg: Binary,
admin: Option<String>,
},
UpdateConfig {
instance_code_id: Option<u64>,
creation_fee: Option<Coin>,
public_creation: Option<bool>,
max_instances: Option<u32>,
},
PauseInstance { instance_id: u32 },
ResumeInstance { instance_id: u32 },
MigrateInstance { instance_id: u32, new_code_id: u64, migrate_msg: Binary },
WithdrawFees { to: String, denom: String },
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum FactoryQueryMsg {
GetConfig {},
GetInstance { instance_id: u32 },
ListInstances { start_after: Option<u32>, limit: Option<u32> },
ListInstancesByCreator { creator: String, start_after: Option<u32>, limit: Option<u32> },
InstanceCount {},
}
// 工厂合约入口
#[entry_point]
pub fn instantiate(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
msg: FactoryInstantiateMsg,
) -> StdResult<Response> {
let config = FactoryConfig {
instance_code_id: msg.instance_code_id,
creation_fee: msg.creation_fee,
admin: msg.admin,
public_creation: msg.public_creation,
max_instances: msg.max_instances,
};
CONFIG.save(deps.storage, &config)?;
NEXT_INSTANCE_ID.save(deps.storage, &1)?;
Ok(Response::new()
.add_attribute("action", "instantiate_factory")
.add_attribute("instance_code_id", msg.instance_code_id.to_string()))
}
#[entry_point]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: FactoryExecuteMsg,
) -> StdResult<Response> {
match msg {
FactoryExecuteMsg::CreateInstance { label, instantiate_msg, admin } =>
execute_create_instance(deps, env, info, label, instantiate_msg, admin),
FactoryExecuteMsg::UpdateConfig { .. } => execute_update_config(deps, env, info, msg),
FactoryExecuteMsg::PauseInstance { instance_id } =>
execute_pause_instance(deps, env, info, instance_id),
FactoryExecuteMsg::ResumeInstance { instance_id } =>
execute_resume_instance(deps, env, info, instance_id),
FactoryExecuteMsg::MigrateInstance { .. } => execute_migrate_instance(deps, env, info, msg),
FactoryExecuteMsg::WithdrawFees { to, denom } =>
execute_withdraw_fees(deps, env, info, to, denom),
}
}
// 核心:创建合约实例
pub fn execute_create_instance(
deps: DepsMut,
env: Env,
info: MessageInfo,
label: String,
instantiate_msg: Binary,
admin: Option<String>,
) -> StdResult<Response> {
let config = CONFIG.load(deps.storage)?;
if !config.public_creation && info.sender != config.admin {
return Err(StdError::generic_err("Only factory admin can create instances"));
}
if !config.creation_fee.amount.is_zero() {
let fee_paid = info
.funds
.iter()
.find(|c| c.denom == config.creation_fee.denom)
.map(|c| c.amount)
.unwrap_or(Uint128::zero());
if fee_paid < config.creation_fee.amount {
return Err(StdError::generic_err("Insufficient creation fee"));
}
}
let next_id = NEXT_INSTANCE_ID.load(deps.storage)?;
if config.max_instances > 0 && next_id > config.max_instances + 1 {
return Err(StdError::generic_err("Maximum number of instances reached"));
}
let instance_count = next_id;
let instance_admin = admin.unwrap_or_else(|| env.contract.address.to_string());
let instantiate = WasmMsg::Instantiate {
admin: instance_admin,
code_id: config.instance_code_id,
msg: instantiate_msg,
funds: info.funds,
label: format!("{}-{}", label, instance_count),
};
let instance_info = InstanceInfo {
address: Addr::unchecked("pending"),
creator: info.sender.clone(),
created_at: env.block.height,
label: label.clone(),
active: true,
};
INSTANCES.save(deps.storage, instance_count, &instance_info)?;
CREATOR_INSTANCES.update(
deps.storage,
&info.sender,
|existing| -> StdResult<Vec<u32>> {
let mut list = existing.unwrap_or_default();
list.push(instance_count);
Ok(list)
},
)?;
NEXT_INSTANCE_ID.save(deps.storage, &(next_id + 1))?;
let sub_msg = cosmwasm_std::SubMsg {
id: instance_count,
msg: instantiate.into(),
gas_limit: None,
reply_on: cosmwasm_std::ReplyOn::Success,
};
Ok(Response::new()
.add_attribute("action", "create_instance")
.add_attribute("instance_id", instance_count.to_string())
.add_attribute("label", &label)
.add_attribute("creator", info.sender)
.add_submessage(sub_msg))
}
// Reply 处理 -- 更新实例地址
#[entry_point]
pub fn reply(deps: DepsMut, _env: Env, msg: cosmwasm_std::Reply) -> StdResult<Response> {
let instance_id = msg.id;
let new_address = match msg.result {
cosmwasm_std::SubMsgResult::Ok(response) => {
response
.events
.iter()
.flat_map(|e| e.attributes.iter())
.find(|a| a.key == "_contract_address")
.map(|a| a.value.clone())
.ok_or_else(|| StdError::generic_err("Missing _contract_address in reply"))?
}
cosmwasm_std::SubMsgResult::Err(err) => {
return Err(StdError::generic_err(format!("Instance creation failed: {}", err)));
}
};
let mut instance_info = INSTANCES.load(deps.storage, instance_id)?;
instance_info.address = Addr::unchecked(&new_address);
INSTANCES.save(deps.storage, instance_id, &instance_info)?;
Ok(Response::new()
.add_attribute("action", "reply_create_instance")
.add_attribute("instance_id", instance_id.to_string())
.add_attribute("instance_address", new_address))
}
// 管理操作
pub fn execute_update_config(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: FactoryExecuteMsg,
) -> StdResult<Response> {
let mut config = CONFIG.load(deps.storage)?;
if info.sender != config.admin {
return Err(StdError::generic_err("Unauthorized"));
}
match msg {
FactoryExecuteMsg::UpdateConfig {
instance_code_id, creation_fee, public_creation, max_instances,
} => {
if let Some(code_id) = instance_code_id { config.instance_code_id = code_id; }
if let Some(fee) = creation_fee { config.creation_fee = fee; }
if let Some(public) = public_creation { config.public_creation = public; }
if let Some(max) = max_instances { config.max_instances = max; }
CONFIG.save(deps.storage, &config)?;
Ok(Response::new().add_attribute("action", "update_config"))
}
_ => unreachable!(),
}
}
pub fn execute_pause_instance(
deps: DepsMut,
_env: Env,
info: MessageInfo,
instance_id: u32,
) -> StdResult<Response> {
let config = CONFIG.load(deps.storage)?;
if info.sender != config.admin {
return Err(StdError::generic_err("Unauthorized"));
}
let mut instance = INSTANCES.load(deps.storage, instance_id)?;
instance.active = false;
INSTANCES.save(deps.storage, instance_id, &instance)?;
Ok(Response::new()
.add_attribute("action", "pause_instance")
.add_attribute("instance_id", instance_id.to_string()))
}
// 查询函数
#[entry_point]
pub fn query(deps: Deps, _env: Env, msg: FactoryQueryMsg) -> StdResult<Binary> {
match msg {
FactoryQueryMsg::GetConfig {} => to_binary(&CONFIG.load(deps.storage)?),
FactoryQueryMsg::GetInstance { instance_id } =>
to_binary(&INSTANCES.load(deps.storage, instance_id)?),
FactoryQueryMsg::ListInstances { start_after, limit } => {
let limit = limit.unwrap_or(30).min(100);
let start = start_after.unwrap_or(0);
let instances: Vec<InstanceInfo> = INSTANCES
.range(deps.storage, Some(start.into()), None, Order::Ascending)
.take(limit as usize)
.map(|item| item.map(|(_, v)| v))
.collect::<StdResult<Vec<_>>>()?;
to_binary(&instances)
}
FactoryQueryMsg::ListInstancesByCreator { creator, start_after, limit } => {
let creator = Addr::unchecked(&creator);
let ids = CREATOR_INSTANCES.load(deps.storage, &creator).unwrap_or_default();
let limit = limit.unwrap_or(30).min(100);
let start_idx = start_after.unwrap_or(0) as usize;
let mut instances = Vec::new();
for id in ids.into_iter().skip(start_idx).take(limit as usize) {
if let Ok(inst) = INSTANCES.load(deps.storage, id) {
instances.push(inst);
}
}
to_binary(&instances)
}
FactoryQueryMsg::InstanceCount {} =>
to_binary(&(NEXT_INSTANCE_ID.load(deps.storage)? - 1)),
}
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct FactoryInstantiateMsg {
pub instance_code_id: u64,
pub creation_fee: Coin,
pub admin: String,
pub public_creation: bool,
pub max_instances: u32,
}
3.3 CW20 代币工厂示例
基于 CW20-Base 的代币工厂,AI Agent 可以用它来批量创建代币:
// CW20 代币工厂
//
// 该工厂封装了 CW20 代币创建逻辑,使得 Agent 可以一键部署代币
// 而不需要直接与 CosmWasm 的 WasmMsg::Instantiate 交互。
pub const CW20_CODE_ID: u64 = 1;
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct CreateCw20Msg {
pub name: String,
pub symbol: String,
pub decimals: u8,
pub initial_balances: Vec<Cw20Balance>,
pub mint: Option<Cw20Mint>,
pub marketing: Option<Cw20Marketing>,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct Cw20Balance {
pub address: String,
pub amount: Uint128,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct Cw20Mint {
pub minter: String,
pub cap: Option<Uint128>,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct Cw20Marketing {
pub project: Option<String>,
pub description: Option<String>,
pub logo: Option<String>,
pub marketing: Option<String>,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct Cw20InitMsg {
pub name: String,
pub symbol: String,
pub decimals: u8,
pub initial_balances: Vec<Cw20Balance>,
pub mint: Option<Cw20Mint>,
pub marketing: Option<Cw20Marketing>,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct Cw20TokenInfo {
pub token_address: Addr,
pub creator: Addr,
pub name: String,
pub symbol: String,
pub decimals: u8,
pub created_at: u64,
pub total_supply: Uint128,
}
pub const CW20_TOKENS: Map<&Addr, Cw20TokenInfo> = Map::new("cw20_tokens");
pub const CREATOR_TOKENS: Map<&Addr, Vec<Addr>> = Map::new("creator_tokens");
pub const TOKEN_COUNT: Item<u64> = Item::new("token_count");
pub fn execute_create_cw20(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: CreateCw20Msg,
) -> StdResult<Response> {
let config = CONFIG.load(deps.storage)?;
if !config.creation_fee.amount.is_zero() {
let fee_paid = info
.funds
.iter()
.find(|c| c.denom == config.creation_fee.denom)
.map(|c| c.amount)
.unwrap_or(Uint128::zero());
if fee_paid < config.creation_fee.amount {
return Err(StdError::generic_err("Insufficient fee"));
}
}
let cw20_init = Cw20InitMsg {
name: msg.name.clone(),
symbol: msg.symbol.clone(),
decimals: msg.decimals,
initial_balances: msg.initial_balances,
mint: msg.mint,
marketing: msg.marketing,
};
let label = format!("cw20-{}-{}", msg.symbol, env.block.height);
let token_count = TOKEN_COUNT.load(deps.storage).unwrap_or(0) + 1;
TOKEN_COUNT.save(deps.storage, &token_count)?;
let sub_msg = cosmwasm_std::SubMsg {
id: token_count,
msg: WasmMsg::Instantiate {
admin: env.contract.address.to_string(),
code_id: CW20_CODE_ID,
msg: to_binary(&cw20_init)?,
funds: vec![],
label,
}.into(),
gas_limit: None,
reply_on: cosmwasm_std::ReplyOn::Success,
};
let token_info = Cw20TokenInfo {
token_address: Addr::unchecked("pending"),
creator: info.sender.clone(),
name: msg.name,
symbol: msg.symbol,
decimals: msg.decimals,
created_at: env.block.height,
total_supply: Uint128::zero(),
};
deps.storage.set(
format!("pending_token_{}", token_count).as_bytes(),
&serde_json::to_vec(&token_info)?,
);
CREATOR_TOKENS.update(
deps.storage,
&info.sender,
|existing| -> StdResult<Vec<Addr>> {
let mut list = existing.unwrap_or_default();
list.push(Addr::unchecked("pending"));
Ok(list)
},
)?;
Ok(Response::new()
.add_attribute("action", "create_cw20")
.add_attribute("token_count", token_count.to_string())
.add_submessage(sub_msg))
}
3.4 工厂模式变体
// 变体一:代理工厂 -- 创建"代理合约"转发调用
// 适合实现可升级的 Agent 实例
//
// 代理合约存储:
// - implementation: Addr (当前实现合约地址)
// - owner: Addr (代理所有者)
// 所有调用通过 delegatecall 转发到 implementation
// 变体二:克隆工厂 -- 基于同一 code_id 创建多个实例
// 适合标准化的 Agent 模板
// 每个实例有独立的状态但共享代码逻辑
// 变体三:注册表工厂 -- 工厂 + Registry 集成
// 工厂在创建实例后自动将实例注册到 agent_registry_v1
pub fn execute_create_and_register(
deps: DepsMut,
env: Env,
info: MessageInfo,
label: String,
instantiate_msg: Binary,
registry_addr: String,
agent_id: String,
) -> StdResult<Response> {
let config = CONFIG.load(deps.storage)?;
let next_id = NEXT_INSTANCE_ID.load(deps.storage)?;
let instance_admin = env.contract.address.to_string();
let create_msg = WasmMsg::Instantiate {
admin: instance_admin,
code_id: config.instance_code_id,
msg: instantiate_msg,
funds: vec![],
label: format!("{}-{}", label, next_id),
};
let register_msg = RegistryExecuteMsg::Register {
agent_id: agent_id.clone(),
metadata: Some(label.clone()),
capabilities: vec![],
endpoint: None,
pricing: None,
};
Ok(Response::new()
.add_attribute("action", "create_and_register")
.add_attribute("agent_id", &agent_id)
.add_attribute("instance_id", next_id.to_string())
.add_message(create_msg)
.add_message(WasmMsg::Execute {
contract_addr: registry_addr,
msg: to_binary(®ister_msg)?,
funds: vec![],
}))
}
// 变体四:DAO 工厂 -- 治理合约 + Treasury 组合创建
// 一次创建完整的 DAO 包含: governance + treasury + timelock
pub fn execute_create_dao(
deps: DepsMut,
env: Env,
info: MessageInfo,
dao_name: String,
) -> StdResult<Response> {
let governance_init = DaoInstantiateMsg {
name: dao_name.clone(),
proposal_duration_blocks: 10080,
};
let treasury_init = TreasuryInstantiateMsg {
owner: env.contract.address.to_string(),
deposit_denom: "uusage".to_string(),
};
let timelock_init = TimelockInstantiateMsg {
owner: env.contract.address.to_string(),
delay_blocks: 10080,
};
Ok(Response::new()
.add_attribute("action", "create_dao")
.add_attribute("dao_name", &dao_name)
.add_message(WasmMsg::Instantiate {
admin: env.contract.address.to_string(),
code_id: 24,
msg: to_binary(&governance_init)?,
funds: vec![],
label: format!("dao-{}-gov", dao_name),
})
.add_message(WasmMsg::Instantiate {
admin: env.contract.address.to_string(),
code_id: 25,
msg: to_binary(&treasury_init)?,
funds: vec![],
label: format!("dao-{}-treasury", dao_name),
})
.add_message(WasmMsg::Instantiate {
admin: env.contract.address.to_string(),
code_id: 26,
msg: to_binary(&timelock_init)?,
funds: vec![],
label: format!("dao-{}-timelock", dao_name),
}))
}
4. Registry 发现与地址解析
4.1 Canonical Key 解析机制
MSG Chain 上的每个关键合约都有一个唯一 canonical key。通过 agent_registry_v1 的 Resolve 查询,可以将 canonical key 解析为当前实际的合约地址。
canonical key: "agent_payment_v1"
|
v
agent_registry_v1.Resolve { key: "agent_payment_v1" }
|
v
"msg1a2b3c4d5e6f7g8h9i0j..." <- 实际合约地址
Canonical key 解析的核心优势:
| 特性 | 说明 |
|---|---|
| 地址抽象 | 代码中使用 "agent_payment_v1" 而非硬编码地址 |
| 热替换 | 合约升级后只需更新 Registry 中的映射 |
| 多版本 | 同一 key 可以解析不同版本(如 agent_payment_v1 vs agent_payment_v2) |
| 蓝绿部署 | 键指向新的合约实现而旧合约继续运行 |
4.2 Registry 查询模式
// 基础 Registry 查询模式
use cosmwasm_std::{
to_binary, Binary, Deps, QuerierWrapper, QueryRequest,
StdResult, StdError, WasmQuery,
};
/// MSG Chain 系统合约的 canonical key
pub mod canonical_keys {
pub const AIDID_DID_REGISTRY: &str = "aidid_did_registry_v1";
pub const AGENT_REGISTRY: &str = "agent_registry_v1";
pub const AGENT_PAYMENT: &str = "agent_payment_v1";
pub const AGENT_A2A: &str = "agent_a2a_v1";
pub const AGENT_MPC: &str = "agent_mpc_v1";
pub const AI_AGENT_CONSTITUTION: &str = "ai_agent_constitution_v1";
pub const DAO_GOVERNANCE: &str = "dao_governance_v1";
}
/// 解析 canonical key 为合约地址
pub fn resolve_canonical_key(
querier: &QuerierWrapper,
registry_addr: &str,
canonical_key: &str,
) -> StdResult<String> {
let resolve_query = RegistryQuery::Resolve {
key: canonical_key.to_string(),
};
let result: ResolveResponse = querier.query(&QueryRequest::Wasm(
WasmQuery::Smart {
contract_addr: registry_addr.to_string(),
msg: to_binary(&resolve_query)?,
}
))?;
if !result.active {
return Err(StdError::generic_err(format!(
"Contract '{}' is not active (status: {})",
canonical_key, result.status
)));
}
Ok(result.address)
}
/// 解析并验证合约是否存在
pub fn resolve_and_verify(
querier: &QuerierWrapper,
registry_addr: &str,
canonical_key: &str,
expected_prefix: &str,
) -> StdResult<String> {
let address = resolve_canonical_key(querier, registry_addr, canonical_key)?;
if !address.starts_with(expected_prefix) {
return Err(StdError::generic_err(format!(
"Resolved address does not have expected prefix '{}'", expected_prefix
)));
}
Ok(address)
}
/// 批量解析多个 canonical key
pub fn resolve_multiple_keys(
querier: &QuerierWrapper,
registry_addr: &str,
keys: &[&str],
) -> StdResult<std::collections::HashMap<String, String>> {
let mut results = std::collections::HashMap::new();
for key in keys {
match resolve_canonical_key(querier, registry_addr, key) {
Ok(address) => { results.insert(key.to_string(), address); }
Err(e) => { results.insert(key.to_string(), format!("ERROR: {}", e)); }
}
}
Ok(results)
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct ResolveResponse {
pub key: String,
pub address: String,
pub active: bool,
pub status: String,
pub code_id: Option<u64>,
pub updated_at: Option<u64>,
}
// 带缓存的 Registry 解析
//
// 高频调用场景下,建议在合约自身状态中缓存 Registry 解析结果。
// 注意缓存需要定期刷新。
use cw_storage_plus::Map;
pub const ADDR_CACHE: Map<&str, CachedAddr> = Map::new("addr_cache");
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct CachedAddr {
pub address: String,
pub cached_at: u64,
pub ttl_blocks: u64,
}
/// 从缓存或 Registry 获取合约地址
pub fn get_contract_address(
deps: Deps,
registry_addr: &str,
canonical_key: &str,
current_height: u64,
cache_ttl: u64,
) -> StdResult<String> {
if let Ok(cached) = ADDR_CACHE.load(deps.storage, canonical_key) {
if current_height < cached.cached_at + cached.ttl_blocks {
return Ok(cached.address);
}
}
let address = resolve_canonical_key(&deps.querier, registry_addr, canonical_key)?;
ADDR_CACHE.save(deps.storage, canonical_key, &CachedAddr {
address: address.clone(),
cached_at: current_height,
ttl_blocks: cache_ttl,
})?;
Ok(address)
}
4.3 Agent Discovery 查询
// 通过能力搜索 Agent
pub fn find_agents_by_capability(
deps: Deps,
registry_addr: &str,
capability: &str,
limit: Option<u32>,
) -> StdResult<Vec<AgentResponse>> {
let find_query = RegistryQuery::FindByCapability {
capability: capability.to_string(),
limit,
};
let agents: Vec<AgentResponse> = deps.querier.query(&QueryRequest::Wasm(
WasmQuery::Smart {
contract_addr: registry_addr.to_string(),
msg: to_binary(&find_query)?,
}
))?;
Ok(agents.into_iter().filter(|a| a.status == "active").collect())
}
/// 完整发现流程:先解析 Registry 地址,再查询 Agent
pub fn discover_service_providers(
deps: Deps,
registry_addr: &str,
capability: &str,
max_results: u32,
) -> StdResult<Vec<AgentServiceProvider>> {
let agents = find_agents_by_capability(&deps, registry_addr, capability, Some(max_results))?;
let providers: Vec<AgentServiceProvider> = agents
.into_iter()
.map(|a| AgentServiceProvider {
agent_id: a.agent_id,
did: a.did,
endpoint: a.endpoint,
reputation: a.reputation,
capabilities: a.capabilities,
})
.collect();
Ok(providers)
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct AgentServiceProvider {
pub agent_id: String,
pub did: String,
pub endpoint: Option<String>,
pub reputation: u64,
pub capabilities: Vec<String>,
}
// 状态检查 -- 验证 Agent 是否可交互
pub fn check_agent_status(
deps: Deps,
registry_addr: &str,
agent_id: &str,
) -> StdResult<AgentStatusInfo> {
let result: StatusResponse = deps.querier.query(&QueryRequest::Wasm(
WasmQuery::Smart {
contract_addr: registry_addr.to_string(),
msg: to_binary(&RegistryQuery::Status {
agent_id: agent_id.to_string(),
})?,
}
))?;
Ok(AgentStatusInfo {
agent_id: agent_id.to_string(),
status: result.status,
active: result.active,
last_seen: result.last_seen,
})
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct AgentStatusInfo {
pub agent_id: String,
pub status: String,
pub active: bool,
pub last_seen: Option<u64>,
}
#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
pub struct StatusResponse {
pub status: String,
pub active: bool,
pub last_seen: Option<u64>,
}
4.4 DID 身份解析
// DID 解析集成
//
// 在组合多个合约时,通常需要验证 Agent 的 DID 身份。
// aidid_did_registry_v1 提供了 DID 文档的链上存储与解析。
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct DidDocument {
pub id: String,
pub public_key: String,
pub controller: Option<String>,
pub service_endpoints: Vec<ServiceEndpoint>,
pub authentication: Vec<String>,
pub created: u64,
pub updated: u64,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ServiceEndpoint {
pub id: String,
pub type_: String,
pub service_endpoint: String,
pub description: Option<String>,
}
/// 解析 Agent 的 DID 文档
pub fn resolve_agent_did(
deps: Deps,
did_registry_addr: &str,
did: &str,
) -> StdResult<DidDocument> {
let query = DidQuery::ResolveDid {
did: did.to_string(),
};
let doc: DidDocument = deps.querier.query(&QueryRequest::Wasm(
WasmQuery::Smart {
contract_addr: did_registry_addr.to_string(),
msg: to_binary(&query)?,
}
))?;
Ok(doc)
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum DidQuery {
ResolveDid { did: String },
ReverseLookup { address: String },
CheckDelegation { did: String, delegate: String },
}
// DID + Registry 联合验证
// 组合两个查询:验证 Agent 在 Registry 中的身份与其 DID 一致
pub fn verify_agent_identity(
deps: Deps,
registry_addr: &str,
did_registry_addr: &str,
agent_id: &str,
) -> StdResult<bool> {
let agent: AgentResponse = deps.querier.query(&QueryRequest::Wasm(
WasmQuery::Smart {
contract_addr: registry_addr.to_string(),
msg: to_binary(&RegistryQuery::GetAgent {
agent_id: agent_id.to_string(),
})?,
}
))?;
let did_doc: DidDocument = deps.querier.query(&QueryRequest::Wasm(
WasmQuery::Smart {
contract_addr: did_registry_addr.to_string(),
msg: to_binary(&DidQuery::ResolveDid {
did: agent.did.clone(),
})?,
}
))?;
Ok(did_doc.controller.as_deref() == Some(agent_id) || did_doc.id == agent.did)
}
4.5 宪章合规检查
// 宪法合规检查 -- ai_agent_constitution_v1
//
// 在执行跨合约调用前,Agent 可以检查拟执行的操作是否符合其宪法。
pub enum ConstitutionCheck {
Allowed,
Denied(String),
RequiresApproval(String),
}
/// 检查某个行动是否符合 Agent 的宪法
pub fn check_action_compliance(
deps: Deps,
constitution_addr: &str,
action: &str,
context: &str,
) -> StdResult<ConstitutionCheck> {
let result: ConstitutionCheckResponse = deps.querier.query(&QueryRequest::Wasm(
WasmQuery::Smart {
contract_addr: constitution_addr.to_string(),
msg: to_binary(&ConstitutionQuery::CheckAction {
action: action.to_string(),
context: context.to_string(),
})?,
}
))?;
if result.allowed {
Ok(ConstitutionCheck::Allowed)
} else if result.requires_approval {
Ok(ConstitutionCheck::RequiresApproval(result.reason))
} else {
Ok(ConstitutionCheck::Denied(result.reason))
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ConstitutionCheckResponse {
pub allowed: bool,
pub requires_approval: bool,
pub reason: String,
pub applicable_articles: Vec<u32>,
}
5. 组合合约模式
5.1 CW4626 代币化金库
CW4626 是 CosmWasm 生态系统中的代币化金库标准,类似 EVM 世界的 ERC-4626。它组合了 CW20 代币标准和策略执行逻辑。
用户存入资产 -> CW4626 Vault
|
+-- 铸造份额代币(CW20)给用户
+-- 将资产部署到收益策略
| |
| +---> 策略 A (借贷)
| +---> 策略 B (做市)
| +---> 策略 C (质押)
|
+-- 用户赎回时销毁份额代币,返还资产+收益
// CW4626 核心接口
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum Cw4626ExecuteMsg {
Deposit { assets: Uint128, receiver: String },
Withdraw { shares: Uint128, receiver: String, owner: String },
ClaimRewards { receiver: String },
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum Cw4626QueryMsg {
TotalAssets {},
TotalSupply {},
ConvertToShares { assets: Uint128 },
ConvertToAssets { shares: Uint128 },
MaxDeposit { user: String },
MaxWithdraw { owner: String },
AssetsOf { user: String },
SharesOf { user: String },
}
// CW4626 组合示例:AI Agent 收益金库
// 这个金库接收 Agent 的收入(uusage 代币),自动将其分配到
// 多个 DeFi 策略中,并为每个 Agent 铸造代表其份额的 CW20 代币。
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct AgentVaultConfig {
pub asset_denom: String,
pub share_token_addr: Addr,
pub strategies: Vec<StrategyConfig>,
pub performance_fee_bps: u64,
pub management_fee_bps: u64,
pub admin: Addr,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct StrategyConfig {
pub name: String,
pub contract_addr: Addr,
pub allocation_bps: u64,
pub active: bool,
}
pub const VAULT_CONFIG: Item<AgentVaultConfig> = Item::new("vault_config");
pub const USER_DEPOSITS: Map<&Addr, Uint128> = Map::new("user_deposits");
// 存款流程 -- 组合 CW20 + 策略调用
pub fn execute_deposit(
deps: DepsMut,
env: Env,
info: MessageInfo,
assets: Uint128,
receiver: String,
) -> StdResult<Response> {
let config = VAULT_CONFIG.load(deps.storage)?;
let receiver_addr = deps.api.addr_validate(&receiver)?;
let deposit = info
.funds
.iter()
.find(|c| c.denom == config.asset_denom)
.map(|c| c.amount)
.unwrap_or(Uint128::zero());
if deposit != assets {
return Err(StdError::generic_err("Deposit amount does not match sent funds"));
}
let total_assets = query_total_assets(&deps.querier, &config)?;
let total_shares = query_total_shares(&deps.querier, &config.share_token_addr)?;
let shares = if total_assets.is_zero() || total_shares.is_zero() {
assets
} else {
assets.multiply_ratio(total_shares, total_assets)
};
let mut messages: Vec<CosmosMsg> = vec![];
messages.push(WasmMsg::Execute {
contract_addr: config.share_token_addr.to_string(),
msg: to_binary(&Cw20ExecuteMsg::Mint {
recipient: receiver_addr.to_string(),
amount: shares,
})?,
funds: vec![],
}.into());
for strategy in &config.strategies {
if !strategy.active { continue; }
let strategy_amount = assets.multiply_ratio(strategy.allocation_bps, 10000u128);
if strategy_amount.is_zero() { continue; }
messages.push(BankMsg::Send {
to_address: strategy.contract_addr.to_string(),
amount: vec![Coin { denom: config.asset_denom.clone(), amount: strategy_amount }],
}.into());
}
USER_DEPOSITS.update(
deps.storage,
&receiver_addr,
|existing| -> StdResult<Uint128> {
Ok(existing.unwrap_or_default() + assets)
},
)?;
Ok(Response::new()
.add_attribute("action", "vault_deposit")
.add_attribute("receiver", &receiver)
.add_attribute("shares", shares.to_string())
.add_messages(messages))
}
fn query_total_assets(querier: &QuerierWrapper, config: &AgentVaultConfig) -> StdResult<Uint128> {
let vault_balance = querier.query_balance("", &config.asset_denom)?;
Ok(vault_balance)
}
fn query_total_shares(querier: &QuerierWrapper, share_token_addr: &Addr) -> StdResult<Uint128> {
let info: Cw20TokenInfoResponse = querier.query(&QueryRequest::Wasm(
WasmQuery::Smart {
contract_addr: share_token_addr.to_string(),
msg: to_binary(&Cw20QueryMsg::TokenInfo {})?,
}
))?;
Ok(info.total_supply)
}
// Cw20ExecuteMsg / Cw20QueryMsg 定义
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum Cw20ExecuteMsg {
Transfer { recipient: String, amount: Uint128 },
Burn { amount: Uint128 },
Mint { recipient: String, amount: Uint128 },
BurnFrom { owner: String, amount: Uint128 },
Approve { spender: String, amount: Uint128 },
TransferFrom { owner: String, recipient: String, amount: Uint128 },
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum Cw20QueryMsg {
Balance { address: String },
TokenInfo {},
Allowance { owner: String, spender: String },
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct Cw20TokenInfoResponse {
pub name: String,
pub symbol: String,
pub decimals: u8,
pub total_supply: Uint128,
}
5.2 Agent + Payment + Registry 三合约组合
最常见的 AI Agent 业务场景是"提供服务并收取费用"。这需要组合 agent_registry_v1 (身份与发现)、agent_payment_v1(支付结算)和 Agent 自身的业务逻辑。
调用方 -- AgentService.provide_service()
|
+-- 1. query Registry --> agent_registry_v1
| 验证调用方身份
|
+-- 2. query DID Registry --> aidid_did_registry_v1
| 解析调用方公钥
|
+-- 3. 验证签名
|
+-- 4. execute Payment --> agent_payment_v1
| 结算费用
|
+-- 5. 执行实际服务逻辑
// Agent 服务合约 -- 集成 Registry + DID + Payment
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ServiceConfig {
pub service_type: String,
pub price: Uint128,
pub denom: String,
pub registry_addr: String,
pub did_registry_addr: String,
pub payment_addr: String,
pub owner: Addr,
}
pub const SERVICE_CONFIG: Item<ServiceConfig> = Item::new("service_config");
pub const SERVICE_USAGE: Map<&Addr, u64> = Map::new("service_usage");
/// 提供 AI 推理服务
pub fn execute_provide_service(
deps: DepsMut,
env: Env,
info: MessageInfo,
consumer_did: String,
input_data: String,
consumer_signature: String,
) -> StdResult<Response> {
let config = SERVICE_CONFIG.load(deps.storage)?;
// 阶段 1:身份验证 -- 组合 Registry + DID 查询
let did_doc = resolve_agent_did(
&deps.as_ref(), &config.did_registry_addr, &consumer_did,
)?;
let is_valid = !did_doc.public_key.is_empty() && !consumer_signature.is_empty();
if !is_valid {
return Err(StdError::generic_err("Invalid signature"));
}
// 阶段 2:权限验证 -- Registry 状态检查
let status = check_agent_status(
&deps.as_ref(), &config.registry_addr, &consumer_did,
)?;
if !status.active {
return Err(StdError::generic_err("Consumer agent is not active"));
}
// 阶段 3:支付结算 -- Payment 合约转账
let payment_msg = PaymentExecuteMsg::Transfer {
recipient: config.owner.to_string(),
amount: config.price,
denom: config.denom.clone(),
memo: Some(format!("Service: {}", config.service_type)),
};
// 阶段 4:服务执行 + 记录使用量
let consumer_addr = deps.api.addr_validate(
&did_doc.controller.unwrap_or(consumer_did.clone()),
)?;
SERVICE_USAGE.update(
deps.storage, &consumer_addr,
|count| -> StdResult<u64> { Ok(count.unwrap_or(0) + 1) },
)?;
Ok(Response::new()
.add_attribute("action", "provide_service")
.add_attribute("consumer", &consumer_did)
.add_attribute("price", config.price.to_string())
.add_message(WasmMsg::Execute {
contract_addr: config.payment_addr,
msg: to_binary(&payment_msg)?,
funds: vec![],
}))
}
5.3 MPC 多签 + A2A 通信组合
// MPC 多签决策 + A2A 消息传递
//
// 场景:一个 Agent 联盟需要共同决策后执行某个操作。
// 流程:
// 1. 联盟成员通过 agent_mpc_v1 发起多签提案
// 2. 收集足够的签名后,提案自动执行
// 3. 执行结果通过 agent_a2a_v1 通知所有成员
pub fn execute_mpc_proposal_with_notification(
deps: DepsMut,
env: Env,
info: MessageInfo,
mpc_addr: String,
a2a_addr: String,
target_contract: String,
target_msg: Binary,
description: String,
notify_members: Vec<String>,
) -> StdResult<Response> {
let propose_msg = MpcExecuteMsg::ProposeTransaction {
target: target_contract,
msg: target_msg,
description: description.clone(),
expires_at: env.block.height + 10080,
};
let mut messages: Vec<CosmosMsg> = vec![
WasmMsg::Execute {
contract_addr: mpc_addr,
msg: to_binary(&propose_msg)?,
funds: vec![],
}.into(),
];
for member in ¬ify_members {
let notify_msg = A2AExecuteMsg::SendMessage {
recipient: member.clone(),
payload: format!("New MPC proposal: {}. Sign via agent_mpc_v1.", description),
priority: Some(1),
};
messages.push(WasmMsg::Execute {
contract_addr: a2a_addr.clone(),
msg: to_binary(¬ify_msg)?,
funds: vec![],
}.into());
}
Ok(Response::new()
.add_attribute("action", "mpc_proposal_with_notification")
.add_attribute("proposer", info.sender)
.add_messages(messages))
}
// MPC 签名收集 + 自动执行 + A2A 广播结果
pub fn execute_collect_and_execute(
deps: DepsMut,
_env: Env,
info: MessageInfo,
mpc_addr: String,
proposal_id: String,
a2a_addr: String,
members: Vec<String>,
) -> StdResult<Response> {
let proposal: MpcProposalResponse = deps.querier.query(&QueryRequest::Wasm(
WasmQuery::Smart {
contract_addr: mpc_addr.clone(),
msg: to_binary(&MpcQuery::GetProposal {
proposal_id: proposal_id.clone(),
})?,
}
))?;
if proposal.signatures.len() < proposal.threshold as usize {
return Err(StdError::generic_err("Not enough signatures yet"));
}
let execute_msg = WasmMsg::Execute {
contract_addr: mpc_addr,
msg: to_binary(&MpcExecuteMsg::ExecuteSigned {
proposal_id: proposal_id.clone(),
})?,
funds: vec![],
};
Ok(Response::new()
.add_attribute("action", "execute_mpc_proposal")
.add_attribute("proposal_id", &proposal_id)
.add_submessage(SubMsg {
id: EXECUTION_REPLY_ID,
msg: execute_msg.into(),
gas_limit: None,
reply_on: ReplyOn::Success,
}))
}
pub const EXECUTION_REPLY_ID: u64 = 10;
fn handle_execution_reply(
_deps: DepsMut,
_env: Env,
msg: cosmwasm_std::Reply,
a2a_addr: String,
members: Vec<String>,
proposal_id: String,
) -> StdResult<Response> {
match msg.result {
SubMsgResult::Ok(_) => {
let mut messages = vec![];
for member in &members {
messages.push(WasmMsg::Execute {
contract_addr: a2a_addr.clone(),
msg: to_binary(&A2AExecuteMsg::SendMessage {
recipient: member.clone(),
payload: format!("Proposal {} executed successfully", proposal_id),
priority: Some(1),
})?,
funds: vec![],
}.into());
}
Ok(Response::new()
.add_attribute("action", "mpc_execution_succeeded")
.add_attribute("proposal_id", &proposal_id)
.add_messages(messages))
}
SubMsgResult::Err(err) => {
let mut messages = vec![];
for member in &members {
messages.push(WasmMsg::Execute {
contract_addr: a2a_addr.clone(),
msg: to_binary(&A2AExecuteMsg::SendMessage {
recipient: member.clone(),
payload: format!("Proposal {} failed: {}", proposal_id, err),
priority: Some(2),
})?,
funds: vec![],
}.into());
}
Ok(Response::new()
.add_attribute("action", "mpc_execution_failed")
.add_attribute("proposal_id", &proposal_id)
.add_messages(messages))
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct MpcProposalResponse {
pub id: String,
pub target: String,
pub msg: Binary,
pub proposer: String,
pub signers: Vec<String>,
pub signatures: Vec<String>,
pub threshold: u64,
pub expires_at: u64,
pub executed: bool,
}
5.4 DAO + Treasury + Timelock 组合
// DAO 治理 + Treasury + Timelock 三合约组合
//
// 完整的去中心化治理体系需要三个合约协同工作:
//
// dao_governance_v1: 提案与投票
// |
// v
// Timelock: 延迟执行(防止恶意操作)
// |
// v
// Treasury: 资金管理
//
// 典型流程:
// 1. 成员在 DAO 创建提案(附带 Treasury 操作)
// 2. 投票通过后,提案进入 Timelock 排队
// 3. 延迟期过后,Timelock 执行 Treasury 转账
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct DaoTreasuryConfig {
pub dao_addr: Addr,
pub treasury_addr: Addr,
pub timelock_addr: Addr,
pub proposal_duration_blocks: u64,
pub timelock_delay_blocks: u64,
}
/// 创建完整的资金提取提案 -- 组合 DAO + Timelock + Treasury
pub fn execute_create_treasury_proposal(
deps: DepsMut,
info: MessageInfo,
config: DaoTreasuryConfig,
title: String,
description: String,
recipient: String,
amount: Uint128,
denom: String,
) -> StdResult<Response> {
let treasury_transfer = CosmosMsg::Bank(BankMsg::Send {
to_address: recipient.clone(),
amount: vec![Coin { denom: denom.clone(), amount }],
});
let timelock_schedule = WasmMsg::Execute {
contract_addr: config.timelock_addr.to_string(),
msg: to_binary(&TimelockExecuteMsg::Schedule {
target: config.treasury_addr.to_string(),
msg: to_binary(&treasury_transfer)?,
after_blocks: config.timelock_delay_blocks,
})?,
funds: vec![],
};
let propose_msg = DaoExecuteMsg::Propose {
title,
description,
msgs: vec![timelock_schedule.into()],
};
Ok(Response::new()
.add_attribute("action", "create_treasury_proposal")
.add_attribute("recipient", &recipient)
.add_attribute("amount", amount.to_string())
.add_message(WasmMsg::Execute {
contract_addr: config.dao_addr.to_string(),
msg: to_binary(&propose_msg)?,
funds: vec![],
}))
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "snake_case")]
pub enum TimelockExecuteMsg {
Schedule { target: String, msg: Binary, after_blocks: u64 },
Execute { operation_id: String },
Cancel { operation_id: String },
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct DaoInstantiateMsg {
pub name: String,
pub proposal_duration_blocks: u64,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TreasuryInstantiateMsg {
pub owner: String,
pub deposit_denom: String,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct TimelockInstantiateMsg {
pub owner: String,
pub delay_blocks: u64,
}
5.5 多合约工作流编排
// 复杂多合约工作流编排
//
// 场景:Agent 市场中的"发布任务 -> 接受任务 -> 交付结果 -> 支付"
// 涉及合约:
// 1. agent_registry_v1 -- 发现服务提供商
// 2. agent_a2a_v1 -- 协商任务细节
// 3. agent_payment_v1 -- 托管付款
// 4. AI Agent contract -- 实际执行任务
// 5. ai_agent_constitution_v1 -- 验证合规性
/// 工作流阶段枚举
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum WorkflowStage {
Idle, DiscoveringProvider, Negotiating, EscrowFunding,
TaskExecution, Verification, Completed, Disputed, Cancelled,
}
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct WorkflowState {
pub id: String,
pub requester: Addr,
pub provider: Option<Addr>,
pub stage: WorkflowStage,
pub task_description: String,
pub price: Uint128,
pub denom: String,
pub escrow_id: Option<String>,
pub result: Option<String>,
pub expires_at: u64,
pub created_at: u64,
}
pub const WORKFLOWS: Map<&str, WorkflowState> = Map::new("workflows");
pub const WORKFLOW_COUNT: Item<u64> = Item::new("wf_count");
/// 第一步:发起任务请求(同时检查宪法合规性)
pub fn execute_request_task(
deps: DepsMut,
env: Env,
info: MessageInfo,
constitution_addr: String,
registry_addr: String,
task_description: String,
required_capability: String,
max_price: Uint128,
denom: String,
) -> StdResult<Response> {
let compliance = check_action_compliance(
&deps.as_ref(), &constitution_addr, "request_task", &task_description,
)?;
match compliance {
ConstitutionCheck::Denied(reason) => {
return Err(StdError::generic_err(format!("Action denied: {}", reason)));
}
ConstitutionCheck::RequiresApproval(_) => {}
ConstitutionCheck::Allowed => {}
}
let providers = find_agents_by_capability(
&deps.as_ref(), ®istry_addr, &required_capability, Some(5),
)?;
if providers.is_empty() {
return Err(StdError::generic_err("No available providers found"));
}
let count = WORKFLOW_COUNT.load(deps.storage).unwrap_or(0) + 1;
let workflow_id = format!("wf-{}", count);
let workflow = WorkflowState {
id: workflow_id.clone(),
requester: info.sender,
provider: None,
stage: WorkflowStage::DiscoveringProvider,
task_description,
price: max_price,
denom,
escrow_id: None,
result: None,
expires_at: env.block.height + 20160,
created_at: env.block.height,
};
WORKFLOWS.save(deps.storage, &workflow_id, &workflow)?;
WORKFLOW_COUNT.save(deps.storage, &count)?;
Ok(Response::new()
.add_attribute("action", "request_task")
.add_attribute("workflow_id", &workflow_id)
.add_attribute("providers_found", providers.len().to_string()))
}
/// 第二步:选择提供商并创建托管
pub fn execute_select_provider(
deps: DepsMut,
env: Env,
info: MessageInfo,
workflow_id: String,
provider_addr: String,
payment_addr: String,
) -> StdResult<Response> {
let mut workflow = WORKFLOWS.load(deps.storage, &workflow_id)?;
if info.sender != workflow.requester {
return Err(StdError::generic_err("Only requester can select provider"));
}
if workflow.stage != WorkflowStage::DiscoveringProvider {
return Err(StdError::generic_err("Invalid workflow stage"));
}
let provider = deps.api.addr_validate(&provider_addr)?;
workflow.provider = Some(provider.clone());
workflow.stage = WorkflowStage::EscrowFunding;
let escrow_msg = PaymentExecuteMsg::Escrow {
recipient: provider.to_string(),
amount: workflow.price,
condition: format!("workflow:{}", workflow_id),
};
WORKFLOWS.save(deps.storage, &workflow_id, &workflow)?;
Ok(Response::new()
.add_attribute("action", "select_provider")
.add_attribute("workflow_id", &workflow_id)
.add_attribute("provider", &provider_addr)
.add_message(WasmMsg::Execute {
contract_addr: payment_addr,
msg: to_binary(&escrow_msg)?,
funds: vec![Coin { denom: workflow.denom.clone(), amount: workflow.price }],
}))
}
/// 第三步:确认交付并释放托管
pub fn execute_confirm_delivery(
deps: DepsMut,
_env: Env,
info: MessageInfo,
workflow_id: String,
payment_addr: String,
a2a_addr: String,
escrow_id: String,
) -> StdResult<Response> {
let mut workflow = WORKFLOWS.load(deps.storage, &workflow_id)?;
if info.sender != workflow.requester {
return Err(StdError::generic_err("Only requester can confirm"));
}
if workflow.stage != WorkflowStage::TaskExecution {
return Err(StdError::generic_err("Invalid workflow stage"));
}
workflow.stage = WorkflowStage::Completed;
WORKFLOWS.save(deps.storage, &workflow_id, &workflow)?;
let release_msg = PaymentExecuteMsg::ReleaseEscrow {
escrow_id: escrow_id.clone(),
};
let notify_msg = A2AExecuteMsg::SendMessage {
recipient: workflow.provider.unwrap().to_string(),
payload: format!("Task {} confirmed. Escrow released.", workflow_id),
priority: None,
};
Ok(Response::new()
.add_attribute("action", "confirm_delivery")
.add_attribute("workflow_id", &workflow_id)
.add_message(WasmMsg::Execute {
contract_addr: payment_addr,
msg: to_binary(&release_msg)?,
funds: vec![],
})
.add_message(WasmMsg::Execute {
contract_addr: a2a_addr,
msg: to_binary(¬ify_msg)?,
funds: vec![],
}))
}
6. 安全考量
6.1 重入保护
CosmWasm 的同步执行模型天然防御了经典的重入攻击 -- 合约在查询请求中不会执行状态变更。但在跨合约消息流中仍需注意:
// 重入保护机制
use cw_storage_plus::Item;
pub const REENTRANCY_LOCK: Item<bool> = Item::new("reentrancy_lock");
/// 带重入保护的执行包装器
pub fn with_reentrancy_protection<F, T>(
storage: &mut dyn Storage,
f: F,
) -> StdResult<T>
where
F: FnOnce() -> StdResult<T>,
{
if REENTRANCY_LOCK.load(storage).unwrap_or(false) {
return Err(StdError::generic_err("Reentrancy detected"));
}
REENTRANCY_LOCK.save(storage, &true)?;
let result = f();
REENTRANCY_LOCK.save(storage, &false)?;
result
}
// 使用示例:
pub fn execute_protected_operation(deps: DepsMut) -> StdResult<Response> {
with_reentrancy_protection(deps.storage, || {
Ok(Response::new().add_attribute("action", "protected_operation"))
})
}
// 跨合约重入风险示例
//
// 风险场景:
// Contract A 调用 Contract B,Contract B 在返回前又回调 Contract A
//
// CosmWasm 处理方式:
// - 同步查询 (WasmQuery::Smart) 是只读的,不修改状态
// - 异步消息 (WasmMsg::Execute) 在当前合约执行完后才执行
// - 因此 CosmWasm 中不会有"在修改状态过程中被重入"的问题
//
// 仍需注意:
// 1. Reply 回调中可能触发新的状态变更
// 2. 多个 SubMsg 之间的状态依赖需要谨慎设计
// 安全的状态更新模式
/// 反模式:在发出子调用后修改状态
pub fn bad_pattern(deps: DepsMut, target: String) -> StdResult<Response> {
// 错误:先更新了状态,然后发出调用
// 如果 SomeMsg 执行失败,整个交易回滚,状态也会回滚
// 但如果在 reply 中处理错误,状态可能已提交
Ok(Response::new()
.add_message(WasmMsg::Execute {
contract_addr: target,
msg: to_binary(&SomeMsg {})?,
funds: vec![],
}))
}
/// 正确模式:使用 reply 处理可能的失败
pub fn good_pattern(deps: DepsMut, target: String) -> StdResult<Response> {
Ok(Response::new()
.add_submessage(SubMsg {
id: 1,
msg: WasmMsg::Execute {
contract_addr: target,
msg: to_binary(&SomeMsg {})?,
funds: vec![],
}.into(),
gas_limit: None,
reply_on: ReplyOn::Success,
}))
}
6.2 跨合约访问控制
// 跨合约权限验证
/// 合约级访问控制列表
pub const CONTRACT_WHITELIST: Map<&Addr, bool> = Map::new("contract_whitelist");
/// 添加受信合约
pub fn add_trusted_contract(
deps: DepsMut,
info: MessageInfo,
admin: Addr,
contract: Addr,
) -> StdResult<Response> {
if info.sender != admin {
return Err(StdError::generic_err("Unauthorized"));
}
CONTRACT_WHITELIST.save(deps.storage, &contract, &true)?;
Ok(Response::new()
.add_attribute("action", "add_trusted_contract")
.add_attribute("contract", contract))
}
/// 验证调用合约是否在受信任列表中
pub fn assert_trusted_caller(deps: &Deps, caller: &Addr) -> StdResult<()> {
if !CONTRACT_WHITELIST.load(deps.storage, caller).unwrap_or(false) {
return Err(StdError::generic_err(format!(
"Caller {} is not in trusted list", caller
)));
}
Ok(())
}
// 跨合约授权模式
/// 模式 1:委托式 -- 合约 A 授权合约 B 代为执行某些操作
/// 类似 CW20 的 Allowance 机制
pub mod cross_contract_auth {
use cw_storage_plus::Map;
pub const DELEGATED_AUTHORITY: Map<(&Addr, &Addr, &str), AuthorityLevel> =
Map::new("delegated_auth");
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum AuthorityLevel {
None,
Limited { max_amount: Uint128, used: Uint128 },
Full,
}
pub fn grant_authority(
storage: &mut dyn Storage,
granter: &Addr,
grantee: &Addr,
action: &str,
level: AuthorityLevel,
) -> StdResult<()> {
DELEGATED_AUTHORITY.save(
storage,
&(granter.clone(), grantee.clone(), action.to_string()),
&level,
)?;
Ok(())
}
pub fn check_authority(
storage: &dyn Storage,
granter: &Addr,
grantee: &Addr,
action: &str,
) -> StdResult<bool> {
let level = DELEGATED_AUTHORITY
.load(storage, &(granter.clone(), grantee.clone(), action.to_string()))
.unwrap_or(AuthorityLevel::None);
Ok(!matches!(level, AuthorityLevel::None))
}
}
/// 模式 2:策略式 -- 合约根据调用者地址动态决定权限
pub fn check_agent_permission(
deps: &Deps,
registry_addr: &str,
agent_id: &str,
required_role: &str,
) -> StdResult<bool> {
let agent: AgentResponse = deps.querier.query(&QueryRequest::Wasm(
WasmQuery::Smart {
contract_addr: registry_addr.to_string(),
msg: to_binary(&RegistryQuery::GetAgent {
agent_id: agent_id.to_string(),
})?,
}
))?;
let metadata = agent.metadata.unwrap_or_default();
Ok(metadata.contains(required_role))
}
6.3 消息排序与原子性
// 消息排序的注意事项
//
// 在同一个 Response 中添加多个消息时,消息按添加顺序执行。
// 理解这个顺序对正确组合合约至关重要。
pub fn demonstrate_message_ordering(
deps: DepsMut,
contract_a: String,
contract_b: String,
contract_c: String,
) -> StdResult<Response> {
// 消息执行顺序:
// 1. contract_a.execute(msg_a)
// 2. contract_b.execute(msg_b)
// 3. contract_c.execute(msg_c)
Ok(Response::new()
.add_message(WasmMsg::Execute {
contract_addr: contract_a,
msg: to_binary(&MsgA {})?,
funds: vec![],
})
.add_message(WasmMsg::Execute {
contract_addr: contract_b,
msg: to_binary(&MsgB {})?,
funds: vec![],
})
.add_message(WasmMsg::Execute {
contract_addr: contract_c,
msg: to_binary(&MsgC {})?,
funds: vec![],
}))
}
// 原子性保证与回滚
//
// CosmWasm 的交易原子性保证:如果同一交易中的任何消息失败,
// 所有状态变更(包括之前已成功的消息)都会回滚。
// 但注意:SubMsg 的失败不一定会回滚父合约的状态。
// 这取决于 reply_on 设置:
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum AtomicityMode {
StrictAtomic,
SoftAtomic,
}
pub fn demonstrate_atomicity_modes(
target: String,
mode: AtomicityMode,
) -> StdResult<Response> {
match mode {
AtomicityMode::StrictAtomic => {
Ok(Response::new()
.add_message(WasmMsg::Execute {
contract_addr: target,
msg: to_binary(&CriticalMsg {})?,
funds: vec![],
}))
}
AtomicityMode::SoftAtomic => {
Ok(Response::new()
.add_submessage(SubMsg {
id: 1,
msg: WasmMsg::Execute {
contract_addr: target,
msg: to_binary(&NonCriticalMsg {})?,
funds: vec![],
}.into(),
gas_limit: None,
reply_on: ReplyOn::Error,
}))
}
}
}
6.4 错误处理
// 跨合约错误传播
#[derive(Serialize, Deserialize, Clone, Debug)]
pub enum CompositionError {
RegistryResolutionFailed(String),
DidVerificationFailed(String),
PaymentFailed(String),
TargetExecutionFailed(String),
Timeout(String),
ConstitutionViolation(String),
}
impl From<CompositionError> for StdError {
fn from(e: CompositionError) -> Self {
StdError::generic_err(format!("CompositionError: {}", match e {
CompositionError::RegistryResolutionFailed(m)
| CompositionError::DidVerificationFailed(m)
| CompositionError::PaymentFailed(m)
| CompositionError::TargetExecutionFailed(m)
| CompositionError::Timeout(m)
| CompositionError::ConstitutionViolation(m) => m,
}))
}
}
/// 带详细上下文的错误包装
pub fn wrap_composition_error(context: &str, error: StdError) -> StdError {
StdError::generic_err(format!("[Composition:{}] {}", context, error))
}
// 使用示例:
pub fn safe_composition_call(
deps: DepsMut,
registry_addr: &str,
target_key: &str,
) -> StdResult<Response> {
let _target_addr = resolve_canonical_key(&deps.querier, registry_addr, target_key)
.map_err(|e| wrap_composition_error(
&format!("resolve({})", target_key), e,
))?;
Ok(Response::new())
}
6.5 Gas 核算最佳实践
// Gas 核算与优化
//
// MSG Chain 的每个区块有 gas 上限。跨合约调用消耗更多 gas,
// 因此组合合约必须关注 gas 效率。
// Gas 消耗因素:
// 1. 基础操作: ~10_000 gas
// 2. 存储写入(Set): ~40_000 gas (每 32 字节)
// 3. 存储读取(Get): ~10_000 gas
// 4. WasmMsg::Execute: 基础 20_000 + 目标合约执行 gas
// 5. WasmQuery::Smart: ~15_000 gas + 目标合约查询 gas
// 6. SubMsg: 额外 5_000 gas 管理费
/// Gas 预算建议
pub mod gas_budget {
pub const SIMPLE_CALL: u64 = 200_000;
pub const CALL_WITH_QUERY: u64 = 300_000;
pub const THREE_CONTRACT_COMPOSITION: u64 = 600_000;
pub const COMPLEX_WORKFLOW: u64 = 1_200_000;
pub const BATCH_PROCESSING: u64 = 5_000_000;
}
// Gas 优化技巧:
//
// 1. 批量查询合并 -- 如果目标合约支持,使用批量查询而非多次单独查询
//
// 2. 缓存 Registry 解析结果 -- 同一交易中多次使用同一个 canonical key,只需解析一次
//
// 3. 减少不必要的存储写入 -- 只在必要时存储中间状态
//
// 4. 使用 RawQuery 替代 SmartQuery -- RawQuery 更轻量(不需要 JSON 序列化/反序列化)
//
// 5. 选择合适的分页大小 -- 查询列表时,不要请求超过需要的数量
/// RawQuery 示例(比 SmartQuery 更省 gas)
pub fn raw_query_strategy(
deps: &Deps,
contract_addr: &str,
storage_key: &[u8],
) -> StdResult<Option<Vec<u8>>> {
let result: Option<Binary> = deps.querier.query(&QueryRequest::Wasm(
WasmQuery::Raw {
contract_addr: contract_addr.to_string(),
key: storage_key.to_vec().into(),
}
))?;
Ok(result.map(|b| b.to_vec()))
}
7. AI Agent 最佳实践
7.1 组合 vs 单体的决策框架
// 何时组合,何时单体
//
// AI Agent 在架构设计时面临的首要决策:是将功能拆分为多个
// 合约(组合)还是放在一个合约中(单体)?
pub struct CompositionDecisionMatrix;
impl CompositionDecisionMatrix {
/// 根据以下因素评分,分数越高越倾向于组合模式
pub fn evaluate(situation: &Situation) -> f64 {
let mut score = 0.0;
if situation.functions_share_state { score -= 2.0; }
if situation.functions_have_clear_boundaries { score += 2.0; }
if situation.need_independent_upgrades { score += 3.0; }
if situation.cross_call_frequency == CallFrequency::High { score -= 1.5; }
if situation.submodule_lifecycle_independent { score += 2.0; }
if situation.different_trust_levels { score += 3.0; }
if situation.module_reusable_by_others { score += 2.0; }
score
}
}
pub struct Situation {
pub functions_share_state: bool,
pub functions_have_clear_boundaries: bool,
pub need_independent_upgrades: bool,
pub cross_call_frequency: CallFrequency,
pub submodule_lifecycle_independent: bool,
pub different_trust_levels: bool,
pub module_reusable_by_others: bool,
}
pub enum CallFrequency { Low, Medium, High }
// 决策指南:
//
// score > 5: 强烈推荐组合模式
// score 2-5: 组合模式,但注意优化 gas
// score -2-2: 视具体情况,可以组合但需要权衡
// score < -2: 推荐单体模式
7.2 组合合约测试策略
// 组合合约的测试方法论
//
// 测试组合合约比测试单体合约更复杂,因为需要模拟多个合约之间的交互。
// 单元测试 -- 模拟跨合约查询
#[cfg(test)]
mod tests {
use super::*;
use cosmwasm_std::testing::{
mock_dependencies, mock_env, mock_info,
MockQuerier, MockApi, MockStorage,
};
use cosmwasm_std::{from_binary, ContractResult, SystemResult};
fn setup_mock_registry_querier() -> MockQuerier {
let mut querier = MockQuerier::new(&[]);
querier.update_wasm(|query| match query {
WasmQuery::Smart { contract_addr: _, msg } => {
let query_msg: RegistryQuery = from_binary(msg).unwrap();
match query_msg {
RegistryQuery::Resolve { key } => {
SystemResult::Ok(ContractResult::Ok(
to_binary(&ResolveResponse {
key: key.clone(),
address: format!("msg1{}_addr", key.replace('_', "")),
active: true,
status: "active".to_string(),
code_id: Some(1),
updated_at: Some(1000),
}).unwrap(),
))
}
RegistryQuery::Exists { agent_id: _ } => {
SystemResult::Ok(ContractResult::Ok(
to_binary(&true).unwrap(),
))
}
_ => SystemResult::Err(
cosmwasm_std::SystemError::InvalidRequest {
error: "unexpected query".to_string(),
request: Default::default(),
}
),
}
}
_ => {
SystemResult::Err(cosmwasm_std::SystemError::InvalidRequest {
error: "unexpected query type".to_string(),
request: Default::default(),
})
}
});
querier
}
#[test]
fn test_resolve_canonical_key() {
let deps = mock_dependencies();
let querier = setup_mock_registry_querier();
let deps_with_querier = Deps {
storage: deps.storage,
api: MockApi::default(),
querier: querier.into(),
};
let result = resolve_canonical_key(
&deps_with_querier.querier,
"msg_registry_addr",
"agent_payment_v1",
);
assert!(result.is_ok());
assert!(result.unwrap().contains("msg1"));
}
#[test]
fn test_resolve_inactive_contract() {
let deps = mock_dependencies();
let mut querier = setup_mock_registry_querier();
querier.update_wasm(|query| match query {
WasmQuery::Smart { contract_addr: _, msg } => {
let query_msg: RegistryQuery = from_binary(msg).unwrap();
match query_msg {
RegistryQuery::Resolve { key } => {
SystemResult::Ok(ContractResult::Ok(
to_binary(&ResolveResponse {
key,
address: "msg_inactive".to_string(),
active: false,
status: "paused".to_string(),
code_id: None,
updated_at: None,
}).unwrap(),
))
}
_ => SystemResult::Err(cosmwasm_std::SystemError::InvalidRequest {
error: "unexpected".to_string(),
request: Default::default(),
}),
}
}
_ => SystemResult::Err(cosmwasm_std::SystemError::InvalidRequest {
error: "unexpected".to_string(),
request: Default::default(),
}),
});
let deps_with_querier = Deps {
storage: deps.storage,
api: MockApi::default(),
querier: querier.into(),
};
let result = resolve_canonical_key(
&deps_with_querier.querier,
"msg_registry_addr",
"agent_payment_v1",
);
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("not active"));
}
}
// 集成测试(骨架)
// 在集成测试中,建议使用以下策略:
//
// 1. 使用 multi_test 框架模拟多合约环境(cw-multi-test crate)
// 2. 测试完整的调用链路
// 3. 测试失败路径
#[cfg(test)]
mod integration_tests {
use cosmwasm_std::Addr;
use cw_multi_test::{App, ContractWrapper, Executor};
fn setup_app() -> App {
App::default()
}
fn store_agent_contract(app: &mut App) -> u64 {
let contract = ContractWrapper::new(
execute_agent_contract,
instantiate_agent_contract,
query_agent_contract,
);
app.store_code(Box::new(contract))
}
fn store_registry_contract(app: &mut App) -> u64 {
let contract = ContractWrapper::new(
execute_registry, instantiate_registry, query_registry,
);
app.store_code(Box::new(contract))
}
#[test]
fn test_full_service_workflow() {
let mut app = setup_app();
let registry_code = store_registry_contract(&mut app);
let agent_code = store_agent_contract(&mut app);
let registry_addr = app.instantiate_contract(
registry_code, Addr::unchecked("admin"),
&RegistryInstantiateMsg {}, &[], "registry", None,
).unwrap();
let agent_addr = app.instantiate_contract(
agent_code, Addr::unchecked("admin"),
&AgentInstantiateMsg {
registry_addr: registry_addr.to_string(),
payment_addr: "msg_payment".to_string(),
}, &[], "agent", None,
).unwrap();
let result = app.execute_contract(
Addr::unchecked("consumer"), agent_addr,
&AgentExecuteMsg::ProvideService {
consumer_did: "did:msg:consumer".to_string(),
input_data: "test_input".to_string(),
consumer_signature: "sig".to_string(),
}, &[],
);
assert!(result.is_ok());
}
}
7.3 升级考量
// 组合合约的升级策略
//
// 组合合约的升级比单体更复杂,因为需要保持合约间的接口兼容性。
// 接口兼容性规则
pub mod compatibility {
pub fn check_backward_compat(
_old_schema: &str,
_new_schema: &str,
) -> Result<(), Vec<String>> {
let mut breaking_changes = vec![];
// 规则 1:不能删除已有的 Query 变体
// 规则 2:Execute 消息可以新增变体,但不能删除
// 规则 3:不能更改已有字段的类型
// 规则 4:新增字段必须有默认值(Option<T>)
// 规则 5:响应结构不能减少字段
if breaking_changes.is_empty() { Ok(()) }
else { Err(breaking_changes) }
}
}
// 升级顺序建议
//
// 1. 先升级"被依赖"的合约
// - 例如:先升级 agent_registry_v1,再升级依赖它的 Agent
//
// 2. 使用 canonical key 而非硬编码地址
// - 升级后只需更新 Registry 中的映射
//
// 3. 保留旧合约运行一段时间
// - 给依赖方迁移时间
//
// 4. 测试每个版本的接口兼容性
// - 使用 schema 比较工具
// 升级流程:
// +-------------+ +--------------+ +--------------+
// | 1. 上传新代码 | => | 2. 部署新实例 | => | 3. 更新 Registry|
// | (StoreCode) | | (Instantiate)| | (Resolve key) |
// +-------------+ +--------------+ +-------+------+
// |
// +----------+ +-------------+ |
// | 5. 旧合约 | <= | 4. 验证新合约| <-----+
// | (退役) | | (集成测试) |
// +----------+ +-------------+
// 迁移辅助函数
pub fn migrate_contract_instance(
deps: DepsMut,
env: Env,
info: MessageInfo,
factory_addr: String,
instance_id: u32,
new_code_id: u64,
migrate_msg: Binary,
) -> StdResult<Response> {
// 通过工厂执行迁移
Ok(Response::new()
.add_message(WasmMsg::Execute {
contract_addr: factory_addr,
msg: to_binary(&FactoryExecuteMsg::MigrateInstance {
instance_id,
new_code_id,
migrate_msg,
})?,
funds: vec![],
}))
}
7.4 人类审批复杂组合
// 人类对复杂组合的审批机制
//
// AI Agent 在组合多个合约执行复杂操作时,可能需要人类审批。
// 以下是一个"人类在其中"的审批模式:
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct PendingComposition {
pub id: String,
pub creator: Addr,
pub description: String,
pub msgs: Vec<CosmosMsg>,
pub required_approvals: u32,
pub approvals: Vec<Addr>,
pub status: PendingStatus,
pub created_at: u64,
pub expires_at: u64,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]
pub enum PendingStatus {
Pending,
Approved,
Rejected,
Executed,
Expired,
}
pub const PENDING_COMPOSITIONS: Map<&str, PendingComposition> = Map::new("pending_comp");
pub const PENDING_COUNT: Item<u64> = Item::new("pending_count");
/// 提议一个多合约组合操作,等待审批
pub fn propose_composition(
deps: DepsMut,
env: Env,
info: MessageInfo,
description: String,
msgs: Vec<CosmosMsg>,
required_approvals: u32,
) -> StdResult<Response> {
let count = PENDING_COUNT.load(deps.storage).unwrap_or(0) + 1;
let id = format!("comp-{}", count);
let pending = PendingComposition {
id: id.clone(),
creator: info.sender,
description,
msgs,
required_approvals,
approvals: vec![],
status: PendingStatus::Pending,
created_at: env.block.height,
expires_at: env.block.height + 20160,
};
PENDING_COMPOSITIONS.save(deps.storage, &id, &pending)?;
PENDING_COUNT.save(deps.storage, &count)?;
Ok(Response::new()
.add_attribute("action", "propose_composition")
.add_attribute("id", &id)
.add_attribute("required_approvals", required_approvals.to_string()))
}
/// 审批一个组合操作
pub fn approve_composition(
deps: DepsMut,
env: Env,
info: MessageInfo,
id: String,
) -> StdResult<Response> {
let mut pending = PENDING_COMPOSITIONS.load(deps.storage, &id)?;
if pending.status != PendingStatus::Pending {
return Err(StdError::generic_err("Composition is not pending"));
}
if env.block.height > pending.expires_at {
pending.status = PendingStatus::Expired;
PENDING_COMPOSITIONS.save(deps.storage, &id, &pending)?;
return Err(StdError::generic_err("Composition has expired"));
}
if pending.approvals.contains(&info.sender) {
return Err(StdError::generic_err("Already approved"));
}
pending.approvals.push(info.sender);
if pending.approvals.len() as u32 >= pending.required_approvals {
pending.status = PendingStatus::Approved;
PENDING_COMPOSITIONS.save(deps.storage, &id, &pending)?;
// 收集足够的审批后,执行组合操作
return Ok(Response::new()
.add_attribute("action", "composition_approved")
.add_attribute("id", &id)
.add_messages(pending.msgs));
}
PENDING_COMPOSITIONS.save(deps.storage, &id, &pending)?;
Ok(Response::new()
.add_attribute("action", "approve_composition")
.add_attribute("id", &id)
.add_attribute("approvals", pending.approvals.len().to_string()))
}
7.5 常见陷阱与应对
// AI Agent 合约组合的常见陷阱
// 陷阱 1:忽略查询与执行的 gas 差异
// 查询:免费(不消耗 gas)
// 执行:消耗 gas(需要预留足够 gas)
// 陷阱 2:过深的调用栈
// 合约 A -> 合约 B -> 合约 C -> 合约 D
// CosmWasm 支持多层嵌套,但每层增加 gas 开销
// 建议:限制调用深度不超过 5 层
// 陷阱 3:循环依赖
// 合约 A 调用合约 B,合约 B 又调用合约 A
// CosmWasm 通过异步消息避免直接重入,但业务逻辑循环仍可能发生
// 建议:设计无环的 DAG 依赖结构
// 陷阱 4:忽略合约升级对组合的影响
// 如果合约 A 升级改变了查询返回值格式,合约 B 可能崩溃
// 建议:使用版本化的查询接口
// 陷阱 5:假设消息执行顺序
// Response 中的消息按添加顺序执行
// 但 SubMsg 的执行时机与普通消息不同
// 建议:仔细阅读 CosmWasm 执行模型文档
// 陷阱 6:不处理子调用的失败
// 默认情况下,子调用失败会回滚整个交易
// 但如果使用了 SubMsg + ReplyOn::Error,父合约需处理失败
// 建议:始终在 reply 中处理可能的失败
// 陷阱 7:状态膨胀
// 每个跨合约调用可能产生额外的状态存储
// 长期运行可能导致合约状态过大
// 建议:定期清理不需要的中间状态
/// 安全组合检查清单
pub struct CompositionChecklist;
impl CompositionChecklist {
pub fn verify(deps: &Deps, target_addr: &str) -> Vec<String> {
let mut warnings = vec![];
// 检查目标合约是否存在
if let Ok(code_id) = deps.querier.query_wasm_contract_info(target_addr) {
if code_id == 0 {
warnings.push("Target contract code_id is 0".to_string());
}
} else {
warnings.push("Cannot query target contract info".to_string());
}
warnings
}
}
附录
A. 合约地址与 Code ID
| Contract | Code ID | Canonical Key |
|---|---|---|
| agent_registry_v1 | 20 | agent_registry_v1 |
| agent_payment_v1 | 21 | agent_payment_v1 |
| agent_mpc_v1 | 22 | agent_mpc_v1 |
| ai_agent_constitution_v1 | 23 | ai_agent_constitution_v1 |
| dao_governance_v1 | 24 | dao_governance_v1 |
| agent_a2a_v1 | 19 | agent_a2a_v1 |
| aidid_did_registry_v1 | 29 | aidid_did_registry_v1 |
| CW20 Base | 1 | cw20_base_v1 |
B. Gas 对照参考
| 操作 | 预估 Gas |
|---|---|
| 一次 WasmMsg::Execute | 20,000 + 目标合约执行 |
| 一次 WasmQuery::Smart | 15,000 + 目标合约查询 |
| 一次 WasmQuery::Raw | 5,000 |
| 一次 SubMsg | 基础 20,000 + SubMsg 管理费 5,000 |
| 一次存储写入(32B) | 40,000 |
| 一次存储读取 | 10,000 |
| Registry resolve 查询 | ~25,000 |
