AI Agent 链上信誉评分系统
适用链:
msg-chain-1| Bech32 前缀:msg
核心语言:Rust (CosmWasm) + Python (算法) + TypeScript (客户端)
版本:v1.0.0⚠️ No-Go Disclaimer: MSGChain 主网裁决为 No-Go。本文件所有内容反映的是开发阶段的技术设计,不代表主网未独立核验上线状态。生产部署状态请以白皮书为准:https://msgchain.org/whitepaper/
目录
1. 概述
1.1 为什么 AI Agent 需要信誉系统
在 MSG Chain 上,AI Agent 自主执行任务、支付费用、相互协作。没有信誉系统,整个 Agent 经济面临以下根本性问题:
- 信息不对称:任务发布者无法区分可靠 Agent 与恶意 Agent
- 道德风险:低质量 Agent 伪装成高质量,获取任务后交付劣质结果
- 逆向选择:优质 Agent 因无法证明自身价值而退出市场
- 交易成本:每次交互都需要高额抵押或预付款,降低经济效率
- 协作障碍:Agent 间无法互相信任,限制复杂协作工作流的构建
信誉评分系统通过量化 Agent 的历史行为,为任务匹配、定价、抵押要求等提供可验证的参考依据。
1.2 信誉来源
系统从三个维度收集信誉数据:
| 来源 | 类型 | 可信度 | 示例 |
|---|---|---|---|
| 链上行为 | 客观 | 高 | 任务完成率、违约记录、支付历史 |
| 链下评价 | 主观 | 中 | 任务发布者的星级评分、文字评价 |
| 可验证凭证 | 认证 | 高 | KYC 认证、第三方审计报告、技能证书 |
链上行为直接从交易历史中提取,不可篡改,是最可靠的信誉来源。链下评价通过链上提交哈希、链下存储内容的方式保存。可验证凭证由受信任的发行方签名,Agent 可选择性披露。
1.3 评分核心维度
系统定义了六个核心评分维度:
- 任务成功率(权重 30%):已完成任务 / 总接受任务
- 平均评分(权重 25%):任务发布者的 1-5 星评价均值
- 任务总量(权重 15%):历史累计完成任务数(对数缩放)
- 抵押乘数(权重 10%):根据抵押量获得的信誉加成
- 时间衰减(权重 10%):近期行为权重高于历史行为
- 多样性(权重 10%):合作过的不同发布者数量
1.4 评分公式概要
composite_score = 0.30 * normalized_success_rate
+ 0.25 * (avg_rating - 1) / 4
+ 0.15 * log(1 + total_tasks) / log(1 + MAX_TASKS)
+ 0.10 * stake_multiplier
+ 0.10 * time_decay_factor
+ 0.10 * diversity_factor
评分范围归一化为 [0, 100],其中:
- 0-20:低信誉 —— 需要高额抵押,受限参与
- 21-50:基础信誉 —— 正常参与,标准抵押
- 51-75:良好信誉 —— 降低抵押,优先匹配
- 76-90:高信誉 —— 低抵押,高优先级
- 91-100:顶级信誉 —— 最低抵押,VIP 队列
1.5 系统架构概览
+-------------------------------------------------------+
| 前端 (React) |
| 信誉看板 | 评分详情 | Agent 对比 | 争议页面 |
+---------------------------+---------------------------+
| RPC / GraphQL
+---------------------------v---------------------------+
| API 层 (TypeScript) |
| 查询聚合 | 信誉排序 | 任务匹配 | 争议提交 |
+---------------------------+---------------------------+
| CosmWasm
+---------------------------v---------------------------+
| 智能合约层 (Rust) |
| +----------+ +----------+ +----------+ |
| | 信誉核心 | | 争议模块 | | 治理模块 | |
| +----------+ +----------+ +----------+ |
| +----------+ +----------+ +----------+ |
| | 评分算法 | | 抵押模块 | | 凭证验证 | |
| +----------+ +----------+ +----------+ |
+---------------------------+---------------------------+
| ABCI / Cosmos SDK
+---------------------------v---------------------------+
| MSG Chain 底层 |
| 共识 | 存储 | IBC | 质押 | 治理 |
+-------------------------------------------------------+
2. 信誉合约实现
2.1 合约结构
contracts/
+-- reputation-core/ # 信誉核心合约
| +-- Cargo.toml
| +-- src/
| | +-- contract.rs # 入口与消息分发
| | +-- state.rs # 状态存储
| | +-- msg.rs # 消息定义
| | +-- scoring.rs # 评分算法
| | +-- stake.rs # 抵押逻辑
| | +-- dispute.rs # 争议处理
| | +-- helpers.rs # 工具函数
| +-- examples/
| +-- full_integration.rs
+-- reputation-oracle/ # 链下数据预言机适配器
+-- reputation-governance/ # 治理覆写模块
2.2 Cargo.toml
[package]
name = "reputation-core"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib", "rlib"]
[features]
default = ["library"]
library = []
[dependencies]
cosmwasm-std = { version = "1.5", features = ["stargate"] }
cosmwasm-storage = "1.5"
cosmwasm-schema = "1.5"
cw-storage-plus = "1.2"
cw-controllers = "1.1"
cw-utils = "1.0"
cw2 = "1.1"
schemars = "0.8"
serde = { version = "1.0", default-features = false, features = ["derive"] }
serde-json-wasm = "0.5"
thiserror = "1.0"
uint = "0.9"
cosmwasm-crypto = "1.5"
[dev-dependencies]
cw-multi-test = "0.18"
assert_matches = "1.5"
2.3 状态定义
// contracts/reputation-core/src/state.rs
use cosmwasm_std::{Addr, Decimal, Timestamp, Uint128};
use cw_storage_plus::{Item, Map, IndexedMap, MultiIndex};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ReputationScore {
pub agent: Addr,
pub total_tasks: Uint128,
pub successful_tasks: Uint128,
pub failed_tasks: Uint128,
pub avg_rating: Decimal,
pub total_earned: Uint128,
pub stake_amount: Uint128,
pub last_activity: Timestamp,
pub composite_score: Decimal,
}
impl ReputationScore {
pub fn new(agent: Addr) -> Self {
ReputationScore {
agent,
total_tasks: Uint128::zero(),
successful_tasks: Uint128::zero(),
failed_tasks: Uint128::zero(),
avg_rating: Decimal::one(),
total_earned: Uint128::zero(),
stake_amount: Uint128::zero(),
last_activity: Timestamp::from_nanos(0),
composite_score: Decimal::zero(),
}
}
pub fn success_rate(&self) -> Decimal {
if self.total_tasks.is_zero() {
return Decimal::zero();
}
Decimal::from_ratio(self.successful_tasks, self.total_tasks)
}
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct TaskRecord {
pub task_id: String,
pub agent: Addr,
pub requester: Addr,
pub status: TaskStatus,
pub rating: Option<u8>,
pub reward: Uint128,
pub timestamp: Timestamp,
pub evidence_hash: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub enum TaskStatus {
Assigned,
InProgress,
Completed,
Failed,
Disputed,
Resolved,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Rating {
pub rater: Addr,
pub agent: Addr,
pub task_id: String,
pub score: u8,
pub comment_hash: String,
pub timestamp: Timestamp,
pub weight: Decimal,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct Dispute {
pub dispute_id: String,
pub task_id: String,
pub initiator: Addr,
pub respondent: Addr,
pub dispute_type: DisputeType,
pub status: DisputeStatus,
pub evidence_hashes: Vec<String>,
pub arbitrator: Option<Addr>,
pub created_at: Timestamp,
pub resolved_at: Option<Timestamp>,
pub resolution: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub enum DisputeType {
RatingDisagreement,
FraudAllegation,
NonDelivery,
Plagiarism,
Other(String),
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub enum DisputeStatus {
Open,
UnderReview,
Resolved,
Dismissed,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct StakePosition {
pub agent: Addr,
pub amount: Uint128,
pub locked_until: Timestamp,
pub staked_at: Timestamp,
pub auto_renew: bool,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct GovernanceParams {
pub success_rate_weight: Decimal,
pub rating_weight: Decimal,
pub volume_weight: Decimal,
pub stake_weight: Decimal,
pub recency_weight: Decimal,
pub diversity_weight: Decimal,
pub min_rating: u8,
pub max_rating: u8,
pub dispute_deposit: Uint128,
pub max_disputes_active: u32,
pub admin: Addr,
}
impl Default for GovernanceParams {
fn default() -> Self {
GovernanceParams {
success_rate_weight: Decimal::percent(30),
rating_weight: Decimal::percent(25),
volume_weight: Decimal::percent(15),
stake_weight: Decimal::percent(10),
recency_weight: Decimal::percent(10),
diversity_weight: Decimal::percent(10),
min_rating: 1,
max_rating: 5,
dispute_deposit: Uint128::new(100_000_000),
max_disputes_active: 5,
admin: Addr::unchecked(""),
}
}
}
pub const CONFIG_KEY: &str = "config";
pub const LATEST_SCORE_KEY: &str = "latest_score";
pub const AGENT_SCORES: Map<&Addr, ReputationScore> = Map::new("agent_scores");
pub struct ScoreIndexes {
pub by_composite: MultiIndex<Vec<u8>, ReputationScore, Addr>,
}
pub const AGENT_SCORE_INDEXES: IndexedMap<&Addr, ReputationScore> = IndexedMap::new(
"score_index",
ScoreIndexes {
by_composite: MultiIndex::new(
|_, score| score.composite_score.to_string().into_bytes(),
"score_index",
"by_composite",
),
},
);
pub const TASK_RECORDS: Map<&str, TaskRecord> = Map::new("tasks");
pub const AGENT_TASKS: Map<(&Addr, &str), bool> = Map::new("agent_tasks");
pub const RATINGS: Map<(&Addr, &str), Vec<Rating>> = Map::new("ratings");
pub const DISPUTES: Map<&str, Dispute> = Map::new("disputes");
pub const AGENT_DISPUTES: Map<(&Addr, &str), bool> = Map::new("agent_disputes");
pub const STAKE_POSITIONS: Map<&Addr, StakePosition> = Map::new("stakes");
pub const GOV_PARAMS: Item<GovernanceParams> = Item::new("gov_params");
pub const COLLABORATORS: Map<(&Addr, &Addr), bool> = Map::new("collabs");
2.4 消息定义
// contracts/reputation-core/src/msg.rs
use cosmwasm_std::{Addr, Decimal, Timestamp, Uint128};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct InstantiateMsg {
pub admin: String,
pub params: Option<GovernanceParamsMsg>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct GovernanceParamsMsg {
pub success_rate_weight: Option<Decimal>,
pub rating_weight: Option<Decimal>,
pub volume_weight: Option<Decimal>,
pub stake_weight: Option<Decimal>,
pub recency_weight: Option<Decimal>,
pub diversity_weight: Option<Decimal>,
pub min_rating: Option<u8>,
pub max_rating: Option<u8>,
pub dispute_deposit: Option<Uint128>,
pub max_disputes_active: Option<u32>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
RecordTaskResult {
task_id: String,
agent: String,
requester: String,
status: TaskStatusMsg,
reward: Uint128,
evidence_hash: Option<String>,
},
SubmitRating {
agent: String,
task_id: String,
score: u8,
comment_hash: String,
},
Stake {
amount: Uint128,
lock_duration_secs: u64,
},
Unstake {
amount: Uint128,
},
OpenDispute {
task_id: String,
respondent: String,
dispute_type: DisputeTypeMsg,
evidence_hashes: Vec<String>,
},
SubmitDisputeEvidence {
dispute_id: String,
evidence_hash: String,
},
ResolveDispute {
dispute_id: String,
resolution: String,
adjusted_rating: Option<u8>,
},
GovernanceOverride {
agent: String,
new_score: Decimal,
reason: String,
},
UpdateParams {
params: GovernanceParamsMsg,
},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub enum TaskStatusMsg {
Completed,
Failed,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub enum DisputeTypeMsg {
RatingDisagreement,
FraudAllegation,
NonDelivery,
Plagiarism,
Other(String),
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum QueryMsg {
GetReputation { agent: String },
GetReputationBatch { agents: Vec<String> },
ListTopAgents { start_after: Option<String>, limit: Option<u32> },
GetAgentTasks { agent: String, start_after: Option<String>, limit: Option<u32> },
GetAgentRatings { agent: String, start_after: Option<String>, limit: Option<u32> },
GetDispute { dispute_id: String },
GetStake { agent: String },
GetParams {},
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ReputationResponse {
pub score: ReputationScoreResp,
pub breakdown: ScoreBreakdown,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ReputationScoreResp {
pub agent: Addr,
pub total_tasks: Uint128,
pub successful_tasks: Uint128,
pub failed_tasks: Uint128,
pub success_rate: Decimal,
pub avg_rating: Decimal,
pub total_earned: Uint128,
pub stake_amount: Uint128,
pub last_activity: Timestamp,
pub composite_score: Decimal,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ScoreBreakdown {
pub success_rate_component: Decimal,
pub rating_component: Decimal,
pub volume_component: Decimal,
pub stake_component: Decimal,
pub recency_component: Decimal,
pub diversity_component: Decimal,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct AgentListResponse {
pub agents: Vec<ReputationScoreResp>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct TaskListResponse {
pub tasks: Vec<TaskRecordResp>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct TaskRecordResp {
pub task_id: String,
pub agent: Addr,
pub requester: Addr,
pub status: String,
pub rating: Option<u8>,
pub reward: Uint128,
pub timestamp: Timestamp,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct RatingListResponse {
pub ratings: Vec<RatingResp>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct RatingResp {
pub rater: Addr,
pub score: u8,
pub comment_hash: String,
pub timestamp: Timestamp,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct DisputeResponse {
pub dispute: DisputeResp,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct DisputeResp {
pub dispute_id: String,
pub task_id: String,
pub initiator: Addr,
pub respondent: Addr,
pub dispute_type: String,
pub status: String,
pub evidence_hashes: Vec<String>,
pub created_at: Timestamp,
pub resolved_at: Option<Timestamp>,
pub resolution: Option<String>,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct StakeResponse {
pub agent: Addr,
pub amount: Uint128,
pub locked_until: Timestamp,
pub staked_at: Timestamp,
pub auto_renew: bool,
}
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
pub struct ParamsResponse {
pub params: GovernanceParams,
}
2.5 合约入口
// contracts/reputation-core/src/contract.rs
use cosmwasm_std::{
entry_point, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo,
Response, StdError, StdResult,
};
use crate::msg::{
ExecuteMsg, InstantiateMsg, QueryMsg,
ReputationResponse, AgentListResponse, TaskListResponse,
RatingListResponse, DisputeResponse, StakeResponse, ParamsResponse,
};
use crate::state::{GovernanceParams, GOV_PARAMS};
use crate::scoring::{compute_composite_score, compute_score_breakdown};
use crate::stake::handle_stake;
const MAX_LIMIT: u32 = 100;
const DEFAULT_LIMIT: u32 = 20;
#[entry_point]
pub fn instantiate(
deps: DepsMut,
_env: Env,
info: MessageInfo,
msg: InstantiateMsg,
) -> StdResult<Response> {
let params = GovernanceParams {
admin: deps.api.addr_validate(&msg.admin)?,
..Default::default()
};
GOV_PARAMS.save(deps.storage, ¶ms)?;
Ok(Response::new()
.add_attribute("action", "instantiate")
.add_attribute("admin", params.admin))
}
#[entry_point]
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> StdResult<Response> {
match msg {
ExecuteMsg::RecordTaskResult { task_id, agent, requester, status, reward, evidence_hash } => {
execute_record_task_result(deps, env, info, task_id, agent, requester, status, reward, evidence_hash)
}
ExecuteMsg::SubmitRating { agent, task_id, score, comment_hash } => {
execute_submit_rating(deps, env, info, agent, task_id, score, comment_hash)
}
ExecuteMsg::Stake { amount, lock_duration_secs } => {
handle_stake(deps, env, info, amount, lock_duration_secs)
}
ExecuteMsg::Unstake { amount } => {
handle_unstake(deps, env, info, amount)
}
ExecuteMsg::OpenDispute { task_id, respondent, dispute_type, evidence_hashes } => {
handle_open_dispute(deps, env, info, task_id, respondent, dispute_type, evidence_hashes)
}
ExecuteMsg::SubmitDisputeEvidence { dispute_id, evidence_hash } => {
handle_submit_evidence(deps, env, info, dispute_id, evidence_hash)
}
ExecuteMsg::ResolveDispute { dispute_id, resolution, adjusted_rating } => {
handle_resolve_dispute(deps, env, info, dispute_id, resolution, adjusted_rating)
}
ExecuteMsg::GovernanceOverride { agent, new_score, reason } => {
handle_governance_override(deps, env, info, agent, new_score, reason)
}
ExecuteMsg::UpdateParams { params } => {
handle_update_params(deps, env, info, params)
}
}
}
#[entry_point]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg {
QueryMsg::GetReputation { agent } => {
to_json_binary(&query_reputation(deps, agent)?)
}
QueryMsg::GetReputationBatch { agents } => {
to_json_binary(&query_reputation_batch(deps, agents)?)
}
QueryMsg::ListTopAgents { start_after, limit } => {
to_json_binary(&query_top_agents(deps, start_after, limit)?)
}
QueryMsg::GetAgentTasks { agent, start_after, limit } => {
to_json_binary(&query_agent_tasks(deps, agent, start_after, limit)?)
}
QueryMsg::GetAgentRatings { agent, start_after, limit } => {
to_json_binary(&query_agent_ratings(deps, agent, start_after, limit)?)
}
QueryMsg::GetDispute { dispute_id } => {
to_json_binary(&query_dispute(deps, dispute_id)?)
}
QueryMsg::GetStake { agent } => {
to_json_binary(&query_stake(deps, agent)?)
}
QueryMsg::GetParams {} => {
to_json_binary(&query_params(deps)?)
}
}
}
fn execute_record_task_result(
deps: DepsMut,
env: Env,
_info: MessageInfo,
task_id: String,
agent: String,
requester: String,
status: crate::msg::TaskStatusMsg,
reward: Uint128,
evidence_hash: Option<String>,
) -> StdResult<Response> {
let agent_addr = deps.api.addr_validate(&agent)?;
let requester_addr = deps.api.addr_validate(&requester)?;
let task_status = match status {
crate::msg::TaskStatusMsg::Completed => crate::state::TaskStatus::Completed,
crate::msg::TaskStatusMsg::Failed => crate::state::TaskStatus::Failed,
};
let task = crate::state::TaskRecord {
task_id: task_id.clone(),
agent: agent_addr.clone(),
requester: requester_addr.clone(),
status: task_status,
rating: None,
reward,
timestamp: env.block.time,
evidence_hash,
};
crate::state::TASK_RECORDS.save(deps.storage, &task_id, &task)?;
crate::state::AGENT_TASKS.save(deps.storage, (&agent_addr, &task_id), &true)?;
crate::state::COLLABORATORS.save(deps.storage, (&agent_addr, &requester_addr), &true)?;
let mut score = crate::state::AGENT_SCORES
.may_load(deps.storage, &agent_addr)?
.unwrap_or_else(|| crate::state::ReputationScore::new(agent_addr.clone()));
score.total_tasks += Uint128::one();
score.total_earned += reward;
score.last_activity = env.block.time;
match status {
crate::msg::TaskStatusMsg::Completed => {
score.successful_tasks += Uint128::one();
}
crate::msg::TaskStatusMsg::Failed => {
score.failed_tasks += Uint128::one();
}
}
let params = GOV_PARAMS.load(deps.storage)?;
let breakdown = compute_score_breakdown(deps.storage, &score, &agent_addr, &env.block.time, ¶ms)?;
score.composite_score = compute_composite_score(&breakdown, ¶ms);
crate::state::AGENT_SCORES.save(deps.storage, &agent_addr, &score)?;
Ok(Response::new()
.add_attribute("action", "record_task_result")
.add_attribute("task_id", &task_id)
.add_attribute("agent", &agent)
.add_attribute("status", format!("{:?}", status)))
}
fn execute_submit_rating(
deps: DepsMut,
env: Env,
_info: MessageInfo,
agent: String,
task_id: String,
score: u8,
comment_hash: String,
) -> StdResult<Response> {
let agent_addr = deps.api.addr_validate(&agent)?;
let params = GOV_PARAMS.load(deps.storage)?;
if score < params.min_rating || score > params.max_rating {
return Err(StdError::generic_err(format!(
"Rating must be between {} and {}", params.min_rating, params.max_rating
)));
}
let mut task = crate::state::TASK_RECORDS
.load(deps.storage, &task_id)
.map_err(|_| StdError::generic_err("Task not found"))?;
task.rating = Some(score);
crate::state::TASK_RECORDS.save(deps.storage, &task_id, &task)?;
let rating = crate::state::Rating {
rater: task.requester.clone(),
agent: agent_addr.clone(),
task_id: task_id.clone(),
score,
comment_hash,
timestamp: env.block.time,
weight: Decimal::one(),
};
let mut ratings = crate::state::RATINGS
.may_load(deps.storage, (&agent_addr, &task_id))?
.unwrap_or_default();
ratings.push(rating);
crate::state::RATINGS.save(deps.storage, (&agent_addr, &task_id), &ratings)?;
let mut total_score = 0u64;
let mut count = 0u64;
for r in ratings.iter() {
total_score += r.score as u64;
count += 1;
}
let mut agent_score = crate::state::AGENT_SCORES.load(deps.storage, &agent_addr)?;
if count > 0 {
agent_score.avg_rating = Decimal::from_ratio(total_score, count);
}
let breakdown = compute_score_breakdown(deps.storage, &agent_score, &agent_addr, &env.block.time, ¶ms)?;
agent_score.composite_score = compute_composite_score(&breakdown, ¶ms);
crate::state::AGENT_SCORES.save(deps.storage, &agent_addr, &agent_score)?;
Ok(Response::new()
.add_attribute("action", "submit_rating")
.add_attribute("agent", &agent)
.add_attribute("task_id", &task_id)
.add_attribute("score", score.to_string()))
}
fn handle_unstake(
deps: DepsMut,
env: Env,
info: MessageInfo,
amount: Uint128,
) -> StdResult<Response> {
let mut stake = crate::state::STAKE_POSITIONS
.load(deps.storage, &info.sender)
.map_err(|_| StdError::generic_err("No active stake position"))?;
if env.block.time < stake.locked_until {
return Err(StdError::generic_err(format!(
"Stake is locked until {}", stake.locked_until
)));
}
if amount > stake.amount {
return Err(StdError::generic_err("Insufficient staked amount"));
}
stake.amount -= amount;
if stake.amount.is_zero() {
crate::state::STAKE_POSITIONS.remove(deps.storage, &info.sender);
} else {
crate::state::STAKE_POSITIONS.save(deps.storage, &info.sender, &stake)?;
}
let mut score = crate::state::AGENT_SCORES.may_load(deps.storage, &info.sender)?
.unwrap_or_else(|| crate::state::ReputationScore::new(info.sender.clone()));
score.stake_amount = stake.amount;
let params = GOV_PARAMS.load(deps.storage)?;
let breakdown = compute_score_breakdown(deps.storage, &score, &info.sender, &env.block.time, ¶ms)?;
score.composite_score = compute_composite_score(&breakdown, ¶ms);
crate::state::AGENT_SCORES.save(deps.storage, &info.sender, &score)?;
Ok(Response::new()
.add_attribute("action", "unstake")
.add_attribute("agent", info.sender.as_str())
.add_attribute("amount", &amount.to_string()))
}
fn handle_submit_evidence(
deps: DepsMut,
_env: Env,
_info: MessageInfo,
dispute_id: String,
evidence_hash: String,
) -> StdResult<Response> {
let mut dispute = crate::state::DISPUTES
.load(deps.storage, &dispute_id)
.map_err(|_| StdError::generic_err("Dispute not found"))?;
dispute.evidence_hashes.push(evidence_hash.clone());
crate::state::DISPUTES.save(deps.storage, &dispute_id, &dispute)?;
Ok(Response::new()
.add_attribute("action", "submit_evidence")
.add_attribute("dispute_id", &dispute_id)
.add_attribute("evidence_hash", &evidence_hash))
}
fn handle_resolve_dispute(
deps: DepsMut,
env: Env,
info: MessageInfo,
dispute_id: String,
resolution: String,
adjusted_rating: Option<u8>,
) -> StdResult<Response> {
let params = GOV_PARAMS.load(deps.storage)?;
if info.sender != params.admin {
return Err(StdError::generic_err("Only admin can resolve disputes"));
}
let mut dispute = crate::state::DISPUTES
.load(deps.storage, &dispute_id)
.map_err(|_| StdError::generic_err("Dispute not found"))?;
dispute.status = crate::state::DisputeStatus::Resolved;
dispute.resolved_at = Some(env.block.time);
dispute.resolution = Some(resolution.clone());
crate::state::DISPUTES.save(deps.storage, &dispute_id, &dispute)?;
if let Some(new_rating) = adjusted_rating {
let task = crate::state::TASK_RECORDS.load(deps.storage, &dispute.task_id)?;
let agent_addr = task.agent.clone();
let mut agent_score = crate::state::AGENT_SCORES.load(deps.storage, &agent_addr)?;
let mut ratings = crate::state::RATINGS.load(deps.storage, (&agent_addr, &dispute.task_id))?;
for r in ratings.iter_mut() {
if r.rater == dispute.initiator {
r.score = new_rating;
}
}
crate::state::RATINGS.save(deps.storage, (&agent_addr, &dispute.task_id), &ratings)?;
let mut total_score = 0u64;
let mut count = 0u64;
for r in ratings.iter() {
total_score += r.score as u64;
count += 1;
}
if count > 0 {
agent_score.avg_rating = Decimal::from_ratio(total_score, count);
}
let breakdown = compute_score_breakdown(deps.storage, &agent_score, &agent_addr, &env.block.time, ¶ms)?;
agent_score.composite_score = compute_composite_score(&breakdown, ¶ms);
crate::state::AGENT_SCORES.save(deps.storage, &agent_addr, &agent_score)?;
}
Ok(Response::new()
.add_attribute("action", "resolve_dispute")
.add_attribute("dispute_id", &dispute_id)
.add_attribute("resolution", &resolution))
}
fn handle_governance_override(
deps: DepsMut,
_env: Env,
info: MessageInfo,
agent: String,
new_score: Decimal,
_reason: String,
) -> StdResult<Response> {
let params = GOV_PARAMS.load(deps.storage)?;
if info.sender != params.admin {
return Err(StdError::generic_err("Only admin can override scores"));
}
let agent_addr = deps.api.addr_validate(&agent)?;
let mut score = crate::state::AGENT_SCORES.load(deps.storage, &agent_addr)?;
score.composite_score = new_score;
crate::state::AGENT_SCORES.save(deps.storage, &agent_addr, &score)?;
Ok(Response::new()
.add_attribute("action", "governance_override")
.add_attribute("agent", &agent)
.add_attribute("new_score", new_score.to_string()))
}
fn handle_update_params(
deps: DepsMut,
_env: Env,
info: MessageInfo,
params_msg: crate::msg::GovernanceParamsMsg,
) -> StdResult<Response> {
GOV_PARAMS.update(deps.storage, |mut params| {
if info.sender != params.admin {
return Err(StdError::generic_err("Only admin can update params"));
}
if let Some(w) = params_msg.success_rate_weight { params.success_rate_weight = w; }
if let Some(w) = params_msg.rating_weight { params.rating_weight = w; }
if let Some(w) = params_msg.volume_weight { params.volume_weight = w; }
if let Some(w) = params_msg.stake_weight { params.stake_weight = w; }
if let Some(w) = params_msg.recency_weight { params.recency_weight = w; }
if let Some(w) = params_msg.diversity_weight { params.diversity_weight = w; }
if let Some(v) = params_msg.min_rating { params.min_rating = v; }
if let Some(v) = params_msg.max_rating { params.max_rating = v; }
if let Some(v) = params_msg.dispute_deposit { params.dispute_deposit = v; }
if let Some(v) = params_msg.max_disputes_active { params.max_disputes_active = v; }
Ok(params)
})?;
Ok(Response::new().add_attribute("action", "update_params"))
}
fn query_reputation(deps: Deps, agent: String) -> StdResult<ReputationResponse> {
let agent_addr = deps.api.addr_validate(&agent)?;
let score = crate::state::AGENT_SCORES
.load(deps.storage, &agent_addr)
.map_err(|_| StdError::generic_err("Agent not found"))?;
let params = GOV_PARAMS.load(deps.storage)?;
let breakdown = compute_score_breakdown(
deps.storage, &score, &agent_addr, &cosmwasm_std::Timestamp::from_nanos(0), ¶ms,
)?;
Ok(ReputationResponse {
score: ReputationScoreResp {
agent: score.agent.clone(),
total_tasks: score.total_tasks,
successful_tasks: score.successful_tasks,
failed_tasks: score.failed_tasks,
success_rate: score.success_rate(),
avg_rating: score.avg_rating,
total_earned: score.total_earned,
stake_amount: score.stake_amount,
last_activity: score.last_activity,
composite_score: score.composite_score,
},
breakdown: ScoreBreakdown {
success_rate_component: breakdown.success_rate_component,
rating_component: breakdown.rating_component,
volume_component: breakdown.volume_component,
stake_component: breakdown.stake_component,
recency_component: breakdown.recency_component,
diversity_component: breakdown.diversity_component,
},
})
}
fn query_reputation_batch(deps: Deps, agents: Vec<String>) -> StdResult<Vec<ReputationResponse>> {
agents.into_iter().map(|agent| query_reputation(deps, agent)).collect()
}
fn query_top_agents(
deps: Deps,
_start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<AgentListResponse> {
let limit = limit.unwrap_or(DEFAULT_LIMIT).min(MAX_LIMIT) as usize;
let params = GOV_PARAMS.load(deps.storage)?;
let agents: Vec<ReputationScoreResp> = crate::state::AGENT_SCORES
.range(deps.storage, None, None, cosmwasm_std::Order::Descending)
.filter_map(|r| r.ok())
.take(limit)
.map(|(_, score)| {
ReputationScoreResp {
agent: score.agent,
total_tasks: score.total_tasks,
successful_tasks: score.successful_tasks,
failed_tasks: score.failed_tasks,
success_rate: score.success_rate(),
avg_rating: score.avg_rating,
total_earned: score.total_earned,
stake_amount: score.stake_amount,
last_activity: score.last_activity,
composite_score: score.composite_score,
}
})
.collect();
Ok(AgentListResponse { agents })
}
fn query_agent_tasks(
deps: Deps,
agent: String,
_start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<TaskListResponse> {
let agent_addr = deps.api.addr_validate(&agent)?;
let limit = limit.unwrap_or(DEFAULT_LIMIT).min(50) as usize;
let tasks: Vec<TaskRecordResp> = crate::state::AGENT_TASKS
.prefix(&agent_addr)
.range(deps.storage, None, None, cosmwasm_std::Order::Descending)
.filter_map(|r| r.ok())
.take(limit)
.filter_map(|(task_id, _)| {
crate::state::TASK_RECORDS.load(deps.storage, &task_id).ok().map(|t| TaskRecordResp {
task_id: t.task_id,
agent: t.agent,
requester: t.requester,
status: format!("{:?}", t.status),
rating: t.rating,
reward: t.reward,
timestamp: t.timestamp,
})
})
.collect();
Ok(TaskListResponse { tasks })
}
fn query_agent_ratings(
deps: Deps,
agent: String,
_start_after: Option<String>,
limit: Option<u32>,
) -> StdResult<RatingListResponse> {
let agent_addr = deps.api.addr_validate(&agent)?;
let limit = limit.unwrap_or(DEFAULT_LIMIT).min(50) as usize;
let mut ratings = Vec::new();
let mut count = 0;
for result in crate::state::RATINGS.prefix(&agent_addr)
.range(deps.storage, None, None, cosmwasm_std::Order::Descending)
{
if count >= limit { break; }
if let Ok((_, rating_vec)) = result {
for r in rating_vec {
if count >= limit { break; }
ratings.push(RatingResp {
rater: r.rater,
score: r.score,
comment_hash: r.comment_hash,
timestamp: r.timestamp,
});
count += 1;
}
}
}
Ok(RatingListResponse { ratings })
}
fn query_dispute(deps: Deps, dispute_id: String) -> StdResult<DisputeResponse> {
let dispute = crate::state::DISPUTES
.load(deps.storage, &dispute_id)
.map_err(|_| StdError::generic_err("Dispute not found"))?;
Ok(DisputeResponse {
dispute: DisputeResp {
dispute_id: dispute.dispute_id,
task_id: dispute.task_id,
initiator: dispute.initiator,
respondent: dispute.respondent,
dispute_type: format!("{:?}", dispute.dispute_type),
status: format!("{:?}", dispute.status),
evidence_hashes: dispute.evidence_hashes,
created_at: dispute.created_at,
resolved_at: dispute.resolved_at,
resolution: dispute.resolution,
},
})
}
fn query_stake(deps: Deps, agent: String) -> StdResult<StakeResponse> {
let agent_addr = deps.api.addr_validate(&agent)?;
let stake = crate::state::STAKE_POSITIONS
.load(deps.storage, &agent_addr)
.map_err(|_| StdError::generic_err("No active stake"))?;
Ok(StakeResponse {
agent: stake.agent,
amount: stake.amount,
locked_until: stake.locked_until,
staked_at: stake.staked_at,
auto_renew: stake.auto_renew,
})
}
fn query_params(deps: Deps) -> StdResult<ParamsResponse> {
let params = GOV_PARAMS.load(deps.storage)?;
Ok(ParamsResponse { params })
}
2.6 评分算法实现
// contracts/reputation-core/src/scoring.rs
use cosmwasm_std::{Addr, Decimal, StdResult, Storage, Timestamp};
use crate::state::{ReputationScore, GovernanceParams, COLLABORATORS, AGENT_SCORES, GOV_PARAMS};
const MAX_TASKS: u128 = 1_000_000;
const MAX_STAKE: u128 = 1_000_000_000_000;
const STALE_DAYS: u64 = 365;
const SECONDS_PER_DAY: u64 = 86_400;
#[derive(Clone, Debug, Default)]
pub struct ScoreBreakdown {
pub success_rate_component: Decimal,
pub rating_component: Decimal,
pub volume_component: Decimal,
pub stake_component: Decimal,
pub recency_component: Decimal,
pub diversity_component: Decimal,
}
pub fn compute_composite_score(
breakdown: &ScoreBreakdown,
params: &GovernanceParams,
) -> Decimal {
let raw = breakdown.success_rate_component * params.success_rate_weight
+ breakdown.rating_component * params.rating_weight
+ breakdown.volume_component * params.volume_weight
+ breakdown.stake_component * params.stake_weight
+ breakdown.recency_component * params.recency_weight
+ breakdown.diversity_component * params.diversity_weight;
raw * Decimal::from_ratio(100u128, 1u128)
}
pub fn compute_score_breakdown(
storage: &dyn Storage,
score: &ReputationScore,
agent: &Addr,
current_time: &Timestamp,
params: &GovernanceParams,
) -> StdResult<ScoreBreakdown> {
Ok(ScoreBreakdown {
success_rate_component: compute_success_rate_component(score),
rating_component: compute_rating_component(score, params),
volume_component: compute_volume_component(score),
stake_component: compute_stake_component(score),
recency_component: compute_recency_component(score, current_time),
diversity_component: compute_diversity_component(storage, agent)?,
})
}
fn compute_success_rate_component(score: &ReputationScore) -> Decimal {
if score.total_tasks.is_zero() {
return Decimal::zero();
}
Decimal::from_ratio(score.successful_tasks, score.total_tasks)
}
fn compute_rating_component(score: &ReputationScore, params: &GovernanceParams) -> Decimal {
let range = (params.max_rating - params.min_rating) as u128;
if range == 0 {
return Decimal::one();
}
let raw = score.avg_rating - Decimal::from_atomics(params.min_rating as u128, 0).unwrap();
raw / Decimal::from_atomics(range, 0).unwrap()
}
fn compute_volume_component(score: &ReputationScore) -> Decimal {
let tasks = score.total_tasks.u128();
let log_tasks = (1u128 + tasks) as f64;
let log_max = (1u128 + MAX_TASKS) as f64;
let ratio = log_tasks.log2() / log_max.log2();
Decimal::from_atomics((ratio * 1_000_000_000_000_000_000u128) as u128, 18).unwrap_or(Decimal::zero())
}
fn compute_stake_component(score: &ReputationScore) -> Decimal {
let stake = score.stake_amount.u128();
let ratio = (stake as f64) / (MAX_STAKE as f64);
let boosted = (ratio * 5.0).min(1.0);
Decimal::from_atomics((boosted * 1_000_000_000_000_000_000u128) as u128, 18).unwrap_or(Decimal::zero())
}
fn compute_recency_component(score: &ReputationScore, current_time: &Timestamp) -> Decimal {
if score.last_activity.nanos() == 0 {
return Decimal::zero();
}
let elapsed = current_time.minus(score.last_activity);
let days_since = elapsed.nanos() / (SECONDS_PER_DAY * 1_000_000_000);
if days_since >= STALE_DAYS {
return Decimal::zero();
}
let decay = (STALE_DAYS - days_since) as f64 / STALE_DAYS as f64;
Decimal::from_atomics((decay * 1_000_000_000_000_000_000u128) as u128, 18).unwrap_or(Decimal::zero())
}
fn compute_diversity_component(storage: &dyn Storage, agent: &Addr) -> StdResult<Decimal> {
let min_collaborators = 10u128;
let count = COLLABORATORS
.prefix(agent)
.range(storage, None, None, cosmwasm_std::Order::Ascending)
.count() as u128;
if count >= min_collaborators {
return Ok(Decimal::one());
}
Ok(Decimal::from_ratio(count, min_collaborators))
}
pub fn recalculate_all_scores(
storage: &mut dyn Storage,
current_time: &Timestamp,
) -> StdResult<()> {
let params = GOV_PARAMS.load(storage)?;
let agents: Vec<Addr> = AGENT_SCORES
.keys(storage, None, None, cosmwasm_std::Order::Ascending)
.filter_map(|r| r.ok())
.collect();
for agent in agents {
let mut score = AGENT_SCORES.load(storage, &agent)?;
let breakdown = compute_score_breakdown(storage, &score, &agent, current_time, ¶ms)?;
score.composite_score = compute_composite_score(&breakdown, ¶ms);
AGENT_SCORES.save(storage, &agent, &score)?;
}
Ok(())
}
2.7 抵押逻辑实现
// contracts/reputation-core/src/stake.rs
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response, StdError, StdResult, Uint128};
use crate::state::{StakePosition, AGENT_SCORES, STAKE_POSITIONS, GOV_PARAMS};
use crate::scoring::{compute_score_breakdown, compute_composite_score};
const MIN_STAKE: Uint128 = Uint128::new(1_000_000);
const MAX_LOCK_SECS: u64 = 365 * 86_400;
const MIN_LOCK_SECS: u64 = 7 * 86_400;
pub fn handle_stake(
deps: DepsMut,
env: Env,
info: MessageInfo,
amount: Uint128,
lock_duration_secs: u64,
) -> StdResult<Response> {
if amount < MIN_STAKE {
return Err(StdError::generic_err(format!("Minimum stake is {}", MIN_STAKE)));
}
if lock_duration_secs < MIN_LOCK_SECS || lock_duration_secs > MAX_LOCK_SECS {
return Err(StdError::generic_err(format!(
"Lock duration must be between {} and {} seconds", MIN_LOCK_SECS, MAX_LOCK_SECS
)));
}
let sent = info.funds.iter()
.find(|c| c.denom == "msg")
.map(|c| c.amount)
.unwrap_or(Uint128::zero());
if sent < amount {
return Err(StdError::generic_err(format!(
"Insufficient funds: sent {} msg, needed {}", sent, amount
)));
}
let locked_until = env.block.time.plus_seconds(lock_duration_secs);
let existing = STAKE_POSITIONS.may_load(deps.storage, &info.sender)?;
let new_stake = if let Some(mut stake) = existing {
if locked_until > stake.locked_until {
stake.locked_until = locked_until;
}
stake.amount += amount;
stake.auto_renew = lock_duration_secs > 180 * 86_400;
stake
} else {
StakePosition {
agent: info.sender.clone(),
amount,
locked_until,
staked_at: env.block.time,
auto_renew: lock_duration_secs > 180 * 86_400,
}
};
STAKE_POSITIONS.save(deps.storage, &info.sender, &new_stake)?;
let mut score = AGENT_SCORES
.may_load(deps.storage, &info.sender)?
.unwrap_or_else(|| crate::state::ReputationScore::new(info.sender.clone()));
score.stake_amount = new_stake.amount;
let params = GOV_PARAMS.load(deps.storage)?;
let breakdown = compute_score_breakdown(deps.storage, &score, &info.sender, &env.block.time, ¶ms)?;
score.composite_score = compute_composite_score(&breakdown, ¶ms);
AGENT_SCORES.save(deps.storage, &info.sender, &score)?;
Ok(Response::new()
.add_attribute("action", "stake")
.add_attribute("agent", info.sender.as_str())
.add_attribute("amount", &amount.to_string())
.add_attribute("locked_until", &locked_until.to_string())
.add_attribute("auto_renew", &new_stake.auto_renew.to_string()))
}
2.8 争议处理实现
// contracts/reputation-core/src/dispute.rs
use cosmwasm_std::{DepsMut, Env, MessageInfo, Response, StdError, StdResult};
use crate::msg::DisputeTypeMsg;
use crate::state::{
Dispute, DisputeStatus, DisputeType, TASK_RECORDS, DISPUTES, AGENT_DISPUTES, GOV_PARAMS,
};
use uuid::Uuid;
pub fn handle_open_dispute(
deps: DepsMut,
env: Env,
_info: MessageInfo,
task_id: String,
respondent: String,
dispute_type: DisputeTypeMsg,
evidence_hashes: Vec<String>,
) -> StdResult<Response> {
let params = GOV_PARAMS.load(deps.storage)?;
let respondent_addr = deps.api.addr_validate(&respondent)?;
let _task = TASK_RECORDS
.load(deps.storage, &task_id)
.map_err(|_| StdError::generic_err("Task not found"))?;
let active_count = DISPUTES
.range(deps.storage, None, None, cosmwasm_std::Order::Ascending)
.filter_map(|r| r.ok())
.filter(|(_, d)| d.respondent == respondent_addr && d.status == DisputeStatus::Open)
.count() as u32;
if active_count >= params.max_disputes_active {
return Err(StdError::generic_err(format!(
"Agent already has maximum active disputes ({})", params.max_disputes_active
)));
}
let dispute_id = Uuid::new_v4().to_string();
let d_type = match dispute_type {
DisputeTypeMsg::RatingDisagreement => DisputeType::RatingDisagreement,
DisputeTypeMsg::FraudAllegation => DisputeType::FraudAllegation,
DisputeTypeMsg::NonDelivery => DisputeType::NonDelivery,
DisputeTypeMsg::Plagiarism => DisputeType::Plagiarism,
DisputeTypeMsg::Other(s) => DisputeType::Other(s),
};
let dispute = Dispute {
dispute_id: dispute_id.clone(),
task_id: task_id.clone(),
initiator: _task.requester,
respondent: respondent_addr.clone(),
dispute_type: d_type,
status: DisputeStatus::Open,
evidence_hashes,
arbitrator: None,
created_at: env.block.time,
resolved_at: None,
resolution: None,
};
DISPUTES.save(deps.storage, &dispute_id, &dispute)?;
AGENT_DISPUTES.save(deps.storage, (&respondent_addr, &dispute_id), &true)?;
Ok(Response::new()
.add_attribute("action", "open_dispute")
.add_attribute("dispute_id", &dispute_id)
.add_attribute("task_id", &task_id)
.add_attribute("respondent", &respondent))
}
2.9 Helper 函数
// contracts/reputation-core/src/helpers.rs
use cosmwasm_std::{Addr, Decimal, StdResult, Storage, Timestamp};
use crate::state::{AGENT_SCORES, GOV_PARAMS};
use crate::scoring::{compute_score_breakdown, compute_composite_score};
pub fn meets_min_reputation(storage: &dyn Storage, agent: &Addr, min_score: Decimal) -> StdResult<bool> {
let score = AGENT_SCORES.may_load(storage, agent)?
.unwrap_or_else(|| crate::state::ReputationScore::new(agent.clone()));
Ok(score.composite_score >= min_score)
}
pub fn get_reputation_tier(score: Decimal) -> &'static str {
let s = score.u128();
match s {
0..=20 => "Low",
21..=50 => "Basic",
51..=75 => "Good",
76..=90 => "High",
_ => "Elite",
}
}
pub fn compute_stake_discount(score: Decimal) -> Decimal {
let discount = score * Decimal::from_ratio(5u128, 1000u128);
discount.min(Decimal::percent(50))
}
pub fn compute_priority_multiplier(score: Decimal) -> Decimal {
Decimal::one() + score * Decimal::from_ratio(1u128, 100u128)
}
pub fn refresh_score(storage: &mut dyn Storage, agent: &Addr, current_time: &Timestamp) -> StdResult<()> {
let params = GOV_PARAMS.load(storage)?;
let mut score = AGENT_SCORES.load(storage, agent)
.unwrap_or_else(|_| crate::state::ReputationScore::new(agent.clone()));
let breakdown = compute_score_breakdown(storage, &score, agent, current_time, ¶ms)?;
score.composite_score = compute_composite_score(&breakdown, ¶ms);
AGENT_SCORES.save(storage, agent, &score)?;
Ok(())
}
3. 评分算法
3.1 算法总览
评分算法的核心目标是为每个 Agent 生成一个归一化到 [0, 100] 的综合信誉分数。
算法分为六个维度,每个维度独立计算后加权求和。
3.2 权重配置
默认权重如下,可通过治理投票调整:
| 维度 | 默认权重 | 调整范围 | 说明 |
|---|---|---|---|
| 成功率 | 30% | 20-40% | 任务完成比例 |
| 平均评分 | 25% | 15-35% | 发布者评价转化 |
| 任务量 | 15% | 10-25% | 经验丰富度(对数缩放) |
| 抵押加成 | 10% | 5-20% | 经济承诺信号 |
| 时间衰减 | 10% | 5-15% | 近期活动重要性 |
| 多样性 | 10% | 5-15% | 合作广度 |
3.3 Python 参考实现
#!/usr/bin/env python3
# reputation_scoring.py - MSG Chain AI Agent 信誉评分算法参考实现
import math
import json
from dataclasses import dataclass, field
from typing import Optional
from enum import Enum
from datetime import datetime, timedelta
class TaskStatus(Enum):
ASSIGNED = "assigned"
IN_PROGRESS = "in_progress"
COMPLETED = "completed"
FAILED = "failed"
DISPUTED = "disputed"
RESOLVED = "resolved"
@dataclass
class ReputationScore:
agent: str
total_tasks: int = 0
successful_tasks: int = 0
failed_tasks: int = 0
avg_rating: float = 1.0
total_earned: int = 0
stake_amount: int = 0
last_activity: Optional[datetime] = None
composite_score: float = 0.0
@property
def success_rate(self) -> float:
if self.total_tasks == 0:
return 0.0
return self.successful_tasks / self.total_tasks
@dataclass
class GovernanceParams:
success_rate_weight: float = 0.30
rating_weight: float = 0.25
volume_weight: float = 0.15
stake_weight: float = 0.10
recency_weight: float = 0.10
diversity_weight: float = 0.10
min_rating: int = 1
max_rating: int = 5
max_tasks: int = 1_000_000
max_stake: int = 1_000_000_000_000
stale_days: int = 365
min_collaborators: int = 10
@dataclass
class ScoreBreakdown:
success_rate_component: float = 0.0
rating_component: float = 0.0
volume_component: float = 0.0
stake_component: float = 0.0
recency_component: float = 0.0
diversity_component: float = 0.0
class ReputationScoringEngine:
def __init__(self, params: Optional[GovernanceParams] = None):
self.params = params or GovernanceParams()
self.agents: dict[str, ReputationScore] = {}
self.collaborators: dict[str, set[str]] = {}
self.tasks: dict[str, dict] = {}
self.ratings: dict[str, list[dict]] = {}
self.disputes: dict[str, dict] = {}
def compute_composite_score(self, breakdown: ScoreBreakdown) -> float:
raw = (
breakdown.success_rate_component * self.params.success_rate_weight
+ breakdown.rating_component * self.params.rating_weight
+ breakdown.volume_component * self.params.volume_weight
+ breakdown.stake_component * self.params.stake_weight
+ breakdown.recency_component * self.params.recency_weight
+ breakdown.diversity_component * self.params.diversity_weight
)
return raw * 100.0
def compute_breakdown(
self, score: ReputationScore, agent: str, current_time: Optional[datetime] = None,
) -> ScoreBreakdown:
if current_time is None:
current_time = datetime.utcnow()
return ScoreBreakdown(
success_rate_component=self._success_rate_component(score),
rating_component=self._rating_component(score),
volume_component=self._volume_component(score),
stake_component=self._stake_component(score),
recency_component=self._recency_component(score, current_time),
diversity_component=self._diversity_component(agent),
)
def _success_rate_component(self, score: ReputationScore) -> float:
if score.total_tasks == 0:
return 0.0
return score.successful_tasks / score.total_tasks
def _rating_component(self, score: ReputationScore) -> float:
range_val = self.params.max_rating - self.params.min_rating
if range_val == 0:
return 1.0
return (score.avg_rating - self.params.min_rating) / range_val
def _volume_component(self, score: ReputationScore) -> float:
log_tasks = math.log2(1 + score.total_tasks)
log_max = math.log2(1 + self.params.max_tasks)
return log_tasks / log_max
def _stake_component(self, score: ReputationScore) -> float:
ratio = score.stake_amount / self.params.max_stake
boosted = min(ratio * 5.0, 1.0)
return boosted
def _recency_component(self, score: ReputationScore, current_time: datetime) -> float:
if score.last_activity is None:
return 0.0
days_since = (current_time - score.last_activity).days
if days_since >= self.params.stale_days:
return 0.0
return (self.params.stale_days - days_since) / self.params.stale_days
def _diversity_component(self, agent: str) -> float:
collaborators = len(self.collaborators.get(agent, set()))
if collaborators >= self.params.min_collaborators:
return 1.0
return collaborators / self.params.min_collaborators
def compute_sybil_resistance(self, agent: str) -> float:
score = self.agents.get(agent)
if score is None:
return 0.0
factors = []
stake_factor = min(score.stake_amount / (1000 * 10**6), 1.0)
factors.append(stake_factor * 0.30)
task_factor = min(score.total_tasks / 5, 1.0)
factors.append(task_factor * 0.25)
collab_count = len(self.collaborators.get(agent, set()))
collab_factor = min(collab_count / 3, 1.0)
factors.append(collab_factor * 0.25)
agent_ratings = self.ratings.get(agent, [])
if len(agent_ratings) >= 3:
scores_list = [r["score"] for r in agent_ratings]
mean_s = sum(scores_list) / len(scores_list)
variance = sum((s - mean_s) ** 2 for s in scores_list) / len(scores_list)
var_factor = 0.5 if variance < 0.1 else 1.0
factors.append(var_factor * 0.20)
else:
factors.append(0.0)
return sum(factors)
def record_task(
self, task_id: str, agent: str, requester: str, status: TaskStatus,
reward: int, evidence_hash: Optional[str] = None,
) -> ReputationScore:
if agent not in self.agents:
self.agents[agent] = ReputationScore(agent=agent)
self.collaborators[agent] = set()
score = self.agents[agent]
score.total_tasks += 1
score.total_earned += reward
score.last_activity = datetime.utcnow()
if status == TaskStatus.COMPLETED:
score.successful_tasks += 1
elif status == TaskStatus.FAILED:
score.failed_tasks += 1
self.collaborators[agent].add(requester)
self.tasks[task_id] = {
"agent": agent, "requester": requester, "status": status.value,
"reward": reward, "evidence_hash": evidence_hash,
"timestamp": datetime.utcnow().isoformat(),
}
breakdown = self.compute_breakdown(score, agent)
score.composite_score = self.compute_composite_score(breakdown)
return score
def submit_rating(self, agent: str, task_id: str, score_val: int, comment_hash: str, rater: str) -> ReputationScore:
if score_val < self.params.min_rating or score_val > self.params.max_rating:
raise ValueError(f"Rating must be between {self.params.min_rating} and {self.params.max_rating}")
if agent not in self.agents:
raise ValueError(f"Agent {agent} not found")
if agent not in self.ratings:
self.ratings[agent] = []
self.ratings[agent].append({
"task_id": task_id, "rater": rater, "score": score_val,
"comment_hash": comment_hash, "timestamp": datetime.utcnow().isoformat(),
})
if task_id in self.tasks:
self.tasks[task_id]["rating"] = score_val
agent_score = self.agents[agent]
all_scores = [r["score"] for r in self.ratings[agent]]
agent_score.avg_rating = sum(all_scores) / len(all_scores)
breakdown = self.compute_breakdown(agent_score, agent)
agent_score.composite_score = self.compute_composite_score(breakdown)
return agent_score
def get_score(self, agent: str) -> Optional[ReputationScore]:
return self.agents.get(agent)
def get_top_agents(self, n: int = 10) -> list[ReputationScore]:
return sorted(self.agents.values(), key=lambda x: x.composite_score, reverse=True)[:n]
def get_breakdown(self, agent: str) -> dict:
score = self.agents.get(agent)
if score is None:
return {}
breakdown = self.compute_breakdown(score, agent)
return {
"composite_score": round(score.composite_score, 2),
"components": {
"success_rate": {"value": round(breakdown.success_rate_component, 4), "weight": self.params.success_rate_weight, "contribution": round(breakdown.success_rate_component * self.params.success_rate_weight, 4)},
"rating": {"value": round(breakdown.rating_component, 4), "weight": self.params.rating_weight, "contribution": round(breakdown.rating_component * self.params.rating_weight, 4)},
"volume": {"value": round(breakdown.volume_component, 4), "weight": self.params.volume_weight, "contribution": round(breakdown.volume_component * self.params.volume_weight, 4)},
"stake": {"value": round(breakdown.stake_component, 4), "weight": self.params.stake_weight, "contribution": round(breakdown.stake_component * self.params.stake_weight, 4)},
"recency": {"value": round(breakdown.recency_component, 4), "weight": self.params.recency_weight, "contribution": round(breakdown.recency_component * self.params.recency_weight, 4)},
"diversity": {"value": round(breakdown.diversity_component, 4), "weight": self.params.diversity_weight, "contribution": round(breakdown.diversity_component * self.params.diversity_weight, 4)},
},
"raw_metrics": {
"total_tasks": score.total_tasks,
"success_rate": round(score.success_rate, 4),
"avg_rating": round(score.avg_rating, 2),
"stake_amount": score.stake_amount,
"collaborators": len(self.collaborators.get(agent, set())),
},
}
def recalculate_all(self, current_time: Optional[datetime] = None):
if current_time is None:
current_time = datetime.utcnow()
for agent, score in self.agents.items():
breakdown = self.compute_breakdown(score, agent, current_time)
score.composite_score = self.compute_composite_score(breakdown)
def simulate(self):
import random
agents = [f"msg1agent{i}" for i in range(1, 21)]
requesters = [f"msg1requester{i}" for i in range(1, 11)]
for agent in agents:
self.agents[agent] = ReputationScore(agent=agent)
self.collaborators[agent] = set()
num_tasks = random.randint(5, 50)
for t in range(num_tasks):
task_id = f"task-{agent}-{t}"
requester = random.choice(requesters)
status = TaskStatus.COMPLETED if random.random() < 0.85 else TaskStatus.FAILED
reward = random.randint(100, 10000)
self.record_task(task_id, agent, requester, status, reward)
if status == TaskStatus.COMPLETED and random.random() < 0.7:
rating_score = random.choices([1, 2, 3, 4, 5], weights=[1, 2, 10, 30, 50])[0]
self.submit_rating(agent, task_id, rating_score, f"ipfs://QmComment{random.randint(1, 1000)}", requester)
if random.random() < 0.4:
stake = random.randint(1000, 50000) * 10**6
self.agents[agent].stake_amount = stake
days_ago = random.randint(0, 200)
self.agents[agent].last_activity = datetime.utcnow() - timedelta(days=days_ago)
self.recalculate_all()
def print_leaderboard(engine: ReputationScoringEngine, top_n: int = 10):
print(f"\n{'='*110}")
print(f"{'Rank':<6} {'Agent':<30} {'Score':<8} {'Tasks':<8} {'Success%':<10} {'Rating':<8} {'Stake':<10} {'SybilRes':<10}")
print(f"{'='*110}")
for i, score in enumerate(engine.get_top_agents(top_n), 1):
sybil = engine.compute_sybil_resistance(score.agent)
print(f"{i:<6} {score.agent:<30} {score.composite_score:<8.2f} {score.total_tasks:<8} {score.success_rate:<10.2%} {score.avg_rating:<8.2f} {score.stake_amount // 10**6:<10} {sybil:<10.3f}")
def scenario_analysis():
scenarios = [
("新 Agent,无历史", ReputationScore(agent="new", total_tasks=0, avg_rating=1.0, last_activity=datetime.utcnow())),
("普通 Agent,50 任务 90% 成功率", ReputationScore(agent="normal", total_tasks=50, successful_tasks=45, avg_rating=4.2, total_earned=50000, last_activity=datetime.utcnow())),
("优秀 Agent,500 任务 98%", ReputationScore(agent="elite", total_tasks=500, successful_tasks=490, avg_rating=4.8, total_earned=500000, stake_amount=10000 * 10**6, last_activity=datetime.utcnow())),
("衰退 Agent,300 天前活动", ReputationScore(agent="stale", total_tasks=200, successful_tasks=180, avg_rating=4.0, total_earned=200000, last_activity=datetime.utcnow() - timedelta(days=300))),
("高抵押低表现", ReputationScore(agent="stake-heavy", total_tasks=5, successful_tasks=2, avg_rating=2.0, stake_amount=50000 * 10**6, last_activity=datetime.utcnow())),
("Sybil 怀疑者", ReputationScore(agent="sybil", total_tasks=100, successful_tasks=100, avg_rating=5.0, last_activity=datetime.utcnow())),
]
print(f"\n{'Scenario':<35} {'Score':<10} {'Breakdown':<60}")
print("=" * 105)
engine = ReputationScoringEngine()
for name, score in scenarios:
engine.agents[score.agent] = score
if score.agent == "sybil":
engine.collaborators[score.agent] = {"single_requester"}
else:
engine.collaborators[score.agent] = {f"requester{i}" for i in range(1 if score.agent == "new" else 8)}
breakdown = engine.compute_breakdown(score, score.agent)
composite = engine.compute_composite_score(breakdown)
b = breakdown
b_str = f"S:{b.success_rate_component:.2f} R:{b.rating_component:.2f} V:{b.volume_component:.2f} K:{b.stake_component:.2f} T:{b.recency_component:.2f} D:{b.diversity_component:.2f}"
print(f"{name:<35} {composite:<10.2f} {b_str:<60}")
if __name__ == "__main__":
print("=" * 100)
print(" MSG Chain AI Agent 信誉评分系统 - 参考实现")
print("=" * 100)
engine = ReputationScoringEngine()
print("\n[1/3] 生成模拟数据...")
engine.simulate()
print(f" 已生成 {len(engine.agents)} 个 Agent 的信誉数据")
print("\n[2/3] 信誉排行榜:")
print_leaderboard(engine, 10)
print("\n[3/3] 场景分析:")
scenario_analysis()
top = engine.get_top_agents(1)[0]
print(f"\n最佳 Agent 详细分解 ({top.agent}):")
print(json.dumps(engine.get_breakdown(top.agent), indent=2, ensure_ascii=False))
print("\n评分算法示例完成")
3.4 评分计算示例
场景:普通 Agent 评分分解
输入数据:
- total_tasks: 50
- successful_tasks: 45
- avg_rating: 4.2
- stake_amount: 10,000 MSG
- last_activity: 30 天前
- collaborators: 8
计算过程:
- success_rate_component = 45/50 = 0.9000
- rating_component = (4.2 - 1) / 4 = 0.8000
- volume_component = log2(51) / log2(1,000,001) ≈ 0.5017
- stake_component = min(10,000,000,000 / 1,000,000,000,000 * 5, 1) = min(0.05, 1) = 0.0500
- recency_component = (365-30)/365 = 0.9178
- diversity_component = 8/10 = 0.8000
加权计算:
raw = 0.9000x0.30 + 0.8000x0.25 + 0.5017x0.15 + 0.0500x0.10 + 0.9178x0.10 + 0.8000x0.10
= 0.2700 + 0.2000 + 0.0753 + 0.0050 + 0.0918 + 0.0800
= 0.7221
composite_score = 0.7221 x 100 = 72.21 -> Good 等级
4. 信誉使用场景
4.1 Agent 发现与排序
// packages/client/src/reputation/discovery.ts
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
interface ReputationScore {
agent: string;
total_tasks: string;
successful_tasks: string;
failed_tasks: string;
success_rate: string;
avg_rating: string;
total_earned: string;
stake_amount: string;
last_activity: string;
composite_score: string;
}
interface ScoreBreakdown {
success_rate_component: string;
rating_component: string;
volume_component: string;
stake_component: string;
recency_component: string;
diversity_component: string;
}
interface ReputationResponse {
score: ReputationScore;
breakdown: ScoreBreakdown;
}
interface AgentListResponse {
agents: ReputationScore[];
}
export class ReputationClient {
private client: CosmWasmClient;
private contractAddress: string;
constructor(client: CosmWasmClient, contractAddress: string) {
this.client = client;
this.contractAddress = contractAddress;
}
async getReputation(agent: string): Promise<ReputationResponse> {
return this.client.queryContractSmart(this.contractAddress, {
get_reputation: { agent },
});
}
async getReputationBatch(agents: string[]): Promise<ReputationResponse[]> {
return this.client.queryContractSmart(this.contractAddress, {
get_reputation_batch: { agents },
});
}
async getTopAgents(startAfter?: string, limit: number = 20): Promise<AgentListResponse> {
return this.client.queryContractSmart(this.contractAddress, {
list_top_agents: { start_after: startAfter, limit },
});
}
async filterByTier(minScore: number, limit: number = 50): Promise<ReputationScore[]> {
const all = await this.getTopAgents(undefined, limit);
return all.agents.filter((a) => parseFloat(a.composite_score) >= minScore);
}
static getTier(score: number): string {
if (score <= 20) return "Low";
if (score <= 50) return "Basic";
if (score <= 75) return "Good";
if (score <= 90) return "High";
return "Elite";
}
static getTierColor(tier: string): string {
const colors: Record<string, string> = {
Low: "#ef4444", Basic: "#f97316", Good: "#22c55e",
High: "#06b6d4", Elite: "#8b5cf6",
};
return colors[tier] ?? "#6b7280";
}
static getMinScoreForComplexity(complexity: "low" | "medium" | "high" | "critical"): number {
const thresholds: Record<string, number> = { low: 0, medium: 30, high: 55, critical: 75 };
return thresholds[complexity];
}
}
4.2 任务匹配与信誉过滤器
// packages/client/src/reputation/task-matching.ts
import { ReputationClient } from "./discovery";
interface TaskRequirements {
taskId: string;
minReputation: number;
minSuccessRate: number;
minCompletedTasks: number;
}
interface MatchResult {
taskId: string;
agent: string;
score: number;
meetsRequirements: boolean;
matchReason: string;
}
export class TaskMatchingEngine {
private reputationClient: ReputationClient;
constructor(reputationClient: ReputationClient) {
this.reputationClient = reputationClient;
}
async matchAgents(requirements: TaskRequirements, topN: number = 10): Promise<MatchResult[]> {
const topAgents = await this.reputationClient.getTopAgents(undefined, 100);
const results: MatchResult[] = [];
for (const agent of topAgents.agents) {
const score = parseFloat(agent.composite_score);
const successRate = parseFloat(agent.success_rate);
const totalTasks = parseInt(agent.total_tasks);
const issues: string[] = [];
if (score < requirements.minReputation) {
issues.push(`Score ${score.toFixed(1)} < min ${requirements.minReputation}`);
}
if (successRate < requirements.minSuccessRate) {
issues.push(`Rate ${(successRate * 100).toFixed(1)}% < ${(requirements.minSuccessRate * 100).toFixed(1)}%`);
}
if (totalTasks < requirements.minCompletedTasks) {
issues.push(`Tasks ${totalTasks} < min ${requirements.minCompletedTasks}`);
}
results.push({
taskId: requirements.taskId,
agent: agent.agent,
score,
meetsRequirements: issues.length === 0,
matchReason: issues.length === 0 ? "OK" : issues.join("; "),
});
}
results.sort((a, b) => {
if (a.meetsRequirements !== b.meetsRequirements) return a.meetsRequirements ? -1 : 1;
return b.score - a.score;
});
return results.slice(0, topN);
}
}
4.3 基于信誉的抵押减免
// packages/client/src/reputation/collateral.ts
import { ReputationClient } from "./discovery";
export class CollateralManager {
private reputationClient: ReputationClient;
private readonly BASE_COLLATERAL_RATIO = 0.20;
private readonly MAX_DISCOUNT = 0.50;
private readonly DISCOUNT_PER_POINT = 0.005;
constructor(reputationClient: ReputationClient) {
this.reputationClient = reputationClient;
}
async computeRequiredCollateral(agent: string, taskValue: string): Promise<{
baseCollateral: string; discountedCollateral: string;
discountPercent: number; tier: string;
}> {
const rep = await this.reputationClient.getReputation(agent);
const score = parseFloat(rep.score.composite_score);
const value = parseFloat(taskValue);
const discount = Math.min(score * this.DISCOUNT_PER_POINT, this.MAX_DISCOUNT);
const discountPercent = discount * 100;
const baseCollateral = value * this.BASE_COLLATERAL_RATIO;
const discountedCollateral = baseCollateral * (1 - discount);
return {
baseCollateral: baseCollateral.toFixed(0),
discountedCollateral: discountedCollateral.toFixed(0),
discountPercent,
tier: ReputationClient.getTier(score),
};
}
async computeCreditLimit(agent: string): Promise<{ creditLimit: string; score: number }> {
const rep = await this.reputationClient.getReputation(agent);
const score = parseFloat(rep.score.composite_score);
const stakeAmount = parseFloat(rep.score.stake_amount);
const creditLimit = (stakeAmount * (score / 100) * 10).toFixed(0);
return { creditLimit, score };
}
}
4.4 高信誉 Agent 优先队列
// packages/client/src/reputation/priority-queue.ts
import { ReputationClient } from "./discovery";
interface AgentPriority {
agent: string;
priorityScore: number;
tier: string;
maxConcurrent: number;
}
export class PriorityQueue {
private reputationClient: ReputationClient;
private readonly BASE_PRIORITY = 1.0;
private readonly PRIORITY_PER_POINT = 0.01;
private readonly BASE_MAX_CONCURRENT = 3;
private readonly EXTRA_SLOTS: Record<string, number> = {
Low: 0, Basic: 1, Good: 2, High: 4, Elite: 8,
};
constructor(reputationClient: ReputationClient) {
this.reputationClient = reputationClient;
}
async getAgentPriority(agent: string): Promise<AgentPriority> {
const rep = await this.reputationClient.getReputation(agent);
const score = parseFloat(rep.score.composite_score);
const tier = ReputationClient.getTier(score);
return {
agent,
priorityScore: this.BASE_PRIORITY + score * this.PRIORITY_PER_POINT,
tier,
maxConcurrent: this.BASE_MAX_CONCURRENT + (this.EXTRA_SLOTS[tier] ?? 0),
};
}
async canAcceptTask(agent: string): Promise<{ canAccept: boolean; currentLoad: number; maxLoad: number; reason: string }> {
const p = await this.getAgentPriority(agent);
const currentTasks = 0;
return {
canAccept: currentTasks < p.maxConcurrent,
currentLoad: currentTasks,
maxLoad: p.maxConcurrent,
reason: currentTasks < p.maxConcurrent ? "Available" : `At capacity (${currentTasks}/${p.maxConcurrent})`,
};
}
}
4.5 完整集成示例
// packages/client/examples/reputation-flow.ts
import { CosmWasmClient, SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing";
import { ReputationClient } from "../src/reputation/discovery";
import { TaskMatchingEngine } from "../src/reputation/task-matching";
import { CollateralManager } from "../src/reputation/collateral";
import { PriorityQueue } from "../src/reputation/priority-queue";
async function main() {
const rpcUrl = "https://rpc.msg-chain-1.msg.network";
const client = await CosmWasmClient.connect(rpcUrl);
const wallet = await DirectSecp256k1HdWallet.fromMnemonic("your mnemonic...", { prefix: "msg" });
const signer = await SigningCosmWasmClient.connectWithSigner(rpcUrl, wallet);
const reputationContract = "msg1reputation...";
const repClient = new ReputationClient(client, reputationContract);
const matcher = new TaskMatchingEngine(repClient);
const collateralMgr = new CollateralManager(repClient);
const priorityQueue = new PriorityQueue(repClient);
console.log("=== Top Agents ===");
const topAgents = await repClient.getTopAgents(undefined, 10);
for (const agent of topAgents.agents) {
const tier = ReputationClient.getTier(parseFloat(agent.composite_score));
console.log(` ${agent.agent}: ${agent.composite_score} (${tier}) - ${agent.successful_tasks}/${agent.total_tasks} tasks`);
}
console.log("\n=== Task Matching ===");
const matches = await matcher.matchAgents({ taskId: "task-001", minReputation: 60, minSuccessRate: 0.9, minCompletedTasks: 20 });
for (const m of matches.slice(0, 5)) {
console.log(` ${m.agent}: score=${m.score.toFixed(1)}, meets=${m.meetsRequirements}`);
}
console.log("\n=== Collateral Calculation ===");
for (const agent of topAgents.agents.slice(0, 3)) {
const col = await collateralMgr.computeRequiredCollateral(agent.agent, "1000000000");
console.log(` ${agent.agent}: discounted=${col.discountedCollateral}, discount=${col.discountPercent.toFixed(1)}%`);
}
console.log("\n=== Submit Task Result ===");
const msg = {
record_task_result: {
task_id: "task-live-001",
agent: topAgents.agents[0].agent,
requester: "msg1requester...",
status: "completed",
reward: "500000000",
evidence_hash: "ipfs://QmEvidence",
},
};
const result = await signer.execute("msg1sender...", reputationContract, [msg], "auto", "Record task");
console.log(` Tx hash: ${result.transactionHash}`);
console.log("\nReputation flow complete!");
}
main().catch(console.error);
5. 争议与申诉
5.1 争议系统设计
争议流程:
- 发布者提交争议(链上)
- 双方提交证据(链上哈希)
- 仲裁员审查(链下 + 链上裁决)
- 执行裁决(评分调整/罚没)
争议类型:
| 类型 | 说明 | 典型证据 |
|---|---|---|
| RatingDisagreement | 对评分有异议 | 交付截图、日志 |
| FraudAllegation | 指控欺诈 | 交易记录、通信记录 |
| NonDelivery | 未交付成果 | 空的任务输出哈希 |
| Plagiarism | 抄袭 | 原创性报告 |
| Other | 其他 | 自定义 |
5.2 争议扩展逻辑
// contracts/reputation-core/src/dispute_ext.rs
use cosmwasm_std::{Addr, Decimal, DepsMut, Env, MessageInfo, Response, StdError, StdResult};
use crate::state::{DisputeStatus, DISPUTES, AGENT_DISPUTES, GOV_PARAMS, AGENT_SCORES};
use crate::scoring::{compute_score_breakdown, compute_composite_score};
pub struct DisputeSummary {
pub total: u32,
pub open: u32,
pub resolved_in_favor: u32,
pub dismissed: u32,
pub dispute_ratio: Decimal,
}
pub fn get_agent_dispute_summary(deps: &DepsMut, agent: &Addr) -> StdResult<DisputeSummary> {
let mut total = 0u32;
let mut resolved_in_favor = 0u32;
let mut dismissed = 0u32;
let mut open = 0u32;
let dispute_ids: Vec<String> = AGENT_DISPUTES
.prefix(agent)
.keys(deps.storage, None, None, cosmwasm_std::Order::Ascending)
.filter_map(|r| r.ok())
.collect();
for id in dispute_ids {
if let Ok(dispute) = DISPUTES.load(deps.storage, &id) {
total += 1;
match dispute.status {
DisputeStatus::Open | DisputeStatus::UnderReview => open += 1,
DisputeStatus::Resolved => resolved_in_favor += 1,
DisputeStatus::Dismissed => dismissed += 1,
}
}
}
Ok(DisputeSummary {
total, open, resolved_in_favor, dismissed,
dispute_ratio: if total > 0 {
Decimal::from_ratio(resolved_in_favor + dismissed, total)
} else {
Decimal::zero()
},
})
}
pub fn penalize_false_dispute(deps: DepsMut, env: Env, dispute_id: String, penalty_amount: Decimal) -> StdResult<Response> {
let params = GOV_PARAMS.load(deps.storage)?;
let dispute = DISPUTES.load(deps.storage, &dispute_id)?;
if dispute.status != DisputeStatus::Resolved {
return Err(StdError::generic_err("Dispute not yet resolved"));
}
if let Ok(mut initiator_score) = AGENT_SCORES.may_load(deps.storage, &dispute.initiator)? {
initiator_score.composite_score = initiator_score.composite_score.saturating_sub(penalty_amount);
AGENT_SCORES.save(deps.storage, &dispute.initiator, &initiator_score)?;
}
if let Ok(mut respondent_score) = AGENT_SCORES.may_load(deps.storage, &dispute.respondent)? {
let compensation = Decimal::from_ratio(1u128, 100u128);
respondent_score.composite_score = respondent_score.composite_score + compensation;
let breakdown = compute_score_breakdown(deps.storage, &respondent_score, &dispute.respondent, &env.block.time, ¶ms)?;
respondent_score.composite_score = compute_composite_score(&breakdown, ¶ms);
AGENT_SCORES.save(deps.storage, &dispute.respondent, &respondent_score)?;
}
Ok(Response::new()
.add_attribute("action", "penalize_false_dispute")
.add_attribute("dispute_id", &dispute_id)
.add_attribute("penalty", penalty_amount.to_string()))
}
5.3 申诉流程 TypeScript
// packages/client/src/reputation/appeal.ts
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
enum AppealGrounds {
PROCEDURAL_ERROR = "procedural_error",
NEW_EVIDENCE = "new_evidence",
ARBITRATOR_BIAS = "arbitrator_bias",
SCORE_MISCALCULATION = "score_miscalculation",
}
export class AppealManager {
private signer: SigningCosmWasmClient;
private contractAddress: string;
private adminAddress: string;
constructor(signer: SigningCosmWasmClient, contractAddress: string, adminAddress: string) {
this.signer = signer;
this.contractAddress = contractAddress;
this.adminAddress = adminAddress;
}
async submitAppeal(sender: string, disputeId: string, grounds: AppealGrounds, evidenceCIDs: string[], description: string): Promise<string> {
const appealId = `appeal-${disputeId}-${Date.now()}`;
const msg = {
submit_appeal: {
appeal_id: appealId,
dispute_id: disputeId,
grounds: grounds.toString(),
evidence_cids: evidenceCIDs,
appeal_data_cid: `ipfs://QmAppeal${Date.now()}`,
},
};
const result = await this.signer.execute(sender, this.contractAddress, [msg], "auto", `Appeal for ${disputeId}`);
console.log(`Appeal submitted: ${appealId}, tx: ${result.transactionHash}`);
return appealId;
}
async resolveAppeal(appealId: string, upheld: boolean, resolutionNote: string, adjustedScore?: number): Promise<void> {
const msg = {
resolve_appeal: { appeal_id: appealId, upheld, resolution: resolutionNote, adjusted_score: adjustedScore },
};
await this.signer.execute(this.adminAddress, this.contractAddress, [msg], "auto", `Resolve ${appealId}`);
}
async getOpenDisputes(): Promise<Array<{ disputeId: string; taskId: string; status: string; createdAt: Date }>> {
const result: Array<{ dispute_id: string; task_id: string; status: string; created_at: string }> =
await this.signer.queryContractSmart(this.contractAddress, { list_open_disputes: { limit: 50 } });
return result.map((d) => ({ disputeId: d.dispute_id, taskId: d.task_id, status: d.status, createdAt: new Date(d.created_at) }));
}
async generateDisputeReport(agentAddress: string): Promise<{ totalDisputes: number; upheldRate: number; openDisputes: number; riskLevel: "low" | "medium" | "high" }> {
const summary: { total: number; open: number; resolved_in_favor: number; dismissed: number } =
await this.signer.queryContractSmart(this.contractAddress, { get_agent_dispute_summary: { agent: agentAddress } });
const totalResolved = summary.resolved_in_favor + summary.dismissed;
const upheldRate = totalResolved > 0 ? (summary.resolved_in_favor / totalResolved) * 100 : 0;
let riskLevel: "low" | "medium" | "high";
if (summary.total === 0) riskLevel = "low";
else if (upheldRate > 50 || summary.open > 3) riskLevel = "high";
else if (upheldRate > 20 || summary.open > 1) riskLevel = "medium";
else riskLevel = "low";
return { totalDisputes: summary.total, upheldRate, openDisputes: summary.open, riskLevel };
}
}
5.4 争议裁决评分调整
# scripts/dispute_resolution.py
from enum import Enum
from dataclasses import dataclass
class ResolutionOutcome(Enum):
FAVOR_AGENT = "favor_agent"
FAVOR_REQUESTER = "favor_requester"
PARTIAL = "partial"
DISMISSED = "dismissed"
@dataclass
class DisputeAdjustment:
agent_score_delta: float
requester_weight_delta: float
compensation: float
reason: str
class DisputeAdjustmentEngine:
FALSE_DISPUTE_PENALTY = -5.0
FALSE_DISPUTE_COMPENSATION = 200 * 10**6
FALSE_DISPUTE_WEIGHT_REDUCTION = 0.5
PROVEN_BREACH_PENALTY = -15.0
PROVEN_BREACH_STAKE_SLASH = 0.10
def resolve(self, outcome: ResolutionOutcome, agent_score: float, requester_rating_weight: float, agent_stake: int) -> DisputeAdjustment:
if outcome == ResolutionOutcome.FAVOR_AGENT:
return DisputeAdjustment(
agent_score_delta=1.0,
requester_weight_delta=-self.FALSE_DISPUTE_WEIGHT_REDUCTION,
compensation=self.FALSE_DISPUTE_COMPENSATION,
reason="Dispute ruled in agent's favor. Requester penalized.",
)
elif outcome == ResolutionOutcome.FAVOR_REQUESTER:
slash_amount = int(agent_stake * self.PROVEN_BREACH_STAKE_SLASH)
return DisputeAdjustment(
agent_score_delta=self.PROVEN_BREACH_PENALTY,
requester_weight_delta=0.0,
compensation=slash_amount,
reason=f"Agent in breach. Score -{abs(self.PROVEN_BREACH_PENALTY)} pts, stake slashed {slash_amount} umsg.",
)
elif outcome == ResolutionOutcome.PARTIAL:
return DisputeAdjustment(-2.0, -0.1, 50 * 10**6, "Partial fault: both parties share responsibility.")
else:
return DisputeAdjustment(0.0, 0.0, 0, "Dispute dismissed, no adjustment.")
def compute_penalty_severity(self, agent_score: float, breach_count: int, stake_amount: int) -> float:
base = -15.0
multiplier = [1.0, 2.0, 4.0, 8.0][min(breach_count, 4) - 1] if breach_count > 0 else 1.0
return base * multiplier
if __name__ == "__main__":
engine = DisputeAdjustmentEngine()
print("虚假争议:", engine.resolve(ResolutionOutcome.FAVOR_AGENT, 72.0, 1.0, 10000 * 10**6))
print("Agent违约:", engine.resolve(ResolutionOutcome.FAVOR_REQUESTER, 80.0, 1.0, 50000 * 10**6))
print("累犯惩罚:", [engine.compute_penalty_severity(75.0, i, 10000 * 10**6) for i in [1, 2, 3]])
6. 前端
6.1 信誉看板 React 组件
// packages/frontend/src/components/ReputationDashboard.tsx
import React, { useEffect, useState } from "react";
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
interface ReputationScore {
agent: string;
total_tasks: string;
successful_tasks: string;
failed_tasks: string;
success_rate: string;
avg_rating: string;
total_earned: string;
stake_amount: string;
last_activity: string;
composite_score: string;
}
interface ScoreBreakdown {
success_rate_component: string;
rating_component: string;
volume_component: string;
stake_component: string;
recency_component: string;
diversity_component: string;
}
function getTier(score: number): string {
if (score <= 20) return "Low";
if (score <= 50) return "Basic";
if (score <= 75) return "Good";
if (score <= 90) return "High";
return "Elite";
}
function getTierColor(tier: string): string {
const colors: Record<string, string> = {
Low: "#ef4444", Basic: "#f97316", Good: "#22c55e",
High: "#06b6d4", Elite: "#8b5cf6",
};
return colors[tier] ?? "#6b7280";
}
function formatAddress(addr: string): string {
if (addr.length <= 15) return addr;
return `${addr.slice(0, 8)}...${addr.slice(-6)}`;
}
interface ReputationDashboardProps {
rpcEndpoint: string;
contractAddress: string;
onAgentSelect?: (agent: string) => void;
}
export const ReputationDashboard: React.FC<ReputationDashboardProps> = ({
rpcEndpoint, contractAddress, onAgentSelect,
}) => {
const [agents, setAgents] = useState<ReputationScore[]>([]);
const [loading, setLoading] = useState(true);
const [search, setSearch] = useState("");
useEffect(() => {
const fetchData = async () => {
try {
const client = await CosmWasmClient.connect(rpcEndpoint);
const result: { agents: ReputationScore[] } = await client.queryContractSmart(
contractAddress, { list_top_agents: { limit: 50 } }
);
setAgents(result.agents);
} catch (err) {
console.error("Failed to fetch agents:", err);
} finally {
setLoading(false);
}
};
fetchData();
}, [rpcEndpoint, contractAddress]);
const filtered = agents.filter((a) =>
a.agent.toLowerCase().includes(search.toLowerCase())
);
if (loading) return <div className="p-8 text-center text-gray-400">Loading reputation data...</div>;
return (
<div className="p-6 max-w-7xl mx-auto">
<div className="flex justify-between items-center mb-6">
<h1 className="text-2xl font-bold text-gray-100">Agent 信誉看板</h1>
<input
type="text"
placeholder="搜索 Agent 地址..."
className="px-4 py-2 rounded bg-gray-800 border border-gray-700 text-gray-200 w-72"
value={search}
onChange={(e) => setSearch(e.target.value)}
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
{filtered.map((agent, idx) => {
const score = parseFloat(agent.composite_score);
const tier = getTier(score);
const color = getTierColor(tier);
return (
<div
key={agent.agent}
className="bg-gray-900 rounded-xl p-5 border border-gray-800 hover:border-gray-600 cursor-pointer transition-all"
onClick={() => onAgentSelect?.(agent.agent)}
>
<div className="flex items-center justify-between mb-3">
<span className="font-mono text-sm text-gray-400">
#{idx + 1} {formatAddress(agent.agent)}
</span>
<span
className="px-2 py-0.5 rounded text-xs font-semibold"
style={{ backgroundColor: color + "22", color }}
>
{tier}
</span>
</div>
<div className="text-4xl font-bold mb-4" style={{ color }}>
{score.toFixed(1)}
</div>
<div className="grid grid-cols-3 gap-3 text-sm">
<div>
<div className="text-gray-500">成功率</div>
<div className="text-gray-200 font-mono">
{(parseFloat(agent.success_rate) * 100).toFixed(1)}%
</div>
</div>
<div>
<div className="text-gray-500">评分</div>
<div className="text-gray-200 font-mono">
{parseFloat(agent.avg_rating).toFixed(1)}
</div>
</div>
<div>
<div className="text-gray-500">任务</div>
<div className="text-gray-200 font-mono">{agent.total_tasks}</div>
</div>
</div>
</div>
);
})}
</div>
</div>
);
};
6.2 评分分解详情组件
// packages/frontend/src/components/ScoreBreakdown.tsx
import React from "react";
interface BreakdownProps {
score: number;
breakdown: {
success_rate_component: string;
rating_component: string;
volume_component: string;
stake_component: string;
recency_component: string;
diversity_component: string;
};
}
const COMPONENT_LABELS: Record<string, { label: string; color: string }> = {
success_rate_component: { label: "成功率", color: "#22c55e" },
rating_component: { label: "平均评分", color: "#3b82f6" },
volume_component: { label: "任务量", color: "#f59e0b" },
stake_component: { label: "抵押加成", color: "#8b5cf6" },
recency_component: { label: "时间衰减", color: "#06b6d4" },
diversity_component: { label: "多样性", color: "#ec4899" },
};
export const ScoreBreakdownChart: React.FC<BreakdownProps> = ({ score, breakdown }) => {
const items = Object.entries(COMPONENT_LABELS).map(([key, meta]) => {
const rawValue = parseFloat(breakdown[key as keyof typeof breakdown]);
return { ...meta, key, rawValue, contribution: rawValue };
});
return (
<div className="bg-gray-900 rounded-xl p-6 border border-gray-800">
<div className="text-center mb-6">
<div className="text-5xl font-bold text-gray-100">{score.toFixed(1)}</div>
<div className="text-gray-500 mt-1">综合信誉分</div>
</div>
<div className="space-y-3">
{items.map((item) => (
<div key={item.key}>
<div className="flex justify-between text-sm mb-1">
<span style={{ color: item.color }}>{item.label}</span>
<span className="text-gray-400">{(item.rawValue * 100).toFixed(1)}%</span>
</div>
<div className="w-full bg-gray-800 rounded-full h-2">
<div
className="h-2 rounded-full transition-all duration-500"
style={{
width: `${Math.min(item.rawValue * 100, 100)}%`,
backgroundColor: item.color,
}}
/>
</div>
</div>
))}
</div>
</div>
);
};
6.3 Agent 对比组件
// packages/frontend/src/components/AgentComparison.tsx
import React from "react";
interface CompareProps {
agents: Array<{
agent: string;
composite_score: string;
success_rate: string;
avg_rating: string;
total_tasks: string;
stake_amount: string;
breakdown: {
success_rate_component: string;
rating_component: string;
volume_component: string;
stake_component: string;
recency_component: string;
diversity_component: string;
};
}>;
}
export const AgentComparison: React.FC<CompareProps> = ({ agents }) => {
if (agents.length === 0) return null;
return (
<div className="overflow-x-auto">
<table className="w-full text-sm">
<thead>
<tr className="text-gray-500 border-b border-gray-800">
<th className="text-left py-3 px-4">指标</th>
{agents.map((a) => (
<th key={a.agent} className="py-3 px-4 font-mono">
{a.agent.slice(0, 10)}...
</th>
))}
</tr>
</thead>
<tbody>
{[
{ label: "综合评分", key: "composite_score", fmt: (v: string) => parseFloat(v).toFixed(1) },
{ label: "成功率", key: "success_rate", fmt: (v: string) => `${(parseFloat(v) * 100).toFixed(1)}%` },
{ label: "平均评分", key: "avg_rating", fmt: (v: string) => parseFloat(v).toFixed(2) },
{ label: "任务数", key: "total_tasks", fmt: (v: string) => v },
{ label: "抵押 (MSG)", key: "stake_amount", fmt: (v: string) => (parseInt(v) / 10**6).toFixed(0) },
].map((row) => (
<tr key={row.key} className="border-b border-gray-800 hover:bg-gray-800/50">
<td className="py-3 px-4 text-gray-400">{row.label}</td>
{agents.map((a) => (
<td key={a.agent} className="py-3 px-4 font-mono text-gray-200">
{row.fmt(a[row.key as keyof typeof a] as string)}
</td>
))}
</tr>
))}
</tbody>
</table>
</div>
);
};
6.4 争议面板组件
// packages/frontend/src/components/DisputePanel.tsx
import React, { useState } from "react";
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
interface Dispute {
dispute_id: string;
task_id: string;
initiator: string;
respondent: string;
dispute_type: string;
status: string;
created_at: string;
}
interface DisputePanelProps {
disputes: Dispute[];
signer: SigningCosmWasmClient;
contractAddress: string;
adminAddress: string;
}
export const DisputePanel: React.FC<DisputePanelProps> = ({
disputes, signer, contractAddress, adminAddress,
}) => {
const [selected, setSelected] = useState<string | null>(null);
const resolveDispute = async (disputeId: string, upheld: boolean, adjustedRating?: number) => {
const msg = {
resolve_dispute: {
dispute_id: disputeId,
resolution: upheld ? "Upheld" : "Dismissed",
adjusted_rating: adjustedRating,
},
};
await signer.execute(adminAddress, contractAddress, [msg], "auto", `Resolve ${disputeId}`);
};
const statusColors: Record<string, string> = {
Open: "text-yellow-400",
UnderReview: "text-blue-400",
Resolved: "text-green-400",
Dismissed: "text-gray-500",
};
return (
<div className="bg-gray-900 rounded-xl border border-gray-800">
<div className="p-4 border-b border-gray-800">
<h2 className="text-lg font-bold text-gray-100">争议面板</h2>
</div>
<div className="divide-y divide-gray-800">
{disputes.map((d) => (
<div
key={d.dispute_id}
className="p-4 hover:bg-gray-800/50 cursor-pointer"
onClick={() => setSelected(selected === d.dispute_id ? null : d.dispute_id)}
>
<div className="flex justify-between items-center">
<div>
<span className="font-mono text-sm text-gray-300">{d.dispute_id.slice(0, 16)}...</span>
<span className="ml-3 text-sm text-gray-500">{d.dispute_type}</span>
</div>
<span className={`text-sm font-medium ${statusColors[d.status] ?? "text-gray-400"}`}>
{d.status}
</span>
</div>
{selected === d.dispute_id && (
<div className="mt-3 pl-2 border-l-2 border-gray-700">
<p className="text-sm text-gray-400">
Task: <span className="font-mono text-gray-300">{d.task_id}</span>
</p>
<p className="text-sm text-gray-400">
Respondent: <span className="font-mono text-gray-300">{d.respondent.slice(0, 12)}...</span>
</p>
{(d.status === "Open" || d.status === "UnderReview") && (
<div className="mt-3 flex gap-2">
<button
className="px-3 py-1 bg-green-700 hover:bg-green-600 text-white rounded text-xs"
onClick={(e) => { e.stopPropagation(); resolveDispute(d.dispute_id, true); }}
>
Favor Agent
</button>
<button
className="px-3 py-1 bg-red-700 hover:bg-red-600 text-white rounded text-xs"
onClick={(e) => { e.stopPropagation(); resolveDispute(d.dispute_id, false); }}
>
Favor Requester
</button>
</div>
)}
</div>
)}
</div>
))}
{disputes.length === 0 && (
<div className="p-8 text-center text-gray-500">暂无争议记录</div>
)}
</div>
</div>
);
};
7. 集成到现有系统
7.1 集成到 Registry(信誊辅助发现)
在 Agent 注册系统中添加信誉查询,使信誉分数成为发现和排序的核心指标。
// packages/registry/src/integration/reputation-discovery.ts
import { CosmWasmClient } from "@cosmjs/cosmwasm-stargate";
interface RegistryAgent {
id: string;
address: string;
name: string;
description: string;
skills: string[];
reputationScore?: number;
tier?: string;
}
export class ReputationDiscoveryIntegration {
private registryClient: CosmWasmClient;
private repClient: CosmWasmClient;
private repContract: string;
constructor(registryClient: CosmWasmClient, repClient: CosmWasmClient, repContract: string) {
this.registryClient = registryClient;
this.repClient = repClient;
this.repContract = repContract;
}
async listAgentsWithReputation(limit: number = 50): Promise<RegistryAgent[]> {
const agents: RegistryAgent[] = await this.registryClient.queryContractSmart(
"msg1registry...", { list_agents: { limit } }
);
const addresses = agents.map((a) => a.address);
const reps: Array<{ score: { composite_score: string } }> = await this.repClient.queryContractSmart(
this.repContract, { get_reputation_batch: { agents: addresses } }
);
return agents.map((agent, i) => ({
...agent,
reputationScore: parseFloat(reps[i]?.score?.composite_score ?? "0"),
tier: ["Low", "Basic", "Good", "High", "Elite"][
Math.min(Math.floor((parseFloat(reps[i]?.score?.composite_score ?? "0")) / 25), 4)
],
})).sort((a, b) => (b.reputationScore ?? 0) - (a.reputationScore ?? 0));
}
async searchByReputation(minScore: number, skill?: string): Promise<RegistryAgent[]> {
const agents = await this.listAgentsWithReputation(200);
return agents.filter(
(a) => (a.reputationScore ?? 0) >= minScore && (!skill || a.skills.includes(skill))
);
}
}
7.2 集成到 AIPAY(基于信誉的支付条件)
// packages/aipay/src/integration/reputation-payment.ts
import { SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
interface PaymentTerms {
agent: string;
taskValue: string;
upfrontPayment: string;
milestonePayments: string[];
collateralRequired: string;
paymentSchedule: "upfront" | "milestone" | "completion";
}
export class ReputationAIPAYIntegration {
private signer: SigningCosmWasmClient;
private repContract: string;
private aipayContract: string;
constructor(signer: SigningCosmWasmClient, repContract: string, aipayContract: string) {
this.signer = signer;
this.repContract = repContract;
this.aipayContract = aipayContract;
}
async computePaymentTerms(agent: string, taskValue: string): Promise<PaymentTerms> {
const rep: { score: { composite_score: string; stake_amount: string } } =
await this.signer.queryContractSmart(this.repContract, { get_reputation: { agent } });
const score = parseFloat(rep.score.composite_score);
const stake = parseInt(rep.score.stake_amount);
const value = parseInt(taskValue);
let collateralRequired: string;
let paymentSchedule: PaymentTerms["paymentSchedule"];
if (score >= 80) {
collateralRequired = "0";
paymentSchedule = "completion";
} else if (score >= 60) {
collateralRequired = Math.floor(value * 0.1).toString();
paymentSchedule = "milestone";
} else if (score >= 40) {
collateralRequired = Math.floor(value * 0.3).toString();
paymentSchedule = "milestone";
} else {
collateralRequired = Math.floor(value * 0.5).toString();
paymentSchedule = "upfront";
}
const upfrontPayment = paymentSchedule === "upfront" ? taskValue : "0";
const milestoneCount = paymentSchedule === "milestone" ? 3 : 0;
const milestonePayments = Array.from({ length: milestoneCount }, (_, i) =>
Math.floor(value / milestoneCount).toString()
);
return {
agent, taskValue,
upfrontPayment,
milestonePayments,
collateralRequired,
paymentSchedule,
};
}
async createReputationAwareEscrow(agent: string, requester: string, taskValue: string): Promise<string> {
const terms = await this.computePaymentTerms(agent, taskValue);
const msg = {
create_escrow: {
agent,
requester,
task_value: taskValue,
collateral: terms.collateralRequired,
payment_schedule: terms.paymentSchedule,
},
};
const result = await this.signer.execute(requester, this.aipayContract, [msg], "auto", "Create escrow");
return result.transactionHash;
}
}
7.3 集成到 Constitution(最低信誉要求)
// contracts/constitution/src/reputation_guard.rs
use cosmwasm_std::{Addr, Decimal, Deps, StdResult};
/// 检查 Agent 是否满足宪章的最低信誉要求
pub fn check_reputation_requirement(
deps: Deps,
agent: &Addr,
rep_contract: &Addr,
required_score: Decimal,
) -> StdResult<bool> {
let resp: ReputationCheckResponse = deps.querier.query_wasm_smart(
rep_contract.clone(),
&ReputationQuery::GetReputation {
agent: agent.to_string(),
},
)?;
Ok(resp.score.composite_score >= required_score)
}
/// 获取基于信誉的权限级别
pub fn get_reputation_permission_level(
deps: Deps,
agent: &Addr,
rep_contract: &Addr,
) -> StdResult<PermissionLevel> {
let resp: ReputationCheckResponse = deps.querier.query_wasm_smart(
rep_contract.clone(),
&ReputationQuery::GetReputation {
agent: agent.to_string(),
},
)?;
let score = resp.score.composite_score;
if score >= Decimal::from_ratio(90u128, 1u128) {
Ok(PermissionLevel::Full)
} else if score >= Decimal::from_ratio(60u128, 1u128) {
Ok(PermissionLevel::Elevated)
} else if score >= Decimal::from_ratio(30u128, 1u128) {
Ok(PermissionLevel::Standard)
} else {
Ok(PermissionLevel::Restricted)
}
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct ReputationCheckResponse {
pub score: RepScore,
}
#[derive(serde::Serialize, serde::Deserialize)]
pub struct RepScore {
pub composite_score: Decimal,
}
#[derive(serde::Serialize, serde::Deserialize, Debug, PartialEq)]
pub enum PermissionLevel {
Restricted,
Standard,
Elevated,
Full,
}
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ReputationQuery {
GetReputation { agent: String },
}
7.4 跨合约查询示例
// packages/client/examples/cross-contract-integration.ts
import { CosmWasmClient, SigningCosmWasmClient } from "@cosmjs/cosmwasm-stargate";
interface CrossContractResult {
agent: string;
reputation: number;
registryInfo: { name: string; skills: string[] };
paymentTerms: { collateral: string; schedule: string };
governanceStatus: { permissionLevel: string; allowed: boolean };
}
async function getFullAgentProfile(
client: CosmWasmClient,
signer: SigningCosmWasmClient,
agent: string
): Promise<CrossContractResult> {
const repContract = "msg1reputation...";
const registryContract = "msg1registry...";
const aipayContract = "msg1aipay...";
const constitutionContract = "msg1constitution...";
const [rep, registry, governanceStatus] = await Promise.all([
client.queryContractSmart(repContract, { get_reputation: { agent } }),
client.queryContractSmart(registryContract, { get_agent: { address: agent } }),
client.queryContractSmart(constitutionContract, {
check_agent_permission: { agent, required_action: "execute_task" },
}),
]);
const paymentTerms: { collateral: string; schedule: string } =
await signer.queryContractSmart(aipayContract, {
compute_payment_terms: { agent, task_value: "1000000000" },
});
return {
agent,
reputation: parseFloat(rep.score.composite_score),
registryInfo: { name: registry.name, skills: registry.skills },
paymentTerms,
governanceStatus: {
permissionLevel: governanceStatus.level,
allowed: governanceStatus.allowed,
},
};
}
7.5 部署与初始化脚本
#!/bin/bash
# scripts/deploy_reputation.sh
# MSG Chain 信誉合约部署脚本
CHAIN_ID="msg-chain-1"
RPC="https://rpc.msg-chain-1.msg.network"
MNEMONIC="your deployer mnemonic"
ADMIN="msg1admin..."
LABEL="reputation-core"
CODE_ID=""
echo "=== 编译合约 ==="
cargo wasm
echo "=== 优化 WASM ==="
docker run --rm -v "$(pwd)":/code \
--mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \
--mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \
cosmwasm/workspace-optimizer:0.14.0
echo "=== 上传合约 ==="
RES=$(msgd tx wasm store artifacts/reputation_core.wasm \
--from deployer --chain-id "$CHAIN_ID" --node "$RPC" \
--gas auto --gas-prices 1000000000attoMSG --gas-adjustment 1.3 \
--output json -y)
CODE_ID=$(echo "$RES" | jq -r '.logs[0].events[] | select(.type=="store_code") | .attributes[] | select(.key=="code_id") | .value')
echo "Code ID: $CODE_ID"
echo "=== 实例化合约 ==="
INIT_JSON='{"admin":"'"$ADMIN"'","params":null}'
msgd tx wasm instantiate "$CODE_ID" "$INIT_JSON" \
--from deployer --label "$LABEL" --admin "$ADMIN" \
--chain-id "$CHAIN_ID" --node "$RPC" \
--gas auto --gas-prices 1000000000attoMSG --gas-adjustment 1.3 \
--output json -y
echo "=== 查询合约地址 ==="
CONTRACT=$(msgd query wasm list-contract-by-code "$CODE_ID" --node "$RPC" --output json | jq -r '.contracts[0]')
echo "Reputation Contract: $CONTRACT"
echo "=== 初始化治理参数 ==="
PARAMS_JSON='{"update_params":{"params":{"success_rate_weight":"0.30","rating_weight":"0.25","volume_weight":"0.15","stake_weight":"0.10","recency_weight":"0.10","diversity_weight":"0.10"}}}'
msgd tx wasm execute "$CONTRACT" "$PARAMS_JSON" \
--from deployer --chain-id "$CHAIN_ID" --node "$RPC" \
--gas auto --gas-prices 1000000000attoMSG --gas-adjustment 1.3 -y
echo "部署完成!"
echo "合约地址: $CONTRACT"
echo "管理员: $ADMIN"
7.6 完整测试脚本
#!/bin/bash
# scripts/test_reputation.sh
CONTRACT="msg1reputation..."
DEPLOYER="msg1deployer..."
AGENT="msg1agent1..."
REQUESTER="msg1requester..."
echo "=== 1. 记录任务 ==="
msgd tx wasm execute "$CONTRACT" '{
"record_task_result": {
"task_id":"task-001",
"agent":"'"$AGENT"'",
"requester":"'"$REQUESTER"'",
"status":"completed",
"reward":"1000000",
"evidence_hash":"ipfs://QmTest"
}
}' --from "$REQUESTER" --chain-id msg-chain-1 --gas auto --gas-prices 1000000000attoMSG --gas-adjustment 1.3 -y
echo "=== 2. 提交评分 ==="
msgd tx wasm execute "$CONTRACT" '{
"submit_rating": {
"agent":"'"$AGENT"'",
"task_id":"task-001",
"score":5,
"comment_hash":"ipfs://QmComment"
}
}' --from "$REQUESTER" --chain-id msg-chain-1 --gas auto --gas-prices 1000000000attoMSG --gas-adjustment 1.3 -y
echo "=== 3. Agent 抵押 ==="
msgd tx wasm execute "$CONTRACT" '{
"stake": {
"amount":"10000000000",
"lock_duration_secs":2592000
}
}' --from "$AGENT" --amount 10000msg --chain-id msg-chain-1 --gas auto --gas-prices 1000000000attoMSG --gas-adjustment 1.3 -y
echo "=== 4. 查询信誉 ==="
msgd query wasm contract-state smart "$CONTRACT" '{
"get_reputation": {"agent":"'"$AGENT"'"}
}'
echo "=== 5. 查看排行榜 ==="
msgd query wasm contract-state smart "$CONTRACT" '{
"list_top_agents":{"limit":10}
}'
echo "测试完成"
附录
A. 参考链接
| 资源 | 链接 |
|---|---|
| MSG Chain 文档 | https://docs.msg.network |
| CosmWasm 文档 | https://docs.cosmwasm.com |
| cw-storage-plus | https://github.com/CosmWasm/cw-storage-plus |
| 信誉合约完整代码 | https://github.com/msgchain/reputation-contracts |
| 前端 SDK | https://github.com/msgchain/reputation-sdk |
B. 术语表
| 术语 | 英文 | 说明 |
|---|---|---|
| 信誉分 | Reputation Score | 0-100 的综合评分 |
| 信誉等级 | Tier | Low/Basic/Good/High/Elite |
| 抵押 | Stake | 用于信誉加成的代币锁定 |
| 争议 | Dispute | 对评分或任务结果的异议 |
| 申诉 | Appeal | 对争议裁决的再申诉 |
| Sybil 抵抗 | Sybil Resistance | 防止虚假身份攻击 |
| 时间衰减 | Time Decay | 旧行为对评分影响递减 |
| 多样性 | Diversity | 合作过的发布者数量 |
| 治理覆写 | Governance Override | 治理投票修改评分 |
C. 版本历史
| 版本 | 日期 | 变更 |
|---|---|---|
| v1.0.0 | 2026-07-06 | 初始版本 |
