dApp Docs/AI Agent NFT 铸造与数字权益指南
Development reference. Not independently verified for production.

MSG Chain AI Agent NFT 铸造与数字权益指南

链 ID: msg-chain-1 | 共识: DAR | 虚拟机: CosmWasm (WasmVM)
状态: 规划文档 — 主网裁决为 No-Go,所有数据均为主网预演
Gas: 1,000,000,000 attoMSG/gas
Gas 分配: 40% 验证者 / 30% 开发者 / 20% 燃烧 / 10% 基金会金库
地址格式: SHA3-512(前40位) + SHA-256 校验 | 签名: Dilithium-5 (公钥2592字节 / 私钥4864字节 / 签名4595字节)
AI Agent安全边界: 永不自主创建新合约,永不自主调整 Gas 参数,永不自主铸造/销毁代币

目录

  1. 概述
  2. Agent NFT 合约
  3. NFT 铸造服务
  4. NFT 市场集成
  5. NFT 门控访问
  6. NFT 与信誉系统集成
  7. NFT 收益与版税
  8. 完整示例:Agent NFT Collection 启动

1. 概述

1.1 为什么 AI Agent 需要 NFT

AI Agent 在 MSG Chain 上运行,天然需要去中心化身份和可验证的链上凭证。NFT(非同质化代币)为 Agent 提供了以下核心能力:

用途 说明 典型场景
成就证明 将 Agent 的里程碑铸造为 NFT 完成 10,000 次交易、达到 95% 准确率
信誉标识 可验证的信誉评分 NFT Reputation Badge Level 5
访问权限 NFT 门控高级功能 持有会员通行证才能调用 premium API
身份凭证 链上身份标识 Agent DID 绑定 NFT
贡献记录 记录 Agent 对生态的贡献 代码提交、治理投票、数据贡献
会员资格 时间限制的会员 NFT 30 天高级会员、年度白金会员

NFT 对比传统中心化凭证的优势:

1.2 CW721 vs CW1155

特性 CW721 (ERC-721) CW1155 (ERC-1155)
唯一性 每个 token 唯一 支持半同质化
适用场景 成就徽章、会员卡、身份凭证 游戏道具、批量物品
元数据 每个 token 独立 共享元数据 + 可选独立
批量操作 需逐个处理 原生批量铸造/转账
Agent 推荐 成就、信誉、门禁 大批量同类凭证

本指南以 CW721 为主,因为 Agent 场景中每个 NFT 代表独特成就或身份。

1.3 NFT 生命周期

铸造 (Mint) -> 持有 (Hold) -> 验证 (Verify) -> 转移 (Transfer/Burn)
  |              |               |
  v              v               v
Agent 铸造     用户钱包        门控检查
NFT 给用户     持有 NFT      验证持有者身份

1.4 MSG Chain 地址示例


2. Agent NFT 合约

2.1 合约架构总览

Agent NFT 合约基于 cw721-base,扩展了 Agent 特定的 NFT 类型和元数据标准。

AgentNFT Contract
+-- CW721 Base (继承)
|   +-- Mint / Burn / Transfer
|   +-- OwnerOf / Approved
|   +-- Tokens / AllTokens
+-- Agent 扩展
|   +-- NftType 枚举
|   +-- AchievementMetadata
|   +-- MembershipMetadata
|   +-- ReputationMetadata
|   +-- ContributionMetadata
|   +-- AccessPassMetadata
+-- 权限控制
|   +-- only_minter (Agent)
|   +-- only_owner
|   +-- only_approved
+-- 查询扩展
    +-- UserNFTs
    +-- NFTsByType
    +-- ReputationBadges

2.2 Cargo.toml

[package]
name = "agent-nft"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib", "rlib"]

[dependencies]
cosmwasm-std = { version = "1.5", features = ["staking"] }
cosmwasm-storage = "1.5"
cw-storage-plus = "1.2"
cw721 = "0.18"
cw721-base = { version = "0.18", features = ["library"] }
cw-utils = "0.18"
cw2 = "0.18"
serde = { version = "1.0", features = ["derive"] }
serde-json-wasm = "0.5"
thiserror = "1.0"
url = "2.5"
uint = { version = "0.9", default-features = false }

[features]
default = ["library"]
library = []

2.3 NFT 类型枚举

use cosmwasm_std::*;
use cw721_base::*;
use serde::{Deserialize, Serialize};

/// NFT 类型 -- 决定元数据结构和铸造逻辑
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub enum NftType {
    /// 成就徽章:完成特定任务后铸造
    Achievement,
    /// 会员资格:有时间限制或永久会员
    Membership,
    /// 信誉徽章:基于信誉评分铸造
    ReputationBadge,
    /// 贡献证明:记录对生态的贡献
    ProofOfContribution,
    /// 通行证:门控访问凭证
    AccessPass,
    /// 自定义类型,留给未来扩展
    Custom(String),
}

impl NftType {
    pub fn as_str(&self) -> &str {
        match self {
            NftType::Achievement => "achievement",
            NftType::Membership => "membership",
            NftType::ReputationBadge => "reputation_badge",
            NftType::ProofOfContribution => "proof_of_contribution",
            NftType::AccessPass => "access_pass",
            NftType::Custom(s) => s,
        }
    }

    pub fn from_str(s: &str) -> Self {
        match s {
            "achievement" => NftType::Achievement,
            "membership" => NftType::Membership,
            "reputation_badge" => NftType::ReputationBadge,
            "proof_of_contribution" => NftType::ProofOfContribution,
            "access_pass" => NftType::AccessPass,
            _ => NftType::Custom(s.to_string()),
        }
    }
}

2.4 各类型元数据结构

/// 成就元数据
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct AchievementMetadata {
    /// 成就名称(人类可读)
    pub title: String,
    /// 成就描述
    pub description: String,
    /// 成就类型分类
    pub achievement_type: String,
    /// 完成成就时的上下文数据(JSON)
    pub context: String,
    /// 成就等级(1-10)
    pub level: u8,
    /// 达成时间戳
    pub achieved_at: u64,
    /// 关联的 Agent DID
    pub agent_did: String,
    /// 是否可升级
    pub upgradeable: bool,
}

/// 会员元数据
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct MembershipMetadata {
    /// 会员等级
    pub tier: String,
    /// 生效时间
    pub valid_from: u64,
    /// 到期时间(0 表示永久)
    pub valid_until: u64,
    /// 会员权益列表
    pub benefits: Vec<String>,
    /// 是否自动续费
    pub auto_renew: bool,
    /// 发行方 Agent
    pub issuer_agent: String,
    /// 关联的支付 ID(如有)
    pub payment_ref: Option<String>,
}

/// 信誉徽章元数据
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct ReputationMetadata {
    /// 信誉总分
    pub total_score: u64,
    /// 各维度评分
    pub dimensions: Vec<ReputationDimension>,
    /// 当前等级
    pub level: u8,
    /// 徽章在生态系统中的权重
    pub weight: Decimal,
    /// 最后更新时间
    pub last_updated: u64,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct ReputationDimension {
    pub name: String,
    pub score: u64,
    pub max_score: u64,
}

/// 贡献证明元数据
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct ContributionMetadata {
    /// 贡献类型(code, data, compute, governance)
    pub contribution_type: String,
    /// 贡献 ID
    pub contribution_id: String,
    /// 贡献描述
    pub description: String,
    /// 贡献量
    pub amount: Uint128,
    /// 贡献时间范围
    pub period_start: u64,
    pub period_end: u64,
    /// 验证者 Agent 列表
    pub verifiers: Vec<String>,
}

/// 通行证元数据
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
pub struct AccessPassMetadata {
    /// 授予的权限列表
    pub permissions: Vec<String>,
    /// 最大使用次数(0 无限制)
    pub max_uses: u32,
    /// 当前使用次数
    pub used_count: u32,
    /// 生效时间
    pub effective_at: u64,
    /// 到期时间
    pub expires_at: u64,
    /// 是否可转让
    pub transferable: bool,
}

/// 统一扩展元数据
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]
#[serde(untagged)]
pub enum NftExtension {
    Achievement(AchievementMetadata),
    Membership(MembershipMetadata),
    Reputation(ReputationMetadata),
    Contribution(ContributionMetadata),
    AccessPass(AccessPassMetadata),
}

2.5 实例化消息

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
    /// NFT 集合名称
    pub name: String,
    /// NFT 集合符号
    pub symbol: String,
    /// 铸造者(Agent 地址)
    pub minter: String,
    /// Agent 去中心化身份
    pub agent_did: String,
    /// 集合中 NFT 的默认类型
    pub nft_type: NftType,
    /// 集合描述
    pub description: String,
    /// 集合图标 URI
    pub image: Option<String>,
    /// 外部链接
    pub external_link: Option<String>,
    /// 版税信息
    pub royalty_info: Option<RoyaltyInfo>,
}

/// 版税配置
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct RoyaltyInfo {
    /// 版税接收地址
    pub creator: String,
    /// 版税百分比(基点,如 500 = 5%)
    pub royalty_basis_points: u64,
}

/// 合约实例化
pub fn instantiate(
    deps: DepsMut,
    _env: Env,
    info: MessageInfo,
    msg: InstantiateMsg,
) -> StdResult<Response> {
    set_contract_version(deps.storage, "agent-nft", "0.1.0")?;

    let minter = deps.api.addr_validate(&msg.minter)?;
    let creator = if let Some(ref royalty) = msg.royalty_info {
        Some(deps.api.addr_validate(&royalty.creator)?)
    } else {
        None
    };

    CONFIG.save(deps.storage, &Config {
        name: msg.name,
        symbol: msg.symbol,
        minter: minter.clone(),
        agent_did: msg.agent_did,
        nft_type: msg.nft_type,
        description: msg.description,
        image: msg.image,
        external_link: msg.external_link,
        creator,
        royalty_basis_points: msg.royalty_info
            .map(|r| r.royalty_basis_points)
            .unwrap_or(0),
    })?;

    let cw721_msg = cw721_base::InstantiateMsg {
        name: msg.name,
        symbol: msg.symbol,
        minter: info.sender.to_string(),
    };

    let cw721_resp = cw721_base::instantiate(deps, _env, info, cw721_msg)?;

    Ok(cw721_resp
        .add_attribute("agent_did", minter.to_string())
        .add_attribute("nft_type", format!("{:?}", msg.nft_type)))
}

2.6 铸造成就 NFT

/// 铸造成就 NFT
pub fn mint_achievement(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    recipient: String,
    token_id: String,
    achievement_type: String,
    metadata: AchievementMetadata,
) -> StdResult<Response> {
    let config = CONFIG.load(deps.storage)?;
    if info.sender != config.minter {
        return Err(StdError::generic_err("Only the agent minter can mint NFTs"));
    }

    let recipient_addr = deps.api.addr_validate(&recipient)?;

    if TOKENS.may_load(deps.storage, &token_id)?.is_some() {
        return Err(StdError::generic_err(format!(
            "Token ID {} already exists", token_id
        )));
    }

    let extension = NftExtension::Achievement(metadata.clone());

    let token = cw721_base::state::TokenInfo {
        owner: recipient_addr,
        approvals: vec![],
        token_uri: None,
        extension,
    };

    TOKENS.save(deps.storage, &token_id, &token)?;
    NUM_TOKENS.update(deps.storage, |n| Ok(n + 1))?;

    ACHIEVEMENTS.save(deps.storage, &token_id, &AchievementRecord {
        token_id: token_id.clone(),
        owner: recipient_addr,
        achievement_type: achievement_type.clone(),
        level: metadata.level,
        achieved_at: metadata.achieved_at,
        agent_did: config.agent_did.clone(),
    })?;

    USER_ACHIEVEMENTS.update(deps.storage, recipient_addr.as_bytes(), |existing| {
        let mut records = existing.unwrap_or_default();
        records.push(token_id.clone());
        Ok::<_, StdError>(records)
    })?;

    Ok(Response::new()
        .add_attribute("action", "mint_achievement")
        .add_attribute("minter", info.sender)
        .add_attribute("recipient", recipient)
        .add_attribute("token_id", &token_id)
        .add_attribute("achievement_type", &achievement_type)
        .add_attribute("level", metadata.level.to_string())
        .add_attribute("agent_did", &config.agent_did))
}

2.7 铸造会员 NFT

