dApp Docs/AI Agent 合约间调用与组合模式指南
Development reference. Not independently verified for production.

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. 概述
  2. 跨合约调用基础
  3. 工厂模式与合约实例化
  4. Registry 发现与地址解析
  5. 组合合约模式
  6. 安全考量
  7. AI Agent 最佳实践

1. 概述

1.1 为什么合约组合对 AI Agent 至关重要

在 MSG Chain 上,AI Agent 不是孤立的合约。一个有用的 AI Agent 通常需要组合多个链上服务:

没有合约组合,每个 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(&register_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", &registry_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(&registry_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, &registry_addr, &query)?;

3. 工厂模式与合约实例化

3.1 工厂模式概述

工厂模式是合约组合中最基础也是最重要的模式之一。一个"工厂"合约负责创建(实例化)其他合约的实例,并跟踪所有已创建的实例。

在 MSG Chain 的 AI Agent 场景中,工厂模式用于:

用户 -- 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(&register_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 &notify_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(&notify_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(), &registry_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(&notify_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

C. 相关资源