/// 铸造会员资格 NFT(带时间限制)
pub fn mint_membership(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    recipient: String,
    token_id: String,
    metadata: MembershipMetadata,
) -> StdResult<Response> {
    let config = CONFIG.load(deps.storage)?;
    if info.sender != config.minter {
        return Err(StdError::generic_err("Only the agent minter can mint NFTs"));
    }

    let recipient_addr = deps.api.addr_validate(&recipient)?;

    if metadata.valid_until > 0 && metadata.valid_until <= env.block.time.seconds() {
        return Err(StdError::generic_err("Membership validity must be in the future"));
    }
    if metadata.valid_until > 0 && metadata.valid_from >= metadata.valid_until {
        return Err(StdError::generic_err("valid_from must be before valid_until"));
    }

    let extension = NftExtension::Membership(metadata.clone());

    let token = cw721_base::state::TokenInfo {
        owner: recipient_addr.clone(),
        approvals: vec![],
        token_uri: None,
        extension,
    };

    TOKENS.save(deps.storage, &token_id, &token)?;
    NUM_TOKENS.update(deps.storage, |n| Ok(n + 1))?;

    MEMBERSHIPS.save(deps.storage, &token_id, &MembershipRecord {
        token_id: token_id.clone(),
        owner: recipient_addr.clone(),
        tier: metadata.tier.clone(),
        valid_from: metadata.valid_from,
        valid_until: metadata.valid_until,
        beneficiary: recipient_addr,
        issuer_agent: config.agent_did.clone(),
    })?;

    ACTIVE_MEMBERSHIPS.update(deps.storage, recipient_addr.as_bytes(), |existing| {
        let mut records = existing.unwrap_or_default();
        records.push(MembershipSummary {
            token_id: token_id.clone(),
            tier: metadata.tier.clone(),
            valid_until: metadata.valid_until,
        });
        Ok(records)
    })?;

    Ok(Response::new()
        .add_attribute("action", "mint_membership")
        .add_attribute("minter", info.sender)
        .add_attribute("recipient", recipient)
        .add_attribute("token_id", &token_id)
        .add_attribute("tier", &metadata.tier)
        .add_attribute("valid_until", metadata.valid_until.to_string()))
}

2.8 信誉徽章铸造

/// 铸造/升级信誉徽章
pub fn mint_reputation_badge(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    recipient: String,
    token_id: String,
    metadata: ReputationMetadata,
) -> StdResult<Response> {
    let config = CONFIG.load(deps.storage)?;
    if info.sender != config.minter {
        return Err(StdError::generic_err("Only the agent minter can mint NFTs"));
    }

    let recipient_addr = deps.api.addr_validate(&recipient)?;

    let existing_badge = USER_REPUTATION_BADGES
        .may_load(deps.storage, recipient_addr.as_bytes())?;

    if let Some(existing) = existing_badge {
        if existing.total_score < metadata.total_score {
            TOKENS.remove(deps.storage, &existing.token_id);
            NUM_TOKENS.update(deps.storage, |n| Ok(n.saturating_sub(1)))?;

            let _burn_event = Event::new("burn")
                .add_attribute("token_id", &existing.token_id)
                .add_attribute("reason", "upgraded");
        } else {
            return Err(StdError::generic_err(
                "User already has a badge with equal or higher score",
            ));
        }
    }

    let extension = NftExtension::Reputation(metadata.clone());

    let token = cw721_base::state::TokenInfo {
        owner: recipient_addr.clone(),
        approvals: vec![],
        token_uri: None,
        extension,
    };

    TOKENS.save(deps.storage, &token_id, &token)?;
    NUM_TOKENS.update(deps.storage, |n| Ok(n + 1))?;

    USER_REPUTATION_BADGES.save(deps.storage, recipient_addr.as_bytes(), &ReputationBadgeRecord {
        token_id: token_id.clone(),
        owner: recipient_addr.clone(),
        total_score: metadata.total_score,
        level: metadata.level,
        last_updated: env.block.time.seconds(),
    })?;

    REPUTATION_RANKING.save(deps.storage, recipient_addr.as_bytes(), &metadata.total_score)?;

    Ok(Response::new()
        .add_attribute("action", "mint_reputation_badge")
        .add_attribute("recipient", recipient)
        .add_attribute("token_id", &token_id)
        .add_attribute("score", metadata.total_score.to_string())
        .add_attribute("level", metadata.level.to_string())
        .add_attribute("is_upgrade", existing_badge.is_some().to_string()))
}

2.9 贡献证明铸造

/// 铸造贡献证明 NFT
pub fn mint_contribution_nft(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    contributor: String,
    token_id: String,
    metadata: ContributionMetadata,
) -> StdResult<Response> {
    let config = CONFIG.load(deps.storage)?;
    if info.sender != config.minter {
        return Err(StdError::generic_err("Only the agent minter can mint NFTs"));
    }

    let contributor_addr = deps.api.addr_validate(&contributor)?;

    if metadata.verifiers.is_empty() {
        return Err(StdError::generic_err("At least one verifier is required"));
    }

    let extension = NftExtension::Contribution(metadata.clone());

    let token = cw721_base::state::TokenInfo {
        owner: contributor_addr.clone(),
        approvals: vec![],
        token_uri: None,
        extension,
    };

    TOKENS.save(deps.storage, &token_id, &token)?;
    NUM_TOKENS.update(deps.storage, |n| Ok(n + 1))?;

    CONTRIBUTIONS.save(deps.storage, &token_id, &ContributionRecord {
        token_id: token_id.clone(),
        contributor: contributor_addr.clone(),
        contribution_type: metadata.contribution_type.clone(),
        contribution_id: metadata.contribution_id.clone(),
        amount: metadata.amount,
        created_at: env.block.time.seconds(),
        verifiers: metadata.verifiers.clone(),
    })?;

    USER_CONTRIBUTIONS.update(deps.storage, contributor_addr.as_bytes(), |existing| {
        let mut records = existing.unwrap_or_default();
        records.push(token_id.clone());
        Ok::<_, StdError>(records)
    })?;

    Ok(Response::new()
        .add_attribute("action", "mint_contribution")
        .add_attribute("contributor", contributor)
        .add_attribute("token_id", &token_id)
        .add_attribute("contribution_type", &metadata.contribution_type)
        .add_attribute("amount", metadata.amount.to_string()))
}

2.10 访问通行证铸造

/// 铸造访问通行证
pub fn mint_access_pass(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    recipient: String,
    token_id: String,
    metadata: AccessPassMetadata,
) -> StdResult<Response> {
    let config = CONFIG.load(deps.storage)?;
    if info.sender != config.minter {
        return Err(StdError::generic_err("Only the agent minter can mint NFTs"));
    }

    let recipient_addr = deps.api.addr_validate(&recipient)?;

    if metadata.permissions.is_empty() {
        return Err(StdError::generic_err("At least one permission is required"));
    }

    let extension = NftExtension::AccessPass(metadata.clone());

    let token = cw721_base::state::TokenInfo {
        owner: recipient_addr.clone(),
        approvals: vec![],
        token_uri: None,
        extension,
    };

    TOKENS.save(deps.storage, &token_id, &token)?;
    NUM_TOKENS.update(deps.storage, |n| Ok(n + 1))?;

    ACCESS_PASSES.save(deps.storage, &token_id, &AccessPassRecord {
        token_id: token_id.clone(),
        owner: recipient_addr.clone(),
        permissions: metadata.permissions.clone(),
        expires_at: metadata.expires_at,
        transferable: metadata.transferable,
    })?;

    for permission in &metadata.permissions {
        PERMISSION_HOLDERS.update(deps.storage, permission.as_bytes(), |existing| {
            let mut holders = existing.unwrap_or_default();
            holders.push(recipient_addr.to_string());
            Ok::<_, StdError>(holders)
        })?;
    }

    Ok(Response::new()
        .add_attribute("action", "mint_access_pass")
        .add_attribute("recipient", recipient)
        .add_attribute("token_id", &token_id)
        .add_attribute("permissions", metadata.permissions.join(","))
        .add_attribute("expires_at", metadata.expires_at.to_string()))
}

2.11 燃烧与销毁

/// 燃烧 NFT(仅铸造者或持有者可用)
pub fn burn_nft(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    token_id: String,
) -> StdResult<Response> {
    let config = CONFIG.load(deps.storage)?;
    let token = TOKENS.load(deps.storage, &token_id)?;

    if info.sender != config.minter && info.sender != token.owner {
        return Err(StdError::generic_err(
            "Only the minter or token owner can burn",
        ));
    }

    TOKENS.remove(deps.storage, &token_id);
    NUM_TOKENS.update(deps.storage, |n| Ok(n.saturating_sub(1)))?;

    let owner_bytes = token.owner.as_bytes();
    USER_ACHIEVEMENTS.update(deps.storage, owner_bytes, |existing| {
        Ok::<_, StdError>(existing
            .unwrap_or_default()
            .into_iter()
            .filter(|id| id != &token_id)
            .collect())
    })?;

    ACTIVE_MEMBERSHIPS.update(deps.storage, owner_bytes, |existing| {
        Ok::<_, StdError>(existing
            .unwrap_or_default()
            .into_iter()
            .filter(|m| m.token_id != token_id)
            .collect())
    })?;

    USER_REPUTATION_BADGES.remove(deps.storage, owner_bytes);
    REPUTATION_RANKING.remove(deps.storage, owner_bytes);

    Ok(Response::new()
        .add_attribute("action", "burn")
        .add_attribute("token_id", &token_id)
        .add_attribute("burner", info.sender))
}

2.12 合约状态定义

use cw_storage_plus::{Item, Map};

/// 合约级配置
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Config {
    pub name: String,
    pub symbol: String,
    pub minter: Addr,
    pub agent_did: String,
    pub nft_type: NftType,
    pub description: String,
    pub image: Option<String>,
    pub external_link: Option<String>,
    pub creator: Option<Addr>,
    pub royalty_basis_points: u64,
}

pub const CONFIG: Item<Config> = Item::new("config");

pub const TOKENS: Map<&str, cw721_base::state::TokenInfo<NftExtension>> = Map::new("tokens");
pub const NUM_TOKENS: Item<u64> = Item::new("num_tokens");

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct AchievementRecord {
    pub token_id: String,
    pub owner: Addr,
    pub achievement_type: String,
    pub level: u8,
    pub achieved_at: u64,
    pub agent_did: String,
}

pub const ACHIEVEMENTS: Map<&str, AchievementRecord> = Map::new("achievements");
pub const USER_ACHIEVEMENTS: Map<&[u8], Vec<String>> = Map::new("user_achievements");

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct MembershipRecord {
    pub token_id: String,
    pub owner: Addr,
    pub tier: String,
    pub valid_from: u64,
    pub valid_until: u64,
    pub beneficiary: Addr,
    pub issuer_agent: String,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct MembershipSummary {
    pub token_id: String,
    pub tier: String,
    pub valid_until: u64,
}

pub const MEMBERSHIPS: Map<&str, MembershipRecord> = Map::new("memberships");
pub const ACTIVE_MEMBERSHIPS: Map<&[u8], Vec<MembershipSummary>> = Map::new("active_memberships");

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ReputationBadgeRecord {
    pub token_id: String,
    pub owner: Addr,
    pub total_score: u64,
    pub level: u8,
    pub last_updated: u64,
}

pub const USER_REPUTATION_BADGES: Map<&[u8], ReputationBadgeRecord> = Map::new("user_reputation");
pub const REPUTATION_RANKING: Map<&[u8], u64> = Map::new("reputation_ranking");

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ContributionRecord {
    pub token_id: String,
    pub contributor: Addr,
    pub contribution_type: String,
    pub contribution_id: String,
    pub amount: Uint128,
    pub created_at: u64,
    pub verifiers: Vec<String>,
}

pub const CONTRIBUTIONS: Map<&str, ContributionRecord> = Map::new("contributions");
pub const USER_CONTRIBUTIONS: Map<&[u8], Vec<String>> = Map::new("user_contributions");

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct AccessPassRecord {
    pub token_id: String,
    pub owner: Addr,
    pub permissions: Vec<String>,
    pub expires_at: u64,
    pub transferable: bool,
}

pub const ACCESS_PASSES: Map<&str, AccessPassRecord> = Map::new("access_passes");
pub const PERMISSION_HOLDERS: Map<&[u8], Vec<String>> = Map::new("permission_holders");

2.13 查询函数

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
    OwnerOf { token_id: String },
    AllTokens { owner: String, start_after: Option<String>, limit: Option<u32> },
    UserAchievements { user: String },
    UserMemberships { user: String },
    UserReputation { user: String },
    UserContributions { user: String, contribution_type: Option<String> },
    UserPermissions { user: String },
    Config {},
    NftType {},
    ReputationRanking { limit: Option<u32> },
}

pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
    match msg {
        QueryMsg::OwnerOf { token_id } => to_binary(&query_owner_of(deps, token_id)?),
        QueryMsg::AllTokens { owner, start_after, limit } => {
            to_binary(&query_all_tokens(deps, owner, start_after, limit)?)
        }
        QueryMsg::UserAchievements { user } => to_binary(&query_user_achievements(deps, user)?),
        QueryMsg::UserMemberships { user } => to_binary(&query_user_memberships(deps, user)?),
        QueryMsg::UserReputation { user } => to_binary(&query_user_reputation(deps, user)?),
        QueryMsg::UserContributions { user, contribution_type } => {
            to_binary(&query_user_contributions(deps, user, contribution_type)?)
        }
        QueryMsg::UserPermissions { user } => to_binary(&query_user_permissions(deps, user)?),
        QueryMsg::Config {} => to_binary(&CONFIG.load(deps.storage)?),
        QueryMsg::NftType {} => to_binary(&CONFIG.load(deps.storage)?.nft_type),
        QueryMsg::ReputationRanking { limit } => {
            to_binary(&query_reputation_ranking(deps, limit.unwrap_or(50))?)
        }
    }
}

fn query_owner_of(deps: Deps, token_id: String) -> StdResult<OwnerOfResponse> {
    let token = TOKENS.load(deps.storage, &token_id)?;
    Ok(OwnerOfResponse {
        owner: token.owner.to_string(),
        approvals: token.approvals.iter().map(|a| Approval {
            spender: a.spender.to_string(),
            expires: a.expires.clone(),
        }).collect(),
    })
}

fn query_all_tokens(deps: Deps, owner: String, start_after: Option<String>, limit: Option<u32>) -> StdResult<Vec<String>> {
    let owner_addr = deps.api.addr_validate(&owner)?;
    let limit = limit.unwrap_or(30).min(100) as usize;

    let tokens: Vec<String> = TOKENS
        .range(deps.storage, None, None, cosmwasm_std::Order::Ascending)
        .filter(|item| item.as_ref().map(|(_, t)| t.owner == owner_addr).unwrap_or(false))
        .map(|item| item.map(|(k, _)| k))
        .filter_ok(|k| {
            if let Some(ref start) = start_after {
                k.as_str() > start.as_str()
            } else {
                true
            }
        })
        .take(limit)
        .collect::<StdResult<Vec<_>>>()?;

    Ok(tokens)
}

fn query_user_achievements(deps: Deps, user: String) -> StdResult<Vec<AchievementRecord>> {
    let user_addr = deps.api.addr_validate(&user)?;
    let token_ids = USER_ACHIEVEMENTS.may_load(deps.storage, user_addr.as_bytes())?.unwrap_or_default();
    token_ids.into_iter().map(|id| ACHIEVEMENTS.load(deps.storage, &id)).collect()
}

fn query_user_memberships(deps: Deps, user: String) -> StdResult<Vec<MembershipSummary>> {
    let user_addr = deps.api.addr_validate(&user)?;
    Ok(ACTIVE_MEMBERSHIPS.may_load(deps.storage, user_addr.as_bytes())?.unwrap_or_default())
}

fn query_user_reputation(deps: Deps, user: String) -> StdResult<Option<ReputationBadgeRecord>> {
    let user_addr = deps.api.addr_validate(&user)?;
    USER_REPUTATION_BADGES.may_load(deps.storage, user_addr.as_bytes())
}

fn query_user_contributions(deps: Deps, user: String, contribution_type: Option<String>) -> StdResult<Vec<ContributionRecord>> {
    let user_addr = deps.api.addr_validate(&user)?;
    let token_ids = USER_CONTRIBUTIONS.may_load(deps.storage, user_addr.as_bytes())?.unwrap_or_default();
    let mut records: Vec<ContributionRecord> = token_ids.into_iter()
        .map(|id| CONTRIBUTIONS.load(deps.storage, &id)).collect::<StdResult<Vec<_>>>()?;
    if let Some(ctype) = contribution_type {
        records.retain(|r| r.contribution_type == ctype);
    }
    Ok(records)
}

fn query_user_permissions(deps: Deps, user: String) -> StdResult<Vec<String>> {
    let user_addr = deps.api.addr_validate(&user)?;
    let mut permissions = vec![];
    for record in ACCESS_PASSES
        .range(deps.storage, None, None, cosmwasm_std::Order::Ascending)
        .filter_map(|item| item.ok())
        .filter(|(_, r)| r.owner == user_addr)
    {
        permissions.extend(record.1.permissions);
    }
    permissions.sort();
    permissions.dedup();
    Ok(permissions)
}

fn query_reputation_ranking(deps: Deps, limit: u32) -> StdResult<Vec<(String, u64)>> {
    let mut rankings: Vec<(String, u64)> = REPUTATION_RANKING
        .range(deps.storage, None, None, cosmwasm_std::Order::Descending)
        .filter_map(|item| item.ok())
        .map(|(addr_bytes, score)| (String::from_utf8_lossy(&addr_bytes).to_string(), score))
        .take(limit as usize)
        .collect();
    rankings.sort_by(|a, b| b.1.cmp(&a.1));
    Ok(rankings)
}

2.14 入口函数

#[entry_point]
pub fn execute(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    msg: ExecuteMsg,
) -> StdResult<Response> {
    match msg {
        ExecuteMsg::MintAchievement { recipient, token_id, achievement_type, metadata } => {
            mint_achievement(deps, env, info, recipient, token_id, achievement_type, metadata)
        }
        ExecuteMsg::MintMembership { recipient, token_id, metadata } => {
            mint_membership(deps, env, info, recipient, token_id, metadata)
        }
        ExecuteMsg::MintReputationBadge { recipient, token_id, metadata } => {
            mint_reputation_badge(deps, env, info, recipient, token_id, metadata)
        }
        ExecuteMsg::MintContributionNFT { contributor, token_id, metadata } => {
            mint_contribution_nft(deps, env, info, contributor, token_id, metadata)
        }
        ExecuteMsg::MintAccessPass { recipient, token_id, metadata } => {
            mint_access_pass(deps, env, info, recipient, token_id, metadata)
        }
        ExecuteMsg::Burn { token_id } => burn_nft(deps, env, info, token_id),
        ExecuteMsg::TransferNft { recipient, token_id } => {
            transfer_nft(deps, env, info, recipient, token_id)
        }
        ExecuteMsg::UpdateRoyaltyInfo { creator, royalty_basis_points } => {
            update_royalty(deps, env, info, creator, royalty_basis_points)
        }
    }
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
    MintAchievement { recipient: String, token_id: String, achievement_type: String, metadata: AchievementMetadata },
    MintMembership { recipient: String, token_id: String, metadata: MembershipMetadata },
    MintReputationBadge { recipient: String, token_id: String, metadata: ReputationMetadata },
    MintContributionNFT { contributor: String, token_id: String, metadata: ContributionMetadata },
    MintAccessPass { recipient: String, token_id: String, metadata: AccessPassMetadata },
    Burn { token_id: String },
    TransferNft { recipient: String, token_id: String },
    UpdateRoyaltyInfo { creator: String, royalty_basis_points: u64 },
}

2.15 转移与授权

pub fn transfer_nft(
    deps: DepsMut,
    env: Env,
    info: MessageInfo,
    recipient: String,
    token_id: String,
) -> StdResult<Response> {
    let config = CONFIG.load(deps.storage)?;
    let mut token = TOKENS.load(deps.storage, &token_id)?;

    if let NftExtension::AccessPass(ref pass) = token.extension {
        if !pass.transferable {
            return Err(StdError::generic_err("This access pass is not transferable"));
        }
    }

    if info.sender != token.owner {
        return Err(StdError::generic_err("Only the owner can transfer"));
    }

    let recipient_addr = deps.api.addr_validate(&recipient)?;
    let old_owner = token.owner.clone();
    token.owner = recipient_addr.clone();
    TOKENS.save(deps.storage, &token_id, &token)?;

    USER_ACHIEVEMENTS.update(deps.storage, old_owner.as_bytes(), |existing| {
        Ok::<_, StdError>(existing.unwrap_or_default().into_iter().filter(|id| id != &token_id).collect())
    })?;

    USER_ACHIEVEMENTS.update(deps.storage, recipient_addr.as_bytes(), |existing| {
        let mut records = existing.unwrap_or_default();
        records.push(token_id.clone());
        Ok(records)
    })?;

    Ok(Response::new()
        .add_attribute("action", "transfer")
        .add_attribute("token_id", &token_id)
        .add_attribute("from", old_owner)
        .add_attribute("to", recipient))
}

3. NFT 铸造服务

3.1 Python SDK 基础

\"\"\"
Agent NFT 铸造服务
SDK for MSG Chain NFT operations
\"\"\"

import hashlib
import json
import logging
import time
import uuid
from dataclasses import dataclass, field, asdict
from datetime import datetime, timedelta
from enum import Enum
from typing import Any, Optional

import httpx
from cosmos_sdk.client.lcd import LCDClient
from cosmos_sdk.core import Coins
from cosmos_sdk.core.fee import Fee
from cosmos_sdk.core.wasm import MsgExecuteContract, MsgInstantiateContract
from cosmos_sdk.key.mnemonic import MnemonicKey

logger = logging.getLogger(__name__)

MSG_CHAIN_ID = "msg-chain-1"
MSG_LCD_URL = "https://lcd.msgchain.org"
MSG_GAS_PRICE = "1000000000000000000attoMSG"
MSG_DENOM = "umsg"


class NftType(str, Enum):
    ACHIEVEMENT = "achievement"
    MEMBERSHIP = "membership"
    REPUTATION_BADGE = "reputation_badge"
    PROOF_OF_CONTRIBUTION = "proof_of_contribution"
    ACCESS_PASS = "access_pass"


@dataclass
class AgentNFTConfig:
    \"\"\"Agent NFT 服务配置\"\"\"
    mnemonic: str
    nft_contract_addr: str = ""
    marketplace_addr: str = ""
    lcd_url: str = MSG_LCD_URL
    chain_id: str = MSG_CHAIN_ID
    gas_price: str = MSG_GAS_PRICE
    gas_adjustment: float = 1.4

    def get_client(self) -> LCDClient:
        return LCDClient(
            chain_id=self.chain_id,
            url=self.lcd_url,
            gas_prices=self.gas_price,
            gas_adjustment=self.gas_adjustment,
        )

    def get_wallet(self):
        key = MnemonicKey(mnemonic=self.mnemonic)
        client = self.get_client()
        return client.wallet(key)

3.2 元数据上传

class MetadataStorage:
    \"\"\"NFT 元数据存储(IPFS / Arweave)\"\"\"

    def __init__(self, storage_type: str = "ipfs", api_key: str = ""):
        self.storage_type = storage_type
        self.api_key = api_key
        self.client = httpx.AsyncClient(timeout=30)

    async def upload_metadata(self, metadata: dict) -> str:
        \"\"\"上传元数据并返回 URI\"\"\"
        if self.storage_type == "ipfs":
            return await self._upload_to_ipfs(metadata)
        elif self.storage_type == "arweave":
            return await self._upload_to_arweave(metadata)
        else:
            return await self._upload_to_central(metadata)

    async def _upload_to_ipfs(self, metadata: dict) -> str:
        \"\"\"上传到 IPFS(通过 Pinata 或 Infura)\"\"\"
        headers = {"Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json"}
        async with httpx.AsyncClient() as client:
            resp = await client.post(
                "https://api.pinata.cloud/pinning/pinJSONToIPFS",
                json=metadata, headers=headers,
            )
            resp.raise_for_status()
            cid = resp.json().get("IpfsHash")
            return f"ipfs://{cid}"

    async def _upload_to_arweave(self, metadata: dict) -> str:
        \"\"\"上传到 Arweave\"\"\"
        tx_data = json.dumps(metadata).encode()
        tx_hash = hashlib.sha256(tx_data).hexdigest()
        return f"ar://{tx_hash}"

    async def _upload_to_central(self, metadata: dict) -> str:
        \"\"\"上传到中心化存储(开发阶段使用)\"\"\"
        async with httpx.AsyncClient() as client:
            resp = await client.post("https://nft.msgchain.org/api/metadata", json=metadata)
            resp.raise_for_status()
            return resp.json()["uri"]

3.3 基础 Agent NFT 服务

class AgentNFTService:
    \"\"\"Agent NFT 铸造服务 -- 核心逻辑\"\"\"

    def __init__(self, config: AgentNFTConfig):
        self.config = config
        self.wallet = config.get_wallet()
        self.client = config.get_client()
        self.metadata_storage = MetadataStorage(storage_type="ipfs", api_key=config.mnemonic[:32])

    async def _execute_contract(self, contract_addr: str, msg: dict, funds: Optional[list] = None) -> dict:
        \"\"\"执行合约调用\"\"\"
        execute_msg = MsgExecuteContract(
            sender=self.wallet.key.acc_address,
            contract=contract_addr,
            msg=msg,
            funds=funds or [],
        )
        simulated = self.client.estimate_fee(msgs=[execute_msg], wallet=self.wallet)
        fee = Fee(amount=Coins.from_str(self.config.gas_price), gas=simulated[0].gas_limit)
        tx = self.wallet.create_and_sign_tx(msgs=[execute_msg], fee=fee)
        result = self.client.broadcast_tx(tx)
        logger.info(f"TX sent: {result.txhash}")
        return {"txhash": result.txhash, "raw_log": result.raw_log, "code": result.code}

    async def _query_contract(self, contract_addr: str, msg: dict) -> Any:
        \"\"\"查询合约\"\"\"
        return self.client.wasm.contract_query(contract_addr, msg)

    async def _generate_token_id(self, prefix: str, user_addr: str) -> str:
        \"\"\"生成唯一 token_id\"\"\"
        raw = f"{prefix}-{user_addr}-{int(time.time() * 1000)}-{uuid.uuid4().hex[:8]}"
        return hashlib.sha256(raw.encode()).hexdigest()[:32]

3.4 铸造信誉徽章

class AgentNFTService:

    async def mint_reputation_badge(
        self, user_addr: str, total_score: int, dimensions: Optional[list[dict]] = None,
    ) -> dict:
        \"\"\"
        铸造信誉徽章给用户

        评分等级映射:
            Level 0:  0-19   基础
            Level 1:  20-39  青铜
            Level 2:  40-59  白银
            Level 3:  60-79  黄金
            Level 4:  80-99  铂金
            Level 5:  100+   钻石
        \"\"\"
        level = min(total_score // 20, 5)
        level_names = ["基础", "青铜", "白银", "黄金", "铂金", "钻石"]

        metadata = {
            "name": f"信誉徽章 - {level_names[level]} Level {level}",
            "description": f"在 MSG Chain 生态中获得 {total_score}/100 信誉评分",
            "image": f"https://nft.msgchain.org/badges/reputation/v2/{level}.png",
            "external_url": f"https://msgchain.org/badge/{user_addr}",
            "attributes": [
                {"trait_type": "Score", "value": total_score, "display_type": "number"},
                {"trait_type": "Level", "value": level, "display_type": "number"},
                {"trait_type": "Level Name", "value": level_names[level]},
                {"trait_type": "Date", "value": int(time.time()), "display_type": "date"},
                {"trait_type": "Max Score", "value": 100, "display_type": "number"},
            ],
        }

        if dimensions:
            for dim in dimensions:
                metadata["attributes"].append({
                    "trait_type": f"Dimension: {dim['name']}",
                    "value": dim["score"], "max_value": dim.get("max_score", 100),
                })

        token_uri = await self.metadata_storage.upload_metadata(metadata)
        token_id = await self._generate_token_id("reputation", user_addr)

        rep_metadata = {
            "total_score": total_score, "dimensions": dimensions or [],
            "level": level, "weight": str(0.1 * (level + 1)),
            "last_updated": int(time.time()),
        }

        msg = {"mint_reputation_badge": {"recipient": user_addr, "token_id": token_id, "metadata": rep_metadata}}
        result = await self._execute_contract(self.config.nft_contract_addr, msg)

        return {
            "token_id": token_id, "token_uri": token_uri, "level": level,
            "level_name": level_names[level], "score": total_score, "txhash": result["txhash"],
        }

    async def get_user_reputation(self, user_addr: str) -> Optional[dict]:
        msg = {"user_reputation": {"user": user_addr}}
        return await self._query_contract(self.config.nft_contract_addr, msg)

    async def get_reputation_ranking(self, top_n: int = 50) -> list:
        msg = {"reputation_ranking": {"limit": top_n}}
        return await self._query_contract(self.config.nft_contract_addr, msg)

3.5 铸造会员通行证

class AgentNFTService:

    async def mint_membership_pass(
        self, user_addr: str, tier: str, duration_days: int,
        benefits: Optional[list[str]] = None, auto_renew: bool = False,
    ) -> dict:
        \"\"\"
        铸造时间限制会员 NFT

        等级:
            - "bronze":   基础会员, 30天
            - "silver":   银卡会员, 90天
            - "gold":     金卡会员, 180天
            - "platinum": 白金会员, 365天
            - "lifetime": 终身会员, 永久
        \"\"\"
        tier_benefits = {
            "bronze": ["基础 API 调用: 1000次/天", "社区支持", "基础数据分析"],
            "silver": ["API 调用: 10000次/天", "优先支持", "高级数据分析", "自定义 Webhook"],
            "gold": ["API 调用: 100000次/天", "专属支持经理", "实时数据分析", "自定义集成"],
            "platinum": ["无限制 API 调用", "专属支持团队", "白标解决方案", "SLA 保障"],
            "lifetime": ["所有白金权益", "终身免费升级", "创始成员身份", "治理投票权"],
        }

        valid_from = int(time.time())
        valid_until = 0 if duration_days == 0 else valid_from + (duration_days * 86400)
        chosen_benefits = benefits or tier_benefits.get(tier, [])

        metadata = {
            "name": f"{tier.capitalize()} 会员通行证",
            "description": f"MSG Chain AI Agent {tier.capitalize()} 会员资格\n时长: {'永久' if duration_days == 0 else f'{duration_days}天'}\n权益: {', '.join(chosen_benefits[:3])}...",
            "image": f"https://nft.msgchain.org/membership/{tier}.png",
            "attributes": [
                {"trait_type": "Tier", "value": tier},
                {"trait_type": "Duration (Days)", "value": duration_days},
                {"trait_type": "Valid From", "value": valid_from, "display_type": "date"},
                {"trait_type": "Valid Until", "value": valid_until, "display_type": "date"},
                {"trait_type": "Auto Renew", "value": auto_renew},
            ],
        }

        token_uri = await self.metadata_storage.upload_metadata(metadata)
        token_id = await self._generate_token_id(f"member-{tier}", user_addr)

        member_metadata = {
            "tier": tier, "valid_from": valid_from, "valid_until": valid_until,
            "benefits": chosen_benefits, "auto_renew": auto_renew,
            "issuer_agent": self.wallet.key.acc_address, "payment_ref": None,
        }

        msg = {"mint_membership": {"recipient": user_addr, "token_id": token_id, "metadata": member_metadata}}
        result = await self._execute_contract(self.config.nft_contract_addr, msg)

        return {
            "token_id": token_id, "token_uri": token_uri, "tier": tier,
            "valid_from": valid_from, "valid_until": valid_until,
            "is_permanent": duration_days == 0, "txhash": result["txhash"],
        }

    async def check_membership_valid(self, user_addr: str) -> dict:
        msg = {"user_memberships": {"user": user_addr}}
        memberships = await self._query_contract(self.config.nft_contract_addr, msg) or []
        now = int(time.time())
        valid = [m for m in memberships if m["valid_until"] == 0 or m["valid_until"] > now]
        expired = [m for m in memberships if m["valid_until"] != 0 and m["valid_until"] <= now]
        return {
            "has_valid_membership": len(valid) > 0, "active_memberships": valid,
            "expired_memberships": expired,
            "highest_tier": max([m["tier"] for m in valid]) if valid else None,
        }

3.6 铸造成就徽章

class AgentNFTService:

    async def mint_achievement_badge(
        self, user_addr: str, achievement_type: str, title: str,
        description: str, level: int = 1, context: Optional[dict] = None,
    ) -> dict:
        \"\"\"
        铸造成就徽章

        achievement_type 预设分类:
            - "first_transaction":    首次交易
            - "volume_milestone":     交易量里程碑
            - "accuracy_achievement": 准确率成就
            - "streak_milestone":     连续签到
            - "community_contributor": 社区贡献
            - "referral_star":        推荐达人
            - "early_adopter":        早期采用者
            - "governance_participant": 治理参与
        \"\"\"
        type_templates = {
            "first_transaction": {"name": "首次交易", "icon": "🚀"},
            "volume_milestone": {"name": "交易量里程碑", "icon": "📈"},
            "accuracy_achievement": {"name": "准确率成就", "icon": "🎯"},
            "streak_milestone": {"name": "连续成就", "icon": "🔥"},
            "community_contributor": {"name": "社区贡献者", "icon": "🤝"},
            "referral_star": {"name": "推荐之星", "icon": "⭐"},
            "early_adopter": {"name": "早期采用者", "icon": "🔮"},
            "governance_participant": {"name": "治理参与者", "icon": "🗳️"},
        }

        template = type_templates.get(achievement_type, {"name": achievement_type, "icon": "🏅"})
        full_title = f"{template['icon']} {title or template['name']}"

        metadata = {
            "name": full_title, "description": description,
            "image": f"https://nft.msgchain.org/achievements/{achievement_type}/level_{level}.png",
            "attributes": [
                {"trait_type": "Achievement Type", "value": achievement_type},
                {"trait_type": "Level", "value": level, "display_type": "number"},
                {"trait_type": "Date", "value": int(time.time()), "display_type": "date"},
                {"trait_type": "Icon", "value": template["icon"]},
            ],
        }

        if context:
            for k, v in context.items():
                metadata["attributes"].append({"trait_type": k, "value": v})

        token_uri = await self.metadata_storage.upload_metadata(metadata)
        token_id = await self._generate_token_id(f"ach-{achievement_type}", user_addr)

        ach_metadata = {
            "title": full_title, "description": description,
            "achievement_type": achievement_type, "context": json.dumps(context or {}),
            "level": level, "achieved_at": int(time.time()),
            "agent_did": self.wallet.key.acc_address, "upgradeable": level < 5,
        }

        msg = {"mint_achievement": {
            "recipient": user_addr, "token_id": token_id,
            "achievement_type": achievement_type, "metadata": ach_metadata,
        }}
        result = await self._execute_contract(self.config.nft_contract_addr, msg)

        return {
            "token_id": token_id, "token_uri": token_uri, "title": full_title,
            "achievement_type": achievement_type, "level": level, "txhash": result["txhash"],
        }

    async def get_user_achievements(self, user_addr: str) -> list[dict]:
        msg = {"user_achievements": {"user": user_addr}}
        return await self._query_contract(self.config.nft_contract_addr, msg) or []

    async def verify_achievement(self, user_addr: str, achievement_type: str) -> bool:
        achievements = await self.get_user_achievements(user_addr)
        return any(a["achievement_type"] == achievement_type for a in achievements)

3.7 铸造贡献证明

class AgentNFTService:

    async def mint_contribution_nft(
        self, contribution_id: str, contributor: str, contribution_type: str,
        description: str, amount: int, period_start: Optional[int] = None,
        period_end: Optional[int] = None, verifiers: Optional[list[str]] = None,
    ) -> dict:
        \"\"\"
        铸造贡献证明 NFT

        contribution_type:
            - "code":        代码贡献(PR、提交)
            - "data":        数据贡献(数据集、标注)
            - "compute":     算力贡献(节点运行)
            - "governance":  治理贡献(投票、提案)
            - "community":   社区贡献(回答、教程)
            - "security":    安全贡献(漏洞报告)
        \"\"\"
        type_labels = {
            "code": "代码贡献", "data": "数据贡献", "compute": "算力贡献",
            "governance": "治理贡献", "community": "社区贡献", "security": "安全贡献",
        }
        label = type_labels.get(contribution_type, contribution_type)

        metadata = {
            "name": f"{label} 证明",
            "description": description,
            "image": f"https://nft.msgchain.org/contributions/{contribution_type}.png",
            "attributes": [
                {"trait_type": "Contribution Type", "value": contribution_type},
                {"trait_type": "Contribution ID", "value": contribution_id},
                {"trait_type": "Amount", "value": amount, "display_type": "number"},
                {"trait_type": "Date", "value": int(time.time()), "display_type": "date"},
                {"trait_type": "Verifiers", "value": len(verifiers or [])},
            ],
        }

        token_uri = await self.metadata_storage.upload_metadata(metadata)
        token_id = await self._generate_token_id(f"contrib-{contribution_type}", contributor)

        contrib_metadata = {
            "contribution_type": contribution_type, "contribution_id": contribution_id,
            "description": description, "amount": str(amount),
            "period_start": period_start or int(time.time()),
            "period_end": period_end or int(time.time()),
            "verifiers": verifiers or [self.wallet.key.acc_address],
        }

        msg = {"mint_contribution_nft": {"contributor": contributor, "token_id": token_id, "metadata": contrib_metadata}}
        result = await self._execute_contract(self.config.nft_contract_addr, msg)

        return {
            "token_id": token_id, "token_uri": token_uri,
            "contribution_type": contribution_type, "contribution_id": contribution_id,
            "amount": amount, "txhash": result["txhash"],
        }

    async def get_user_contributions(self, user_addr: str, contribution_type: Optional[str] = None) -> list[dict]:
        msg = {"user_contributions": {"user": user_addr, "contribution_type": contribution_type}}
        return await self._query_contract(self.config.nft_contract_addr, msg) or []

3.8 铸造访问通行证

class AgentNFTService:

    async def mint_access_pass(
        self, user_addr: str, permissions: list[str], duration_days: int,
        max_uses: int = 0, transferable: bool = False,
    ) -> dict:
        \"\"\"
        铸造访问通行证 NFT

        permissions 可选项:
            - "premium_query":    高级查询
            - "unlimited_api":    无限制 API
            - "data_export":      数据导出
            - "batch_process":    批量处理
            - "model_inference":  模型推理
            - "admin_panel":      管理面板
            - "audit_log":        审计日志
            - "webhook_config":   Webhook 配置
        \"\"\"
        valid_from = int(time.time())
        valid_until = 0 if duration_days == 0 else valid_from + (duration_days * 86400)

        metadata = {
            "name": f"访问通行证 ({', '.join(permissions[:3])}{'...' if len(permissions) > 3 else ''})",
            "description": f"权限: {', '.join(permissions)}\n有效期: {'永久' if duration_days == 0 else f'{duration_days}天'}\n最大使用次数: {'无限制' if max_uses == 0 else max_uses}",
            "image": "https://nft.msgchain.org/access/pass.png",
            "attributes": [
                {"trait_type": "Permissions", "value": ", ".join(permissions)},
                {"trait_type": "Duration (Days)", "value": duration_days},
                {"trait_type": "Valid From", "value": valid_from, "display_type": "date"},
                {"trait_type": "Valid Until", "value": valid_until, "display_type": "date"},
                {"trait_type": "Max Uses", "value": max_uses, "display_type": "number"},
                {"trait_type": "Transferable", "value": transferable},
            ],
        }

        token_uri = await self.metadata_storage.upload_metadata(metadata)
        token_id = await self._generate_token_id("access", user_addr)

        access_metadata = {
            "permissions": permissions, "max_uses": max_uses, "used_count": 0,
            "effective_at": valid_from, "expires_at": valid_until, "transferable": transferable,
        }

        msg = {"mint_access_pass": {"recipient": user_addr, "token_id": token_id, "metadata": access_metadata}}
        result = await self._execute_contract(self.config.nft_contract_addr, msg)

        return {
            "token_id": token_id, "token_uri": token_uri, "permissions": permissions,
            "valid_from": valid_from, "valid_until": valid_until,
            "max_uses": max_uses, "transferable": transferable, "txhash": result["txhash"],
        }

    async def get_user_permissions(self, user_addr: str) -> list[str]:
        msg = {"user_permissions": {"user": user_addr}}
        return await self._query_contract(self.config.nft_contract_addr, msg) or []

3.9 批量铸造

class AgentNFTService:

    async def batch_mint_achievements(
        self, recipients: list[tuple[str, str, str, int]], achievement_type: str,
    ) -> list[dict]:
        \"\"\"批量铸造成就\"\"\"
        results = []
        for user_addr, title, description, level in recipients:
            try:
                result = await self.mint_achievement_badge(
                    user_addr=user_addr, achievement_type=achievement_type,
                    title=title, description=description, level=level,
                )
                results.append(result)
                logger.info(f"Minted achievement for {user_addr}: {result['token_id']}")
            except Exception as e:
                logger.error(f"Failed to mint for {user_addr}: {e}")
                results.append({"error": str(e), "user": user_addr})
        return results

    async def batch_mint_contributions(self, contributions: list[dict]) -> list[dict]:
        \"\"\"批量铸造贡献证明\"\"\"
        results = []
        for contrib in contributions:
            try:
                result = await self.mint_contribution_nft(
                    contribution_id=contrib["contribution_id"],
                    contributor=contrib["contributor"],
                    contribution_type=contrib["contribution_type"],
                    description=contrib["description"],
                    amount=contrib["amount"],
                    verifiers=contrib.get("verifiers"),
                )
                results.append(result)
            except Exception as e:
                logger.error(f"Failed to mint contribution: {e}")
                results.append({"error": str(e), **contrib})
        return results

4. NFT 市场集成

4.1 市场合约接口

\"\"\"Agent NFT 市场集成服务\"\"\"

from dataclasses import dataclass
from enum import Enum
from typing import Optional


class ListingStatus(str, Enum):
    ACTIVE = "active"
    SOLD = "sold"
    CANCELLED = "cancelled"
    AUCTION = "auction"


@dataclass
class Listing:
    token_id: str
    contract_addr: str
    seller: str
    price: int
    denom: str = MSG_DENOM
    status: ListingStatus = ListingStatus.ACTIVE
    created_at: int = 0

    def price_display(self) -> str:
        return f"{self.price / 1_000_000:,.4f} MSG"


@dataclass
class Auction:
    token_id: str
    contract_addr: str
    seller: str
    reserve_price: int
    highest_bid: int = 0
    highest_bidder: str = ""
    start_time: int = 0
    end_time: int = 0
    status: str = "active"

4.2 市场服务

class NFTMarketplace:
    \"\"\"NFT 市场集成服务\"\"\"

    def __init__(self, config: AgentNFTConfig, marketplace_addr: str):
        self.config = config
        self.wallet = config.get_wallet()
        self.client = config.get_client()
        self.marketplace_addr = marketplace_addr
        self.nft_addr = config.nft_contract_addr

    async def _execute(self, msg: dict, funds: Optional[list] = None) -> dict:
        execute_msg = MsgExecuteContract(
            sender=self.wallet.key.acc_address,
            contract=self.marketplace_addr,
            msg=msg, funds=funds or [],
        )
        simulated = self.client.estimate_fee(msgs=[execute_msg], wallet=self.wallet)
        fee = Fee(amount=Coins.from_str(self.config.gas_price), gas=simulated[0].gas_limit)
        tx = self.wallet.create_and_sign_tx(msgs=[execute_msg], fee=fee)
        result = self.client.broadcast_tx(tx)
        return {"txhash": result.txhash, "raw_log": result.raw_log}

    async def approve_marketplace(self, token_id: str) -> dict:
        msg = {"approve": {"spender": self.marketplace_addr, "token_id": token_id, "expires": {"never": {}}}}
        return await self._execute_cw721(msg)

    async def _execute_cw721(self, msg: dict) -> dict:
        execute_msg = MsgExecuteContract(
            sender=self.wallet.key.acc_address, contract=self.nft_addr, msg=msg,
        )
        simulated = self.client.estimate_fee(msgs=[execute_msg], wallet=self.wallet)
        fee = Fee(amount=Coins.from_str(self.config.gas_price), gas=simulated[0].gas_limit)
        tx = self.wallet.create_and_sign_tx(msgs=[execute_msg], fee=fee)
        result = self.client.broadcast_tx(tx)
        return {"txhash": result.txhash}

4.3 挂单与取消

class NFTMarketplace:

    async def list_nft(self, token_id: str, price: int, denom: str = MSG_DENOM) -> dict:
        \"\"\"
        挂单出售 NFT

        price 以 umsg 为单位(1 MSG = 1,000,000 umsg)
        例如: 50 MSG = 50_000_000 umsg
        \"\"\"
        await self.approve_marketplace(token_id)
        msg = {"list_nft": {"token_id": token_id, "contract_addr": self.nft_addr, "price": str(price), "denom": denom}}
        result = await self._execute(msg)
        return {
            "action": "list", "token_id": token_id, "price": price,
            "price_display": f"{price / 1_000_000:,.4f} MSG",
            "denom": denom, "seller": self.wallet.key.acc_address,
            "marketplace": self.marketplace_addr, "txhash": result["txhash"],
        }

    async def cancel_listing(self, token_id: str) -> dict:
        msg = {"cancel_listing": {"token_id": token_id, "contract_addr": self.nft_addr}}
        result = await self._execute(msg)
        return {"action": "cancel_listing", "token_id": token_id, "txhash": result["txhash"]}

    async def update_price(self, token_id: str, new_price: int) -> dict:
        msg = {"update_price": {"token_id": token_id, "contract_addr": self.nft_addr, "new_price": str(new_price)}}
        result = await self._execute(msg)
        return {
            "action": "update_price", "token_id": token_id, "new_price": new_price,
            "new_price_display": f"{new_price / 1_000_000:,.4f} MSG", "txhash": result["txhash"],
        }

4.4 购买 NFT

class NFTMarketplace:

    async def buy_nft(self, token_id: str, price: int, denom: str = MSG_DENOM) -> dict:
        funds = [{"denom": denom, "amount": str(price)}]
        msg = {"buy_nft": {"token_id": token_id, "contract_addr": self.nft_addr}}
        result = await self._execute(msg, funds=funds)
        return {
            "action": "buy", "token_id": token_id, "price": price,
            "price_display": f"{price / 1_000_000:,.4f} MSG",
            "buyer": self.wallet.key.acc_address, "txhash": result["txhash"],
        }

    async def buy_nft_for(self, token_id: str, price: int, recipient: str, denom: str = MSG_DENOM) -> dict:
        funds = [{"denom": denom, "amount": str(price)}]
        msg = {"buy_nft_for": {"token_id": token_id, "contract_addr": self.nft_addr, "recipient": recipient}}
        result = await self._execute(msg, funds=funds)
        return {"action": "buy_for", "token_id": token_id, "recipient": recipient, "price": price, "txhash": result["txhash"]}

    async def batch_buy(self, purchases: list[tuple[str, str, int]]) -> list[dict]:
        results = []
        for token_id, contract_addr, price in purchases:
            try:
                result = await self.buy_nft(token_id, price)
                results.append(result)
            except Exception as e:
                logger.error(f"Failed to buy {token_id}: {e}")
                results.append({"token_id": token_id, "error": str(e)})
        return results

4.5 竞拍

class NFTMarketplace:

    async def auction_nft(
        self, token_id: str, reserve_price: int, duration_seconds: int,
        denom: str = MSG_DENOM, start_price: Optional[int] = None,
    ) -> dict:
        await self.approve_marketplace(token_id)
        msg = {
            "start_auction": {
                "token_id": token_id, "contract_addr": self.nft_addr,
                "reserve_price": str(reserve_price),
                "start_price": str(start_price or reserve_price),
                "duration": duration_seconds, "denom": denom,
            }
        }
        result = await self._execute(msg)
        return {
            "action": "start_auction", "token_id": token_id,
            "reserve_price": reserve_price, "start_price": start_price or reserve_price,
            "duration": duration_seconds, "end_time": int(time.time()) + duration_seconds,
            "txhash": result["txhash"],
        }

    async def place_bid(self, token_id: str, bid_amount: int, denom: str = MSG_DENOM) -> dict:
        funds = [{"denom": denom, "amount": str(bid_amount)}]
        msg = {"place_bid": {"token_id": token_id, "contract_addr": self.nft_addr}}
        result = await self._execute(msg, funds=funds)
        return {
            "action": "place_bid", "token_id": token_id,
            "bid_amount": bid_amount, "bidder": self.wallet.key.acc_address,
            "txhash": result["txhash"],
        }

    async def cancel_auction(self, token_id: str) -> dict:
        msg = {"cancel_auction": {"token_id": token_id, "contract_addr": self.nft_addr}}
        result = await self._execute(msg)
        return {"action": "cancel_auction", "token_id": token_id, "txhash": result["txhash"]}

    async def claim_auction(self, token_id: str) -> dict:
        msg = {"claim_auction": {"token_id": token_id, "contract_addr": self.nft_addr, "claimant": self.wallet.key.acc_address}}
        result = await self._execute(msg)
        return {"action": "claim_auction", "token_id": token_id, "claimant": self.wallet.key.acc_address, "txhash": result["txhash"]}

4.6 查询市场数据

class NFTMarketplace:

    async def get_listings(self, contract_addr: Optional[str] = None, seller: Optional[str] = None, status: Optional[ListingStatus] = None) -> list[Listing]:
        msg: dict = {"listings": {}}
        if contract_addr: msg["listings"]["contract_addr"] = contract_addr
        if seller: msg["listings"]["seller"] = seller
        if status: msg["listings"]["status"] = status.value
        raw = self.client.wasm.contract_query(self.marketplace_addr, msg)
        return [Listing(**l) for l in raw]

    async def get_auctions(self, status: Optional[str] = None) -> list[Auction]:
        msg = {"auctions": {}}
        if status: msg["auctions"]["status"] = status
        raw = self.client.wasm.contract_query(self.marketplace_addr, msg)
        return [Auction(**a) for a in raw]

    async def get_nft_price_history(self, token_id: str) -> list[dict]:
        msg = {"price_history": {"token_id": token_id}}
        return self.client.wasm.contract_query(self.marketplace_addr, msg) or []

    async def get_market_stats(self) -> dict:
        stats = self.client.wasm.contract_query(self.marketplace_addr, {"market_stats": {}}) or {}
        if stats:
            stats["total_volume_display"] = f"{int(stats.get('total_volume', 0)) / 1_000_000:,.2f} MSG"
        return stats

4.7 市场事件监控

class NFTMarketplaceMonitor:

    def __init__(self, lcd_url: str, marketplace_addr: str, poll_interval: int = 10):
        self.client = LCDClient(chain_id=MSG_CHAIN_ID, url=lcd_url)
        self.marketplace_addr = marketplace_addr
        self.poll_interval = poll_interval
        self.last_height = 0

    async def poll_events(self, callback):
        while True:
            try:
                height = self.client.block_height()
                if self.last_height == 0:
                    self.last_height = height
                    continue
                events = self.client.search_txs(
                    f"wasm._contract_address='{self.marketplace_addr}'",
                    min_height=self.last_height, max_height=height,
                )
                for tx in events:
                    for event in tx.logs[0].events:
                        if event.type == "wasm":
                            parsed = self._parse_event(event)
                            if parsed:
                                await callback(parsed)
                self.last_height = height
            except Exception as e:
                logger.error(f"Poll error: {e}")
            await asyncio.sleep(self.poll_interval)

    def _parse_event(self, event) -> Optional[dict]:
        attrs = {a.key: a.value for a in event.attributes}
        action = attrs.get("action")
        if action in ("list_nft", "buy_nft", "cancel_listing", "start_auction", "place_bid", "claim_auction"):
            return {
                "action": action, "token_id": attrs.get("token_id"), "seller": attrs.get("seller"),
                "buyer": attrs.get("buyer"), "price": attrs.get("price"), "bidder": attrs.get("bidder"),
                "block_height": attrs.get("_tx.height"),
            }
        return None

5. NFT 门控访问

5.1 门控服务核心

\"\"\"NFT 门控访问服务\"\"\"

from enum import Enum
from typing import Optional


class FeatureAccess(str, Enum):
    PUBLIC = "public"
    NFT_GATED = "nft_gated"
    TIERED = "tiered"
    TIME_LOCKED = "time_locked"


@dataclass
class GatingRule:
    feature: str
    access_type: FeatureAccess
    collection: str
    min_quantity: int = 1
    required_tier: Optional[str] = None
    required_permission: Optional[str] = None
    max_age_days: Optional[int] = None


class NFTGatedAccess:

    def __init__(self, config: AgentNFTConfig):
        self.config = config
        self.client = config.get_client()
        self.nft_addr = config.nft_contract_addr

        self.GATING_RULES: dict[str, GatingRule] = {
            "premium_query": GatingRule(
                feature="premium_query", access_type=FeatureAccess.NFT_GATED,
                collection=config.nft_contract_addr, min_quantity=1, required_permission="premium_query",
            ),
            "unlimited_api": GatingRule(
                feature="unlimited_api", access_type=FeatureAccess.NFT_GATED,
                collection=config.nft_contract_addr, min_quantity=1, required_permission="unlimited_api",
            ),
            "data_export": GatingRule(
                feature="data_export", access_type=FeatureAccess.TIERED,
                collection=config.nft_contract_addr, min_quantity=1,
                required_tier="silver", required_permission="data_export",
            ),
            "batch_process": GatingRule(
                feature="batch_process", access_type=FeatureAccess.NFT_GATED,
                collection=config.nft_contract_addr, min_quantity=1, required_permission="batch_process",
            ),
            "model_inference": GatingRule(
                feature="model_inference", access_type=FeatureAccess.TIERED,
                collection=config.nft_contract_addr, min_quantity=1,
                required_tier="gold", required_permission="model_inference",
            ),
            "admin_panel": GatingRule(
                feature="admin_panel", access_type=FeatureAccess.TIERED,
                collection=config.nft_contract_addr, min_quantity=1,
                required_tier="platinum", required_permission="admin_panel",
            ),
            "webhook_config": GatingRule(
                feature="webhook_config", access_type=FeatureAccess.TIERED,
                collection=config.nft_contract_addr, min_quantity=1,
                required_tier="silver", required_permission="webhook_config",
            ),
        }

5.2 访问检查

class NFTGatedAccess:

    async def check_access(self, user: str, feature: str) -> bool:
        rule = self.GATING_RULES.get(feature)
        if not rule:
            return True

        if rule.access_type == FeatureAccess.PUBLIC:
            return True

        user_tokens = await self._query_user_tokens(user)
        if not user_tokens:
            return False

        if rule.access_type == FeatureAccess.NFT_GATED:
            return await self._check_nft_gated(user, user_tokens, rule)
        elif rule.access_type == FeatureAccess.TIERED:
            return await self._check_tiered(user, user_tokens, rule)
        elif rule.access_type == FeatureAccess.TIME_LOCKED:
            return await self._check_time_locked(user, user_tokens, rule)
        return False

    async def _query_user_tokens(self, user: str) -> list[str]:
        try:
            return self.client.wasm.contract_query(self.nft_addr, {"all_tokens": {"owner": user, "limit": 100}}) or []
        except Exception:
            return []

    async def _check_nft_gated(self, user: str, tokens: list[str], rule: GatingRule) -> bool:
        if len(tokens) < rule.min_quantity:
            return False
        if rule.required_permission:
            count = 0
            for token_id in tokens:
                if await self._check_token_permission(token_id, rule.required_permission):
                    count += 1
                    if count >= rule.min_quantity:
                        return True
            return count >= rule.min_quantity
        return len(tokens) >= rule.min_quantity

    async def _check_tiered(self, user: str, tokens: list[str], rule: GatingRule) -> bool:
        tier_order = {"bronze": 0, "silver": 1, "gold": 2, "platinum": 3, "lifetime": 4}
        required_level = tier_order.get(rule.required_tier, 0)
        memberships = self.client.wasm.contract_query(self.nft_addr, {"user_memberships": {"user": user}}) or []
        now = int(time.time())
        for m in memberships:
            if m["valid_until"] != 0 and m["valid_until"] < now:
                continue
            user_level = tier_order.get(m["tier"], -1)
            if user_level >= required_level:
                if rule.required_permission:
                    return await self._check_token_permission(m["token_id"], rule.required_permission)
                return True
        return False

    async def _check_time_locked(self, user: str, tokens: list[str], rule: GatingRule) -> bool:
        now = int(time.time())
        memberships = self.client.wasm.contract_query(self.nft_addr, {"user_memberships": {"user": user}}) or []
        for m in memberships:
            if m["valid_until"] == 0 or m["valid_until"] > now:
                return True
        return False

    async def _check_token_permission(self, token_id: str, required_permission: str) -> bool:
        try:
            token_info = self.client.wasm.contract_query(self.nft_addr, {"owner_of": {"token_id": token_id}})
            if not token_info:
                return False
            owner = token_info.get("owner", "")
            perms = self.client.wasm.contract_query(self.nft_addr, {"user_permissions": {"user": owner}}) or []
            return required_permission in perms
        except Exception:
            return False

5.3 高速缓存访问控制

class CachedNFTGatedAccess(NFTGatedAccess):

    def __init__(self, config: AgentNFTConfig, redis_url: str = "redis://localhost:6379"):
        super().__init__(config)
        import aioredis
        self.redis = aioredis.from_url(redis_url)
        self.cache_ttl = 300

    async def check_access(self, user: str, feature: str) -> bool:
        cache_key = f"access:{user}:{feature}"
        cached = await self.redis.get(cache_key)
        if cached is not None:
            return cached == b"true"
        result = await super().check_access(user, feature)
        await self.redis.setex(cache_key, self.cache_ttl, str(result).lower())
        return result

    async def invalidate_user_cache(self, user: str):
        pattern = f"access:{user}:*"
        keys = await self.redis.keys(pattern)
        if keys:
            await self.redis.delete(*keys)

    async def batch_check_access(self, user: str, features: list[str]) -> dict[str, bool]:
        return {f: await self.check_access(user, f) for f in features}

5.4 Agent 端门控中间件

class GatingMiddleware:

    def __init__(self, gated_access: NFTGatedAccess):
        self.gated_access = gated_access

    def require_nft(self, feature: str):
        def decorator(func):
            @wraps(func)
            async def wrapper(*args, **kwargs):
                user = kwargs.get("user") or args[0].get("user")
                if not user:
                    return {"error": "User address required"}, 401
                has_access = await self.gated_access.check_access(user, feature)
                if not has_access:
                    return {
                        "error": "Access denied", "feature": feature,
                        "required_nft": f"Hold a valid {feature} pass",
                        "upgrade_url": f"https://msgchain.org/mint/{feature}",
                    }, 403
                return await func(*args, **kwargs)
            return wrapper
        return decorator

    async def get_access_status(self, user: str) -> dict:
        features = list(self.gated_access.GATING_RULES.keys())
        access_map = await self.gated_access.batch_check_access(user, features)
        return {
            "user": user, "access": access_map,
            "granted": [f for f, a in access_map.items() if a],
            "denied": [f for f, a in access_map.items() if not a],
        }

5.5 时间限制处理

class NFTGatedAccess:

    async def check_access_with_usage(self, user: str, feature: str) -> dict:
        rule = self.GATING_RULES.get(feature)
        if not rule:
            return {"allowed": True, "reason": "public_feature"}

        passes = self.client.wasm.contract_query(self.nft_addr, {"user_permissions": {"user": user}}) or []
        if not passes:
            return {"allowed": False, "reason": "no_access_pass"}

        now = int(time.time())
        best_pass = None

        for access_pass_id in passes:
            access_info = self.client.wasm.contract_query(self.nft_addr, {"access_pass": {"token_id": access_pass_id}})
            if not access_info:
                continue
            if access_info["expires_at"] != 0 and access_info["expires_at"] < now:
                continue
            if access_info["max_uses"] > 0 and access_info["used_count"] >= access_info["max_uses"]:
                continue
            if rule.required_permission and rule.required_permission not in access_info.get("permissions", []):
                continue
            best_pass = access_info
            break

        if not best_pass:
            return {"allowed": False, "reason": "no_valid_pass"}

        remaining_uses = best_pass["max_uses"] - best_pass["used_count"] if best_pass["max_uses"] > 0 else -1
        remaining_days = (best_pass["expires_at"] - now) // 86400 if best_pass["expires_at"] > 0 else -1

        return {
            "allowed": True, "token_id": best_pass["token_id"],
            "remaining_uses": remaining_uses, "remaining_days": remaining_days,
            "permissions": best_pass.get("permissions", []), "expires_at": best_pass.get("expires_at", 0),
        }

6. NFT 与信誉系统集成

6.1 信誉积分引擎

\"\"\"NFT 信誉系统集成\"\"\"

from dataclasses import dataclass, field


@dataclass
class ReputationConfig:
    categories: dict = field(default_factory=lambda: {
        "trading": {"weight": 0.3, "max_score": 100},
        "contribution": {"weight": 0.25, "max_score": 100},
        "governance": {"weight": 0.2, "max_score": 100},
        "community": {"weight": 0.15, "max_score": 100},
        "longevity": {"weight": 0.1, "max_score": 100},
    })
    badge_levels: list = field(default_factory=lambda: [
        {"level": 0, "min_score": 0, "name": "基础成员"},
        {"level": 1, "min_score": 20, "name": "青铜成员"},
        {"level": 2, "min_score": 40, "name": "白银成员"},
        {"level": 3, "min_score": 60, "name": "黄金成员"},
        {"level": 4, "min_score": 80, "name": "铂金成员"},
        {"level": 5, "min_score": 100, "name": "钻石成员"},
    ])
    nft_contract: str = ""
    agent_addr: str = ""


class ReputationNFT:

    def __init__(self, config: AgentNFTConfig, rep_config: Optional[ReputationConfig] = None):
        self.config = config
        self.wallet = config.get_wallet()
        self.client = config.get_client()
        self.nft_service = AgentNFTService(config)
        self.rep_config = rep_config or ReputationConfig()

    def _calculate_total_score(self, category_scores: dict[str, int]) -> int:
        total = 0
        for category, score in category_scores.items():
            cat_config = self.rep_config.categories.get(category, {"weight": 0, "max_score": 100})
            total += score * cat_config["weight"]
        return min(int(total), 100)

    def _get_level(self, total_score: int) -> dict:
        for badge in reversed(self.rep_config.badge_levels):
            if total_score >= badge["min_score"]:
                return badge
        return self.rep_config.badge_levels[0]

6.2 发放信誉 NFT

class ReputationNFT:

    async def issue_reputation_nft(self, user: str, categories_scores: dict[str, int]) -> dict:
        total_score = self._calculate_total_score(categories_scores)
        badge_level = self._get_level(total_score)

        existing = await self.nft_service.get_user_reputation(user)

        if existing:
            existing_score = existing.get("total_score", 0)
            if existing_score >= total_score:
                logger.info(f"User {user} already has badge with score {existing_score} >= {total_score}, skipping")
                return {"action": "skipped", "reason": "existing_badge_equal_or_higher", "existing_score": existing_score, "new_score": total_score}
            logger.info(f"Upgrading badge for {user}: {existing_score} -> {total_score}")
            await self._execute_on_nft({"burn": {"token_id": existing["token_id"]}})

        dimensions = [
            {"name": cat, "score": score, "max_score": self.rep_config.categories.get(cat, {}).get("max_score", 100)}
            for cat, score in categories_scores.items()
        ]

        result = await self.nft_service.mint_reputation_badge(user_addr=user, total_score=total_score, dimensions=dimensions)
        await self._update_on_chain_reputation(user, categories_scores, total_score)

        return {
            "action": "issued" if not existing else "upgraded", "user": user,
            "previous_score": existing.get("total_score") if existing else None,
            "new_score": total_score, "level": badge_level,
            "token_id": result["token_id"], "txhash": result["txhash"],
        }

    async def _execute_on_nft(self, msg: dict) -> dict:
        execute_msg = MsgExecuteContract(sender=self.wallet.key.acc_address, contract=self.config.nft_contract_addr, msg=msg)
        simulated = self.client.estimate_fee(msgs=[execute_msg], wallet=self.wallet)
        fee = Fee(amount=Coins.from_str(self.config.gas_price), gas=simulated[0].gas_limit)
        tx = self.wallet.create_and_sign_tx(msgs=[execute_msg], fee=fee)
        result = self.client.broadcast_tx(tx)
        return {"txhash": result.txhash}

    async def _update_on_chain_reputation(self, user: str, category_scores: dict[str, int], total_score: int) -> None:
        rep_update_msg = {
            "update_reputation": {"user": user, "scores": category_scores, "total_score": total_score, "updated_at": int(time.time())},
        }
        try:
            await self._execute_on_nft(rep_update_msg)
            logger.info(f"On-chain reputation updated for {user}")
        except Exception as e:
            logger.error(f"Failed to update on-chain reputation: {e}")

6.3 成就验证

class ReputationNFT:

    async def verify_achievement(self, user: str, achievement_type: str) -> bool:
        achievements = await self.nft_service.get_user_achievements(user)
        return any(a["achievement_type"] == achievement_type for a in achievements)

    async def verify_multiple_achievements(self, user: str, required_achievements: list[str], require_all: bool = True) -> dict:
        achievements = await self.nft_service.get_user_achievements(user)
        held_types = {a["achievement_type"] for a in achievements}
        results = {}
        missing = []
        for ach in required_achievements:
            has = ach in held_types
            results[ach] = has
            if not has:
                missing.append(ach)
        verified = len(missing) == 0 if require_all else len(required_achievements) - len(missing) > 0
        return {"verified": verified, "results": results, "missing": missing, "held_count": len(held_types), "required_count": len(required_achievements)}

    async def get_achievement_summary(self, user: str) -> dict:
        achievements = await self.nft_service.get_user_achievements(user)
        by_type = {}
        for ach in achievements:
            atype = ach["achievement_type"]
            by_type.setdefault(atype, []).append(ach)
        return {
            "user": user, "total_achievements": len(achievements),
            "by_type": {k: len(v) for k, v in by_type.items()},
            "highest_level": max((a.get("level", 0) for a in achievements), default=0),
            "achievements": achievements,
        }

6.4 信誉数据查询

class ReputationNFT:

    async def get_user_reputation_profile(self, user: str) -> dict:
        badge = await self.nft_service.get_user_reputation(user)
        achievements = await self.nft_service.get_user_achievements(user)
        contributions = await self.nft_service.get_user_contributions(user)
        memberships = await self.nft_service.check_membership_valid(user)
        permissions = await self.nft_service.get_user_permissions(user)

        rep_score = badge["total_score"] if badge else 0
        ach_score = min(len(achievements) * 5, 30)
        contrib_score = min(sum(int(c.get("amount", 0)) for c in contributions) // 1000, 20)
        composite = min(rep_score + ach_score + contrib_score, 100)
        level = self._get_level(composite)

        return {
            "user": user, "composite_score": composite, "level": level,
            "components": {
                "reputation_badge": {"score": rep_score, "exists": badge is not None},
                "achievements": {"score": ach_score, "count": len(achievements)},
                "contributions": {"score": contrib_score, "count": len(contributions)},
            },
            "badge": badge, "achievements": achievements, "contributions": contributions,
            "memberships": memberships, "permissions": permissions,
        }

    async def get_leaderboard(self, category: Optional[str] = None, top_n: int = 20) -> list[dict]:
        msg = {"reputation_ranking": {"limit": top_n}}
        ranking = await self.nft_service._query_contract(self.config.nft_contract_addr, msg)
        enriched = []
        for user_addr, score in ranking[:top_n]:
            badge = await self.nft_service.get_user_reputation(user_addr)
            level = self._get_level(score)
            enriched.append({
                "user": user_addr, "score": score,
                "level": level["name"], "level_number": level["level"],
                "badge_token_id": badge["token_id"] if badge else None,
            })
        return enriched

6.5 信誉数据激励

class ReputationNFT:

    async def check_and_reward_milestone(self, user: str, event_type: str, event_data: dict) -> Optional[dict]:
        milestones = {
            "transaction": [
                {"name": "first_transaction", "threshold": 1, "title": "首次交易", "level": 1},
                {"name": "volume_milestone", "threshold": 100, "title": "百笔交易", "level": 2},
                {"name": "volume_milestone", "threshold": 1000, "title": "千笔交易", "level": 3},
                {"name": "volume_milestone", "threshold": 10000, "title": "万笔交易", "level": 4},
            ],
            "contribution": [
                {"name": "first_contribution", "threshold": 1, "title": "首次贡献", "level": 1},
                {"name": "community_contributor", "threshold": 10, "title": "十次贡献", "level": 2},
                {"name": "community_contributor", "threshold": 50, "title": "贡献达人", "level": 3},
            ],
            "governance_vote": [
                {"name": "governance_participant", "threshold": 1, "title": "首次投票", "level": 1},
                {"name": "governance_participant", "threshold": 10, "title": "积极选民", "level": 2},
            ],
            "referral": [
                {"name": "referral_star", "threshold": 1, "title": "推荐新秀", "level": 1},
                {"name": "referral_star", "threshold": 5, "title": "推荐达人", "level": 2},
                {"name": "referral_star", "threshold": 20, "title": "推荐之星", "level": 3},
            ],
            "streak": [
                {"name": "streak_milestone", "threshold": 7, "title": "连续7天", "level": 1},
                {"name": "streak_milestone", "threshold": 30, "title": "连续30天", "level": 2},
                {"name": "streak_milestone", "threshold": 100, "title": "连续100天", "level": 3},
            ],
        }

        event_milestones = milestones.get(event_type, [])
        if not event_milestones:
            return None

        current_count = event_data.get(f"count_{event_type}", 0)
        earned = []

        for ms in event_milestones:
            if current_count >= ms["threshold"]:
                has = await self.verify_achievement(user, ms["name"])
                if not has:
                    achievement = await self.nft_service.mint_achievement_badge(
                        user_addr=user, achievement_type=ms["name"], title=ms["title"],
                        description=f"达成 {ms['threshold']} {event_type} 里程碑",
                        level=ms["level"],
                        context={"event_type": event_type, "threshold": ms["threshold"], "current_count": current_count},
                    )
                    earned.append(achievement)

                    cat = {
                        "transaction": "trading", "contribution": "contribution",
                        "governance_vote": "governance", "referral": "community", "streak": "longevity",
                    }.get(event_type, "community")

                    profile = await self.get_user_reputation_profile(user)
                    current_scores = {}
                    for c in self.rep_config.categories:
                        current_scores[c] = 0
                    if profile.get("badge"):
                        for dim in profile["badge"].get("dimensions", []):
                            current_scores[dim["name"]] = dim["score"]
                    current_scores[cat] = min(current_scores.get(cat, 0) + 5, self.rep_config.categories[cat]["max_score"])
                    await self.issue_reputation_nft(user, current_scores)

        return {"earned": earned} if earned else None

7. NFT 收益与版税

7.1 版税合约逻辑

/// 版税与收益分配模块

use cosmwasm_std::{
    coin, ensure, Addr, BankMsg, Coin, Decimal256, DepsMut,
    Env, MessageInfo, Response, StdError, StdResult, Uint128,
};
use cw_storage_plus::{Item, Map};

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct RoyaltyConfig {
    pub creator: Addr,
    pub royalty_basis_points: u64,
    pub total_royalty_collected: Uint128,
    pub collection_count: u64,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct SaleInfo {
    pub token_id: String,
    pub contract_addr: Addr,
    pub seller: Addr,
    pub buyer: Addr,
    pub price: Uint128,
    pub denom: String,
    pub sold_at: u64,
}

#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct RoyaltyEarning {
    pub recipient: Addr,
    pub token_id: String,
    pub amount: Uint128,
    pub denom: String,
    pub sale_txhash: String,
    pub earned_at: u64,
}

pub const ROYALTY_CONFIG: Item<RoyaltyConfig> = Item::new("royalty_config");
pub const ROYALTY_EARNINGS: Map<&[u8], Vec<RoyaltyEarning>> = Map::new("royalty_earnings");
pub const SALE_HISTORY: Map<&str, SaleInfo> = Map::new("sale_history");

pub fn calculate_royalty(price: Uint128, royalty_basis_points: u64) -> StdResult<Uint128> {
    if royalty_basis_points > 10000 {
        return Err(StdError::generic_err("Royalty basis points cannot exceed 10000"));
    }
    Ok(price.multiply_ratio(royalty_basis_points, 10000u64))
}

pub fn handle_sale(deps: DepsMut, env: Env, info: MessageInfo, sale: SaleInfo) -> StdResult<Response> {
    let config = ROYALTY_CONFIG.load(deps.storage)?;
    let royalty_amount = calculate_royalty(sale.price, config.royalty_basis_points)?;
    let seller_net = sale.price.checked_sub(royalty_amount)
        .map_err(|_| StdError::generic_err("Price overflow"))?;

    let royalty_msg = BankMsg::Send {
        to_address: config.creator.to_string(),
        amount: vec![Coin { denom: sale.denom.clone(), amount: royalty_amount }],
    };
    let seller_msg = BankMsg::Send {
        to_address: sale.seller.to_string(),
        amount: vec![Coin { denom: sale.denom.clone(), amount: seller_net }],
    };

    let mut updated_config = config.clone();
    updated_config.total_royalty_collected = config.total_royalty_collected.checked_add(royalty_amount)?;
    updated_config.collection_count += 1;
    ROYALTY_CONFIG.save(deps.storage, &updated_config)?;

    let earning = RoyaltyEarning {
        recipient: config.creator.clone(), token_id: sale.token_id.clone(),
        amount: royalty_amount, denom: sale.denom.clone(),
        sale_txhash: env.transaction.as_ref().map(|t| t.hash.clone()).unwrap_or_default(),
        earned_at: env.block.time.seconds(),
    };
    let mut earnings = ROYALTY_EARNINGS.may_load(deps.storage, config.creator.as_bytes())?.unwrap_or_default();
    earnings.push(earning);
    ROYALTY_EARNINGS.save(deps.storage, config.creator.as_bytes(), &earnings)?;
    SALE_HISTORY.save(deps.storage, &sale.token_id, &sale)?;

    Ok(Response::new()
        .add_message(royalty_msg).add_message(seller_msg)
        .add_attribute("action", "handle_sale")
        .add_attribute("token_id", &sale.token_id)
        .add_attribute("seller", &sale.seller)
        .add_attribute("buyer", &sale.buyer)
        .add_attribute("price", sale.price.to_string())
        .add_attribute("royalty_amount", royalty_amount.to_string())
        .add_attribute("seller_net", seller_net.to_string())
        .add_attribute("royalty_percentage", config.royalty_basis_points.to_string()))
}

pub fn update_royalty(deps: DepsMut, env: Env, info: MessageInfo, creator: String, royalty_basis_points: u64) -> StdResult<Response> {
    let config = ROYALTY_CONFIG.load(deps.storage)?;
    let contract_config = CONFIG.load(deps.storage)?;
    if info.sender != contract_config.minter && info.sender != config.creator {
        return Err(StdError::generic_err("Only the minter or creator can update royalty"));
    }
    if royalty_basis_points > 10000 {
        return Err(StdError::generic_err("Royalty cannot exceed 10000 bps"));
    }
    ROYALTY_CONFIG.save(deps.storage, &RoyaltyConfig {
        creator: deps.api.addr_validate(&creator)?,
        royalty_basis_points,
        total_royalty_collected: config.total_royalty_collected,
        collection_count: config.collection_count,
    })?;
    Ok(Response::new()
        .add_attribute("action", "update_royalty")
        .add_attribute("creator", creator)
        .add_attribute("royalty_basis_points", royalty_basis_points.to_string()))
}

7.2 Python 版税管理服务

\"\"\"版税与收益管理服务\"\"\"

from decimal import Decimal
from typing import Optional


class RoyaltyManager:

    def __init__(self, config: AgentNFTConfig):
        self.config = config
        self.wallet = config.get_wallet()
        self.client = config.get_client()

    async def set_royalty(self, creator: str, royalty_basis_points: int) -> dict:
        msg = {"update_royalty_info": {"creator": creator, "royalty_basis_points": royalty_basis_points}}
        execute_msg = MsgExecuteContract(sender=self.wallet.key.acc_address, contract=self.config.nft_contract_addr, msg=msg)
        simulated = self.client.estimate_fee(msgs=[execute_msg], wallet=self.wallet)
        fee = Fee(amount=Coins.from_str(self.config.gas_price), gas=simulated[0].gas_limit)
        tx = self.wallet.create_and_sign_tx(msgs=[execute_msg], fee=fee)
        result = self.client.broadcast_tx(tx)
        return {
            "action": "set_royalty", "creator": creator, "royalty_basis_points": royalty_basis_points,
            "royalty_percentage": f"{royalty_basis_points / 100}%", "txhash": result.txhash,
        }

    async def get_royalty_info(self) -> dict:
        info = self.client.wasm.contract_query(self.config.nft_contract_addr, {"config": {}})
        return {
            "creator": info.get("creator"), "royalty_basis_points": info.get("royalty_basis_points", 0),
            "royalty_percentage": f"{info.get('royalty_basis_points', 0) / 100}%",
        }

    async def get_royalty_earnings(self, address: Optional[str] = None) -> list[dict]:
        target = address or self.wallet.key.acc_address
        return self.client.wasm.contract_query(self.config.nft_contract_addr, {"royalty_earnings": {"address": target, "limit": 100}}) or []

    async def get_total_royalty_collected(self) -> dict:
        config = self.client.wasm.contract_query(self.config.nft_contract_addr, {"config": {}})
        total = int(config.get("total_royalty_collected", 0))
        return {"total_collected": str(total), "total_collected_display": f"{total / 1_000_000:,.2f} MSG", "sale_count": config.get("collection_count", 0)}

7.3 二次销售收益分配

class RoyaltyManager:

    async def distribute_secondary_sale(self, token_id: str, sale_price: int, buyer: str, seller: str) -> dict:
        info = await self.get_royalty_info()
        basis_points = info.get("royalty_basis_points", 0)
        royalty_amount = sale_price * basis_points // 10000
        market_fee = sale_price * 200 // 10000
        seller_amount = sale_price - royalty_amount - market_fee
        record = {
            "token_id": token_id, "price": sale_price, "royalty": royalty_amount,
            "market_fee": market_fee, "seller_net": seller_amount,
            "buyer": buyer, "seller": seller, "timestamp": int(time.time()),
        }
        logger.info(f"Secondary sale: token={token_id} price={sale_price} royalty={royalty_amount} seller={seller_amount}")
        return record

    async def get_sale_history(self, token_id: Optional[str] = None) -> list[dict]:
        msg = {"sale_history": {"token_id": token_id}} if token_id else {"sale_history": {}}
        return self.client.wasm.contract_query(self.config.nft_contract_addr, msg) or []

    async def get_creator_analytics(self, creator: str) -> dict:
        earnings = await self.get_royalty_earnings(creator)
        total_volume = sum(int(e.get("amount", 0)) for e in earnings)
        sale_count = len(earnings)
        return {
            "creator": creator, "total_earnings": total_volume,
            "total_earnings_display": f"{total_volume / 1_000_000:,.2f} MSG",
            "sale_count": sale_count, "average_earning": total_volume // sale_count if sale_count > 0 else 0,
            "recent_earnings": earnings[-10:] if earnings else [],
        }

7.4 收入分享

class RevenueSharing:

    def __init__(self, config: AgentNFTConfig):
        self.config = config
        self.wallet = config.get_wallet()
        self.client = config.get_client()

    async def distribute_share(self, total_amount: int, shares: list[tuple[str, int]], denom: str = MSG_DENOM) -> dict:
        total_bps = sum(bps for _, bps in shares)
        if total_bps != 10000:
            raise ValueError(f"Basis points must sum to 10000, got {total_bps}")
        execute_msg = MsgExecuteContract(
            sender=self.wallet.key.acc_address, contract=self.config.nft_contract_addr,
            msg={"distribute": {}}, funds=[{"denom": denom, "amount": str(total_amount)}],
        )
        simulated = self.client.estimate_fee(msgs=[execute_msg], wallet=self.wallet)
        fee = Fee(amount=Coins.from_str(self.config.gas_price), gas=simulated[0].gas_limit)
        tx = self.wallet.create_and_sign_tx(msgs=[execute_msg], fee=fee)
        result = self.client.broadcast_tx(tx)
        return {
            "action": "distribute", "total_amount": total_amount,
            "total_display": f"{total_amount / 1_000_000:.2f} MSG",
            "recipients": [{"address": addr, "bps": bps, "amount": total_amount * bps // 10000, "display": f"{total_amount * bps // 10000 / 1_000_000:.2f} MSG"} for addr, bps in shares],
            "txhash": result.txhash,
        }

    async def setup_revenue_split(self, token_id: str, shares: list[tuple[str, int]]) -> dict:
        msg = {"set_revenue_split": {"token_id": token_id, "shares": [{"address": addr, "basis_points": bps} for addr, bps in shares]}}
        execute_msg = MsgExecuteContract(sender=self.wallet.key.acc_address, contract=self.config.nft_contract_addr, msg=msg)
        simulated = self.client.estimate_fee(msgs=[execute_msg], wallet=self.wallet)
        fee = Fee(amount=Coins.from_str(self.config.gas_price), gas=simulated[0].gas_limit)
        tx = self.wallet.create_and_sign_tx(msgs=[execute_msg], fee=fee)
        result = self.client.broadcast_tx(tx)
        return {"action": "setup_revenue_split", "token_id": token_id, "shares": shares, "txhash": result.txhash}

8. 完整示例:Agent NFT Collection 启动

8.1 完整启动脚本

#!/usr/bin/env python3
\"\"\"
完整示例:AI Agent NFT Collection 启动脚本

本脚本演示一个 Agent 如何:
  1. 实例化 NFT 合约
  2. 铸造多种类型 NFT
  3. 配置版税
  4. 在市场挂单
  5. 设置门控访问
\"\"\"

import asyncio
import json
import logging
import os

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("NFT-Launch")

AGENT_MNEMONIC = os.getenv("AGENT_MNEMONIC", "word1 word2 ... word24")
AGENT_ADDR = "msg1agent..."
USER_ADDR_1 = "msg1user1..."
USER_ADDR_2 = "msg1user2..."
WASM_CODE_ID = 123


async def main():
    config = AgentNFTConfig(mnemonic=AGENT_MNEMONIC)
    wallet = config.get_wallet()

    # ============================================================
    # Step 1: 实例化 NFT 合约
    # ============================================================
    logger.info("=== Step 1: 实例化 NFT 合约 ===")

    init_msg = {
        "name": "AI Agent Achievement Collection",
        "symbol": "AGENTNFT",
        "minter": AGENT_ADDR,
        "agent_did": f"did:msg:{AGENT_ADDR}",
        "nft_type": "achievement",
        "description": "AI Agent 成就与会员 NFT 集合",
        "image": "https://nft.msgchain.org/collections/agent-achievements.png",
        "external_link": "https://msgchain.org/agents/nft",
        "royalty_info": {"creator": AGENT_ADDR, "royalty_basis_points": 500},
    }

    instantiate_msg = MsgInstantiateContract(
        sender=AGENT_ADDR, code_id=WASM_CODE_ID, msg=init_msg, funds=[], label="AI-Agent-NFT-Collection",
    )
    simulated = config.get_client().estimate_fee(msgs=[instantiate_msg], wallet=wallet)
    fee = Fee(amount=Coins.from_str(config.gas_price), gas=simulated[0].gas_limit)
    tx = wallet.create_and_sign_tx(msgs=[instantiate_msg], fee=fee)
    result = config.get_client().broadcast_tx(tx)
    logger.info(f"Contract deployed TX: {result.txhash}")

    nft_contract_addr = "msg1nftcontract..."  # parse from events
    config.nft_contract_addr = nft_contract_addr
    logger.info(f"NFT Contract: {nft_contract_addr}")

    # ============================================================
    # Step 2: 初始化服务
    # ============================================================
    logger.info("=== Step 2: 初始化服务 ===")
    nft_service = AgentNFTService(config)
    marketplace = NFTMarketplace(config, marketplace_addr="msg1marketplace...")
    gated_access = NFTGatedAccess(config)
    reputation = ReputationNFT(config)

    # ============================================================
    # Step 3: 铸造信誉徽章
    # ============================================================
    logger.info("=== Step 3: 铸造信誉徽章 ===")

    result = await nft_service.mint_reputation_badge(
        user_addr=USER_ADDR_1, total_score=85,
        dimensions=[
            {"name": "trading", "score": 90, "max_score": 100},
            {"name": "contribution", "score": 80, "max_score": 100},
            {"name": "governance", "score": 75, "max_score": 100},
        ],
    )
    logger.info(f"User1 reputation badge: {result['token_id']}")

    result = await nft_service.mint_reputation_badge(user_addr=USER_ADDR_2, total_score=45)
    logger.info(f"User2 reputation badge: {result['token_id']}")

    # ============================================================
    # Step 4: 铸造成就徽章
    # ============================================================
    logger.info("=== Step 4: 铸造成就徽章 ===")

    achievements = [
        (USER_ADDR_1, "first_transaction", "首次交易达成", 1),
        (USER_ADDR_1, "volume_milestone", "累计 1000 笔交易", 3),
        (USER_ADDR_2, "first_transaction", "首次交易达成", 1),
        (USER_ADDR_2, "referral_star", "推荐 5 位新用户", 2),
    ]
    for user, ach_type, title, level in achievements:
        result = await nft_service.mint_achievement_badge(
            user_addr=user, achievement_type=ach_type, title=title,
            description=f"成就: {title}", level=level,
        )
        logger.info(f"Achievement minted: {result['title']} -> {result['token_id']}")

    # ============================================================
    # Step 5: 铸造会员通行证
    # ============================================================
    logger.info("=== Step 5: 铸造会员通行证 ===")

    result = await nft_service.mint_membership_pass(
        user_addr=USER_ADDR_1, tier="gold", duration_days=180,
        benefits=["无限制 API 调用", "专属支持", "实时数据分析"],
    )
    logger.info(f"Gold membership for user1: {result['token_id']}, expires: {result['valid_until']}")

    result = await nft_service.mint_membership_pass(
        user_addr=USER_ADDR_2, tier="bronze", duration_days=30,
    )
    logger.info(f"Bronze membership for user2: {result['token_id']}")

    # ============================================================
    # Step 6: 铸造贡献证明
    # ============================================================
    logger.info("=== Step 6: 铸造贡献证明 ===")

    result = await nft_service.mint_contribution_nft(
        contribution_id="PR-42", contributor=USER_ADDR_1,
        contribution_type="code", description="Added cross-chain NFT bridge",
        amount=500, verifiers=[AGENT_ADDR],
    )
    logger.info(f"Contribution NFT: {result['token_id']}")

    # ============================================================
    # Step 7: 铸造访问通行证
    # ============================================================
    logger.info("=== Step 7: 铸造访问通行证 ===")

    result = await nft_service.mint_access_pass(
        user_addr=USER_ADDR_1,
        permissions=["premium_query", "data_export", "batch_process"],
        duration_days=90, max_uses=10000,
    )
    logger.info(f"Access pass for user1: {result['token_id']}")

    # ============================================================
    # Step 8: 市场挂单
    # ============================================================
    logger.info("=== Step 8: 市场挂单 ===")

    # 用户2 挂单出售其青铜会员
    result = await marketplace.list_nft(
        token_id="<user2-membership-token-id>",
        price=50_000_000,  # 50 MSG
    )
    logger.info(f"Listed on marketplace: {result['txhash']}")

    # ============================================================
    # Step 9: 门控访问验证
    # ============================================================
    logger.info("=== Step 9: 门控访问验证 ===")

    for user, feature in [(USER_ADDR_1, "premium_query"), (USER_ADDR_1, "admin_panel"), (USER_ADDR_2, "premium_query")]:
        access = await gated_access.check_access(user, feature)
        logger.info(f"User {user[:12]} -> {feature}: {'ALLOWED' if access else 'DENIED'}")

    # ============================================================
    # Step 10: 信誉系统集成
    # ============================================================
    logger.info("=== Step 10: 信誉系统集成 ===")

    result = await reputation.issue_reputation_nft(
        user=USER_ADDR_1,
        categories_scores={
            "trading": 95, "contribution": 88,
            "governance": 70, "community": 85, "longevity": 60,
        },
    )
    logger.info(f"Reputation issued: {result['action']}, level={result['level']}")

    profile = await reputation.get_user_reputation_profile(USER_ADDR_1)
    logger.info(f"User1 composite score: {profile['composite_score']}")

    # 验证成就
    verified = await reputation.verify_achievement(USER_ADDR_1, "first_transaction")
    logger.info(f"User1 has first_transaction: {verified}")

    # ============================================================
    # Summary
    # ============================================================
    logger.info("=== Collection Launch Complete ===")
    logger.info(f"Contract: {nft_contract_addr}")
    logger.info(f"Minted: achievements={len(achievements)}, memberships=2, contributions=1, passes=1")


if __name__ == "__main__":
    asyncio.run(main())

本文档为 MSG Chain AI Agent NFT 铸造与数字权益的完整指南。 涵盖了从合约开发、NFT 铸造、市场交易、门控访问到信誉系统集成的全链路。所有合约基于 CosmWasm CW721 标准,并针对 Agent 场景做了专门扩展。

关键词: msg-chain-1 msg CW721 AI Agent NFT CosmWasm Reputation Gated Access Royalty