use crate::reputation_engine::{
ReputationEngine, TrustScore, TrustTier, CaptureLayer, AttestationRecord,
SWARM_COORDINATOR_THRESHOLD, COLLATERAL_THRESHOLD,
};
use serde::{Deserialize, Serialize};
use std::collections::{HashMap, HashSet};
use std::sync::{Arc, RwLock};
pub const SWARM_MANAGEMENT_FEE_BPS: u64 = 100;
pub const MIN_HIRE_AMOUNT: u64 = 1_000_000;
pub const REFERRAL_BOUNTY_BPS: u32 = 1000;
pub const REFERRAL_BOUNTY_MIN_CREDENTIAL_TIER: u8 = 1;
#[derive(Debug, Default)]
pub struct ReferralRegistry {
referrals: HashMap<String, ReferralRecord>,
agent_bounties: HashMap<String, u32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReferralRecord {
pub referrer_agent: String,
pub referred_user: String,
pub has_qualified_credential: bool,
pub credential_tier: Option<u8>,
pub bounty_paid: bool,
pub bounty_amount: Option<u32>,
pub created_at: u64,
pub paid_at: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReferralBountyResult {
pub success: bool,
pub referrer_agent: String,
pub referred_user: String,
pub reputation_boost: u32,
pub referrer_new_score: Option<u32>,
pub failure_reason: Option<String>,
}
impl ReferralRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn register_referral(
&mut self,
referrer_agent: &str,
referred_user: &str,
timestamp: u64,
) -> Result<(), String> {
if self.referrals.contains_key(referred_user) {
return Err("User already has a referrer".to_string());
}
self.referrals.insert(
referred_user.to_string(),
ReferralRecord {
referrer_agent: referrer_agent.to_string(),
referred_user: referred_user.to_string(),
has_qualified_credential: false,
credential_tier: None,
bounty_paid: false,
bounty_amount: None,
created_at: timestamp,
paid_at: None,
},
);
Ok(())
}
pub fn update_credential_tier(
&mut self,
referred_user: &str,
credential_tier: u8,
) -> bool {
if let Some(record) = self.referrals.get_mut(referred_user) {
record.credential_tier = Some(credential_tier);
record.has_qualified_credential = credential_tier >= REFERRAL_BOUNTY_MIN_CREDENTIAL_TIER;
return record.has_qualified_credential;
}
false
}
pub fn get_referrer(&self, user: &str) -> Option<&str> {
self.referrals.get(user).map(|r| r.referrer_agent.as_str())
}
pub fn is_bounty_eligible(&self, referred_user: &str) -> bool {
self.referrals
.get(referred_user)
.map(|r| r.has_qualified_credential && !r.bounty_paid)
.unwrap_or(false)
}
pub fn mark_bounty_paid(
&mut self,
referred_user: &str,
bounty_amount: u32,
timestamp: u64,
) -> bool {
if let Some(record) = self.referrals.get_mut(referred_user) {
if record.has_qualified_credential && !record.bounty_paid {
record.bounty_paid = true;
record.bounty_amount = Some(bounty_amount);
record.paid_at = Some(timestamp);
*self
.agent_bounties
.entry(record.referrer_agent.clone())
.or_default() += bounty_amount;
return true;
}
}
false
}
pub fn get_agent_bounties(&self, agent_pubkey: &str) -> u32 {
self.agent_bounties.get(agent_pubkey).copied().unwrap_or(0)
}
pub fn get_agent_referrals(&self, agent_pubkey: &str) -> Vec<&ReferralRecord> {
self.referrals
.values()
.filter(|r| r.referrer_agent == agent_pubkey)
.collect()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum GatewayError {
Unauthorized {
message: String,
},
Forbidden {
message: String,
required_threshold: u32,
current_score: u32,
required_tier: String,
current_tier: String,
},
BadRequest {
message: String,
},
RateLimited {
retry_after_ms: u64,
},
Internal {
message: String,
},
}
impl std::fmt::Display for GatewayError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
GatewayError::Unauthorized { message } => {
write!(f, "Unauthorized: {}", message)
}
GatewayError::Forbidden { message, required_threshold, current_score, .. } => {
write!(f, "Forbidden: {} (need {}, have {})", message, required_threshold, current_score)
}
GatewayError::BadRequest { message } => {
write!(f, "Bad Request: {}", message)
}
GatewayError::RateLimited { retry_after_ms } => {
write!(f, "Rate Limited: retry after {}ms", retry_after_ms)
}
GatewayError::Internal { message } => {
write!(f, "Internal Error: {}", message)
}
}
}
}
impl std::error::Error for GatewayError {}
#[derive(Debug, Default)]
pub struct SubAgentRegistry {
active_hires: HashMap<String, HashSet<String>>,
fees_captured: HashMap<String, u64>,
}
impl SubAgentRegistry {
pub fn new() -> Self {
Self::default()
}
pub fn hired_count(&self, user_pubkey: &str) -> usize {
self.active_hires
.get(user_pubkey)
.map(|s| s.len())
.unwrap_or(0)
}
pub fn is_hired(&self, user_pubkey: &str, sub_agent_pubkey: &str) -> bool {
self.active_hires
.get(user_pubkey)
.map(|s| s.contains(sub_agent_pubkey))
.unwrap_or(false)
}
pub fn add_hire(&mut self, user_pubkey: &str, sub_agent_pubkey: &str) {
self.active_hires
.entry(user_pubkey.to_string())
.or_default()
.insert(sub_agent_pubkey.to_string());
}
pub fn remove_hire(&mut self, user_pubkey: &str, sub_agent_pubkey: &str) -> bool {
self.active_hires
.get_mut(user_pubkey)
.map(|s| s.remove(sub_agent_pubkey))
.unwrap_or(false)
}
pub fn record_fee(&mut self, user_pubkey: &str, fee_lamports: u64) {
*self.fees_captured.entry(user_pubkey.to_string()).or_default() += fee_lamports;
}
pub fn total_fees(&self, user_pubkey: &str) -> u64 {
self.fees_captured.get(user_pubkey).copied().unwrap_or(0)
}
pub fn list_hires(&self, user_pubkey: &str) -> Vec<String> {
self.active_hires
.get(user_pubkey)
.map(|s| s.iter().cloned().collect())
.unwrap_or_default()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HireResult {
pub success: bool,
pub sub_agent_pubkey: String,
pub task_id: String,
pub net_budget: u64,
pub fee_captured: u64,
pub current_hired_count: usize,
pub max_allowed: u8,
pub user_tier: TrustTier,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SwarmCoordinationResult {
pub success: bool,
pub task_id: String,
pub hired_agents: Vec<String>,
pub total_net_budget: u64,
pub total_fees_captured: u64,
pub failed_agents: Vec<(String, String)>, }
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct A2ARequest {
pub agent_pubkey: String,
pub user_pubkey: String,
pub session_token: String,
pub action: SwarmAction,
pub params: Option<serde_json::Value>,
pub timestamp: u64,
pub signature: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum SwarmAction {
CaptureReward {
merchant_id: String,
amount: u64,
transaction_ref: String,
},
TrackReferral {
referral_code: String,
conversion_amount: Option<u64>,
},
SubmitAttention {
content_id: String,
duration_ms: u64,
},
LicenseData {
data_type: String,
buyer: String,
terms_hash: String,
},
JoinInsurancePool {
pool_id: String,
coverage_amount: u64,
},
RegisterCompute {
gpu_type: String,
available_hours: u32,
},
RunNode {
node_type: String,
uptime_commitment: u8,
},
EnergyTrade {
direction: String, kwh: f64,
price_per_kwh: f64,
},
LicenseSkill {
skill_type: String,
license_terms: String,
},
SubmitCuration {
signal_type: String,
signal_data: String,
},
SocialAction {
action_type: String,
target: String,
},
DeployLiquidity {
pool: String,
amount: u64,
strategy: String,
},
CastVote {
proposal_id: String,
vote: String,
delegation_power: u64,
},
BulkPurchase {
merchant_id: String,
items: Vec<String>,
max_spend: u64,
},
HireSubAgent {
sub_agent_pubkey: String,
task_description: String,
max_budget: u64,
deadline: u64,
},
PledgeReputation {
protocol: String,
loan_amount: u64,
collateral_score: u32,
},
CoordinateSwarm {
task_id: String,
sub_agents: Vec<String>,
task_allocation: HashMap<String, String>,
total_budget: u64,
coordination_fee_bps: u16,
},
AcceptContract {
contract_id: String,
requester: String,
deliverables: Vec<String>,
payment: u64,
},
DistributeRewards {
task_id: String,
distributions: HashMap<String, u64>,
},
}
impl SwarmAction {
pub fn layer(&self) -> CaptureLayer {
match self {
SwarmAction::CaptureReward { .. } => CaptureLayer::Shopping,
SwarmAction::TrackReferral { .. } => CaptureLayer::Referral,
SwarmAction::SubmitAttention { .. } => CaptureLayer::Attention,
SwarmAction::LicenseData { .. } => CaptureLayer::Data,
SwarmAction::JoinInsurancePool { .. } => CaptureLayer::Insurance,
SwarmAction::RegisterCompute { .. } => CaptureLayer::Compute,
SwarmAction::RunNode { .. } => CaptureLayer::Network,
SwarmAction::EnergyTrade { .. } => CaptureLayer::Energy,
SwarmAction::LicenseSkill { .. } => CaptureLayer::Skill,
SwarmAction::SubmitCuration { .. } => CaptureLayer::CurationSignal,
SwarmAction::SocialAction { .. } => CaptureLayer::Social,
SwarmAction::DeployLiquidity { .. } => CaptureLayer::Liquidity,
SwarmAction::CastVote { .. } => CaptureLayer::GovernanceProxy,
SwarmAction::BulkPurchase { .. } => CaptureLayer::InventoryArbitrage,
SwarmAction::HireSubAgent { .. } => CaptureLayer::SubAgentManager,
SwarmAction::PledgeReputation { .. } => CaptureLayer::ReputationCollateral,
SwarmAction::CoordinateSwarm { .. } => CaptureLayer::SwarmCoordinationFee,
SwarmAction::AcceptContract { .. } => CaptureLayer::SwarmCoordinationFee,
SwarmAction::DistributeRewards { .. } => CaptureLayer::SwarmCoordinationFee,
}
}
pub fn required_score(&self) -> u32 {
match self.layer() {
CaptureLayer::Shopping
| CaptureLayer::Referral
| CaptureLayer::Attention
| CaptureLayer::Data
| CaptureLayer::Insurance
| CaptureLayer::Compute
| CaptureLayer::Network
| CaptureLayer::Energy
| CaptureLayer::DePINAggregator
| CaptureLayer::InferenceArbitrage
| CaptureLayer::StorageDePIN
| CaptureLayer::Skill
| CaptureLayer::CurationSignal
| CaptureLayer::Social
| CaptureLayer::KnowledgeAPI
| CaptureLayer::PersonalModelLicensing => 0,
CaptureLayer::Liquidity
| CaptureLayer::GovernanceProxy
| CaptureLayer::InventoryArbitrage
| CaptureLayer::SubAgentManager
| CaptureLayer::ReputationCollateral => COLLATERAL_THRESHOLD,
CaptureLayer::SwarmCoordinationFee => SWARM_COORDINATOR_THRESHOLD,
}
}
pub fn is_swarm_action(&self) -> bool {
matches!(
self,
SwarmAction::CoordinateSwarm { .. }
| SwarmAction::AcceptContract { .. }
| SwarmAction::DistributeRewards { .. }
)
}
pub fn requires_collateral(&self) -> bool {
self.required_score() >= COLLATERAL_THRESHOLD
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct A2AResponse {
pub success: bool,
pub request_id: String,
pub data: Option<serde_json::Value>,
pub error: Option<GatewayError>,
pub processing_ms: u64,
pub reputation_delta: Option<i32>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionCheck {
pub allowed: bool,
pub layer: String,
pub required_score: u32,
pub current_score: u32,
pub current_tier: TrustTier,
pub required_tier: TrustTier,
pub threshold_checked: Option<String>,
pub points_needed: Option<u32>,
pub unlock_suggestions: Vec<String>,
}
pub struct Gateway {
reputation_cache: Arc<RwLock<HashMap<String, ReputationEngine>>>,
rate_limits: Arc<RwLock<HashMap<String, RateLimitState>>>,
max_requests_per_minute: u32,
sub_agent_registry: Arc<RwLock<SubAgentRegistry>>,
referral_registry: Arc<RwLock<ReferralRegistry>>,
}
struct RateLimitState {
count: u32,
window_start: u64,
}
impl Gateway {
pub fn new() -> Self {
Self {
reputation_cache: Arc::new(RwLock::new(HashMap::new())),
rate_limits: Arc::new(RwLock::new(HashMap::new())),
max_requests_per_minute: 100,
sub_agent_registry: Arc::new(RwLock::new(SubAgentRegistry::new())),
referral_registry: Arc::new(RwLock::new(ReferralRegistry::new())),
}
}
pub fn with_rate_limit(max_requests_per_minute: u32) -> Self {
Self {
reputation_cache: Arc::new(RwLock::new(HashMap::new())),
rate_limits: Arc::new(RwLock::new(HashMap::new())),
max_requests_per_minute,
sub_agent_registry: Arc::new(RwLock::new(SubAgentRegistry::new())),
referral_registry: Arc::new(RwLock::new(ReferralRegistry::new())),
}
}
pub fn check_permission(
&self,
user_pubkey: &str,
action: &SwarmAction,
) -> Result<PermissionCheck, GatewayError> {
let mut cache = self.reputation_cache.write().map_err(|_| {
GatewayError::Internal {
message: "Failed to acquire reputation cache lock".to_string(),
}
})?;
let engine = cache
.entry(user_pubkey.to_string())
.or_insert_with(|| ReputationEngine::new(user_pubkey.to_string()));
let trust_score = engine.calculate_trust_score();
let current_score = trust_score.composite;
let current_tier = trust_score.tier;
let required_score = action.required_score();
let required_tier = TrustTier::from_score(required_score);
let layer = format!("{:?}", action.layer());
let allowed = current_score >= required_score;
let threshold_checked = if action.is_swarm_action() {
Some("swarm_coordinator".to_string())
} else if action.requires_collateral() {
Some("collateral_eligible".to_string())
} else {
None
};
let (points_needed, unlock_suggestions) = if !allowed {
let needed = required_score - current_score;
let suggestions = self.generate_unlock_suggestions(needed, &trust_score);
(Some(needed), suggestions)
} else {
(None, vec![])
};
Ok(PermissionCheck {
allowed,
layer,
required_score,
current_score,
current_tier,
required_tier,
threshold_checked,
points_needed,
unlock_suggestions,
})
}
pub fn enforce_permission(
&self,
user_pubkey: &str,
action: &SwarmAction,
) -> Result<(), GatewayError> {
let check = self.check_permission(user_pubkey, action)?;
if !check.allowed {
return Err(GatewayError::Forbidden {
message: format!(
"Insufficient reputation for {:?} (Layer {}). Need {} points, have {}.",
action.layer(),
action.layer() as u8,
check.required_score,
check.current_score
),
required_threshold: check.required_score,
current_score: check.current_score,
required_tier: format!("{:?}", check.required_tier),
current_tier: format!("{:?}", check.current_tier),
});
}
Ok(())
}
pub fn enforce_swarm_permission(&self, user_pubkey: &str) -> Result<(), GatewayError> {
let mut cache = self.reputation_cache.write().map_err(|_| {
GatewayError::Internal {
message: "Failed to acquire reputation cache lock".to_string(),
}
})?;
let engine = cache
.entry(user_pubkey.to_string())
.or_insert_with(|| ReputationEngine::new(user_pubkey.to_string()));
let trust_score = engine.calculate_trust_score();
let swarm_allowed = trust_score
.threshold_proofs
.get("swarm_coordinator")
.copied()
.unwrap_or(false);
if !swarm_allowed {
return Err(GatewayError::Forbidden {
message: format!(
"Swarm coordination requires {} reputation points. Current: {}. Tier: {:?}.",
SWARM_COORDINATOR_THRESHOLD,
trust_score.composite,
trust_score.tier
),
required_threshold: SWARM_COORDINATOR_THRESHOLD,
current_score: trust_score.composite,
required_tier: "Trusted".to_string(),
current_tier: format!("{:?}", trust_score.tier),
});
}
Ok(())
}
pub fn check_rate_limit(&self, agent_pubkey: &str) -> Result<(), GatewayError> {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as u64;
let mut limits = self.rate_limits.write().map_err(|_| {
GatewayError::Internal {
message: "Failed to acquire rate limit lock".to_string(),
}
})?;
let state = limits
.entry(agent_pubkey.to_string())
.or_insert(RateLimitState {
count: 0,
window_start: now,
});
if now - state.window_start > 60_000 {
state.count = 0;
state.window_start = now;
}
if state.count >= self.max_requests_per_minute {
let retry_after = 60_000 - (now - state.window_start);
return Err(GatewayError::RateLimited {
retry_after_ms: retry_after,
});
}
state.count += 1;
Ok(())
}
pub fn process_request(&self, request: &A2ARequest) -> Result<PermissionCheck, GatewayError> {
self.check_rate_limit(&request.agent_pubkey)?;
let permission = self.check_permission(&request.user_pubkey, &request.action)?;
if !permission.allowed {
return Err(GatewayError::Forbidden {
message: format!(
"Action {:?} requires {} reputation. User has {}.",
request.action.layer(),
permission.required_score,
permission.current_score
),
required_threshold: permission.required_score,
current_score: permission.current_score,
required_tier: format!("{:?}", permission.required_tier),
current_tier: format!("{:?}", permission.current_tier),
});
}
Ok(permission)
}
pub fn get_reputation(&self, user_pubkey: &str) -> Result<TrustScore, GatewayError> {
let mut cache = self.reputation_cache.write().map_err(|_| {
GatewayError::Internal {
message: "Failed to acquire reputation cache lock".to_string(),
}
})?;
let engine = cache
.entry(user_pubkey.to_string())
.or_insert_with(|| ReputationEngine::new(user_pubkey.to_string()));
Ok(engine.calculate_trust_score())
}
pub fn record_attestation(
&self,
user_pubkey: &str,
attestation: &crate::reputation_engine::AttestationRecord,
) -> Result<TrustScore, GatewayError> {
let mut cache = self.reputation_cache.write().map_err(|_| {
GatewayError::Internal {
message: "Failed to acquire reputation cache lock".to_string(),
}
})?;
let engine = cache
.entry(user_pubkey.to_string())
.or_insert_with(|| ReputationEngine::new(user_pubkey.to_string()));
engine.process_attestation(attestation);
Ok(engine.calculate_trust_score())
}
pub fn hire_sub_agent(
&self,
user_pubkey: &str,
sub_agent_pubkey: &str,
task_id: &str,
budget: u64,
) -> Result<HireResult, GatewayError> {
if budget < MIN_HIRE_AMOUNT {
return Err(GatewayError::BadRequest {
message: format!(
"Budget too low. Minimum: {} lamports, provided: {}",
MIN_HIRE_AMOUNT, budget
),
});
}
let trust_score = self.get_reputation(user_pubkey)?;
let max_allowed = trust_score.tier.max_sub_agents();
let action = SwarmAction::HireSubAgent {
sub_agent_pubkey: sub_agent_pubkey.to_string(),
task_description: task_id.to_string(),
max_budget: budget,
deadline: 0, };
self.enforce_permission(user_pubkey, &action)?;
let mut registry = self.sub_agent_registry.write().map_err(|_| {
GatewayError::Internal {
message: "Failed to acquire sub-agent registry lock".to_string(),
}
})?;
let current_count = registry.hired_count(user_pubkey);
if current_count >= max_allowed as usize {
return Err(GatewayError::Forbidden {
message: format!(
"Sub-agent limit reached. Tier {:?} allows max {} sub-agents. Currently hired: {}.",
trust_score.tier, max_allowed, current_count
),
required_threshold: SWARM_COORDINATOR_THRESHOLD,
current_score: trust_score.composite,
required_tier: format!("{:?}", TrustTier::from_score(SWARM_COORDINATOR_THRESHOLD)),
current_tier: format!("{:?}", trust_score.tier),
});
}
if registry.is_hired(user_pubkey, sub_agent_pubkey) {
return Err(GatewayError::BadRequest {
message: format!("Sub-agent {} is already hired", sub_agent_pubkey),
});
}
let fee = (budget * SWARM_MANAGEMENT_FEE_BPS) / 10_000;
let net_budget = budget - fee;
registry.add_hire(user_pubkey, sub_agent_pubkey);
registry.record_fee(user_pubkey, fee);
Ok(HireResult {
success: true,
sub_agent_pubkey: sub_agent_pubkey.to_string(),
task_id: task_id.to_string(),
net_budget,
fee_captured: fee,
current_hired_count: current_count + 1,
max_allowed,
user_tier: trust_score.tier,
})
}
pub fn coordinate_swarm(
&self,
user_pubkey: &str,
task_id: &str,
sub_agents: &[String],
budget_per_agent: u64,
) -> Result<SwarmCoordinationResult, GatewayError> {
self.enforce_swarm_permission(user_pubkey)?;
let trust_score = self.get_reputation(user_pubkey)?;
let max_allowed = trust_score.tier.max_sub_agents();
let mut registry = self.sub_agent_registry.write().map_err(|_| {
GatewayError::Internal {
message: "Failed to acquire sub-agent registry lock".to_string(),
}
})?;
let current_count = registry.hired_count(user_pubkey);
let total_after = current_count + sub_agents.len();
if total_after > max_allowed as usize {
return Err(GatewayError::Forbidden {
message: format!(
"Swarm size exceeds limit. Tier {:?} allows {} sub-agents. Current: {}, Requested: {}.",
trust_score.tier, max_allowed, current_count, sub_agents.len()
),
required_threshold: SWARM_COORDINATOR_THRESHOLD,
current_score: trust_score.composite,
required_tier: "Elite".to_string(),
current_tier: format!("{:?}", trust_score.tier),
});
}
let mut hired_agents = Vec::new();
let mut failed_agents = Vec::new();
let mut total_fees = 0u64;
let mut total_net = 0u64;
for sub_agent in sub_agents {
if registry.is_hired(user_pubkey, sub_agent) {
failed_agents.push((sub_agent.clone(), "Already hired".to_string()));
continue;
}
let fee = (budget_per_agent * SWARM_MANAGEMENT_FEE_BPS) / 10_000;
let net = budget_per_agent - fee;
registry.add_hire(user_pubkey, sub_agent);
registry.record_fee(user_pubkey, fee);
hired_agents.push(sub_agent.clone());
total_fees += fee;
total_net += net;
}
Ok(SwarmCoordinationResult {
success: !hired_agents.is_empty(),
task_id: task_id.to_string(),
hired_agents,
total_net_budget: total_net,
total_fees_captured: total_fees,
failed_agents,
})
}
pub fn release_sub_agent(
&self,
user_pubkey: &str,
sub_agent_pubkey: &str,
) -> Result<bool, GatewayError> {
let mut registry = self.sub_agent_registry.write().map_err(|_| {
GatewayError::Internal {
message: "Failed to acquire sub-agent registry lock".to_string(),
}
})?;
Ok(registry.remove_hire(user_pubkey, sub_agent_pubkey))
}
pub fn get_sub_agent_status(&self, user_pubkey: &str) -> Result<(Vec<String>, u64, u8), GatewayError> {
let trust_score = self.get_reputation(user_pubkey)?;
let max_allowed = trust_score.tier.max_sub_agents();
let registry = self.sub_agent_registry.read().map_err(|_| {
GatewayError::Internal {
message: "Failed to acquire sub-agent registry lock".to_string(),
}
})?;
let hired = registry.list_hires(user_pubkey);
let fees = registry.total_fees(user_pubkey);
Ok((hired, fees, max_allowed))
}
pub fn register_referral(
&self,
referrer_agent: &str,
referred_user: &str,
) -> Result<(), GatewayError> {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let mut registry = self.referral_registry.write().map_err(|_| {
GatewayError::Internal {
message: "Failed to acquire referral registry lock".to_string(),
}
})?;
registry
.register_referral(referrer_agent, referred_user, now)
.map_err(|msg| GatewayError::BadRequest { message: msg })
}
pub fn process_referral_bounty(
&self,
referred_user: &str,
verified_credential_tier: u8,
) -> Result<ReferralBountyResult, GatewayError> {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_secs();
let referrer_agent = {
let registry = self.referral_registry.read().map_err(|_| {
GatewayError::Internal {
message: "Failed to acquire referral registry lock".to_string(),
}
})?;
match registry.get_referrer(referred_user) {
Some(agent) => agent.to_string(),
None => {
return Ok(ReferralBountyResult {
success: false,
referrer_agent: String::new(),
referred_user: referred_user.to_string(),
reputation_boost: 0,
referrer_new_score: None,
failure_reason: Some("User was not referred by an agent".to_string()),
});
}
}
};
{
let mut registry = self.referral_registry.write().map_err(|_| {
GatewayError::Internal {
message: "Failed to acquire referral registry lock".to_string(),
}
})?;
registry.update_credential_tier(referred_user, verified_credential_tier);
if !registry.is_bounty_eligible(referred_user) {
return Ok(ReferralBountyResult {
success: false,
referrer_agent: referrer_agent.clone(),
referred_user: referred_user.to_string(),
reputation_boost: 0,
referrer_new_score: None,
failure_reason: Some(format!(
"Credential tier {} does not meet minimum requirement of Tier {}",
verified_credential_tier, REFERRAL_BOUNTY_MIN_CREDENTIAL_TIER
)),
});
}
}
let referrer_score = self.get_reputation(&referrer_agent)?;
let bounty_amount = (referrer_score.composite * REFERRAL_BOUNTY_BPS) / 10000;
let attestation = AttestationRecord {
layer: CaptureLayer::Referral,
timestamp: now,
positive: true,
magnitude: bounty_amount as u64 * 1_000_000, metadata: Some(crate::reputation_engine::AttestationMetadata {
referral_successful: Some(true),
..Default::default()
}),
};
let new_score = self.record_attestation(&referrer_agent, &attestation)?;
{
let mut registry = self.referral_registry.write().map_err(|_| {
GatewayError::Internal {
message: "Failed to acquire referral registry lock".to_string(),
}
})?;
registry.mark_bounty_paid(referred_user, bounty_amount, now);
}
Ok(ReferralBountyResult {
success: true,
referrer_agent,
referred_user: referred_user.to_string(),
reputation_boost: bounty_amount,
referrer_new_score: Some(new_score.composite),
failure_reason: None,
})
}
pub fn get_agent_referral_stats(
&self,
agent_pubkey: &str,
) -> Result<(Vec<ReferralRecord>, u32), GatewayError> {
let registry = self.referral_registry.read().map_err(|_| {
GatewayError::Internal {
message: "Failed to acquire referral registry lock".to_string(),
}
})?;
let referrals: Vec<ReferralRecord> = registry
.get_agent_referrals(agent_pubkey)
.into_iter()
.cloned()
.collect();
let total_bounties = registry.get_agent_bounties(agent_pubkey);
Ok((referrals, total_bounties))
}
fn generate_unlock_suggestions(&self, points_needed: u32, score: &TrustScore) -> Vec<String> {
let mut suggestions = vec![];
if score.reliability < 300 {
suggestions.push(format!(
"Stack CRED for 90+ days to boost Reliability (+{} potential points)",
300 - score.reliability
));
}
if score.skill < 200 {
suggestions.push(format!(
"Submit high-accuracy data captures to boost Skill (+{} potential points)",
200 - score.skill
));
}
if score.infrastructure < 200 {
suggestions.push(
"Run a network node or provide compute to boost Infrastructure".to_string(),
);
}
if score.social < 150 {
suggestions.push("Invite friends or complete referrals to boost Social".to_string());
}
if points_needed > 200 {
suggestions.push(format!(
"You need {} more points. Focus on your weakest dimension.",
points_needed
));
}
suggestions
}
}
impl Default for Gateway {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::reputation_engine::{AttestationMetadata, AttestationRecord};
#[test]
fn test_layer_1_always_allowed() {
let gateway = Gateway::new();
let action = SwarmAction::CaptureReward {
merchant_id: "merchant123".to_string(),
amount: 1000,
transaction_ref: "tx123".to_string(),
};
let check = gateway.check_permission("new_user", &action).unwrap();
assert!(check.allowed);
assert_eq!(check.required_score, 0);
}
#[test]
fn test_layer_22_requires_swarm_threshold() {
let gateway = Gateway::new();
let action = SwarmAction::CoordinateSwarm {
task_id: "task123".to_string(),
sub_agents: vec!["agent1".to_string(), "agent2".to_string()],
task_allocation: HashMap::new(),
total_budget: 10000,
coordination_fee_bps: 500,
};
let check = gateway.check_permission("new_user", &action).unwrap();
assert!(!check.allowed);
assert_eq!(check.required_score, SWARM_COORDINATOR_THRESHOLD);
assert!(check.points_needed.is_some());
}
#[test]
fn test_enforce_swarm_returns_403() {
let gateway = Gateway::new();
let result = gateway.enforce_swarm_permission("new_user");
assert!(result.is_err());
match result.unwrap_err() {
GatewayError::Forbidden { required_threshold, .. } => {
assert_eq!(required_threshold, SWARM_COORDINATOR_THRESHOLD);
}
_ => panic!("Expected Forbidden error"),
}
}
#[test]
fn test_reputation_increases_access() {
let gateway = Gateway::new();
for i in 0..100 {
let attestation = AttestationRecord {
layer: CaptureLayer::Shopping,
timestamp: 1711497600 + i * 86400,
positive: true,
magnitude: 100_000_000,
metadata: Some(AttestationMetadata {
lock_duration_days: Some(365),
held_to_maturity: Some(true),
..Default::default()
}),
};
gateway.record_attestation("power_user", &attestation).unwrap();
}
for i in 0..50 {
let attestation = AttestationRecord {
layer: CaptureLayer::Skill,
timestamp: 1711497600 + i * 86400,
positive: true,
magnitude: 10_000_000,
metadata: Some(AttestationMetadata {
accuracy_percent: Some(95),
..Default::default()
}),
};
gateway.record_attestation("power_user", &attestation).unwrap();
}
let score = gateway.get_reputation("power_user").unwrap();
println!("Power user score: {}", score.composite);
assert!(score.composite > 300);
}
#[test]
fn test_rate_limiting() {
let gateway = Gateway::with_rate_limit(5);
for _ in 0..5 {
assert!(gateway.check_rate_limit("agent1").is_ok());
}
let result = gateway.check_rate_limit("agent1");
assert!(result.is_err());
match result.unwrap_err() {
GatewayError::RateLimited { .. } => {}
_ => panic!("Expected RateLimited error"),
}
}
#[test]
fn test_action_layer_mapping() {
let capture = SwarmAction::CaptureReward {
merchant_id: "m".to_string(),
amount: 100,
transaction_ref: "t".to_string(),
};
assert_eq!(capture.layer(), CaptureLayer::Shopping);
assert_eq!(capture.required_score(), 0);
let swarm = SwarmAction::CoordinateSwarm {
task_id: "t".to_string(),
sub_agents: vec![],
task_allocation: HashMap::new(),
total_budget: 0,
coordination_fee_bps: 0,
};
assert_eq!(swarm.layer(), CaptureLayer::SwarmCoordinationFee);
assert_eq!(swarm.required_score(), SWARM_COORDINATOR_THRESHOLD);
assert!(swarm.is_swarm_action());
}
fn build_user_to_tier(gateway: &Gateway, user: &str, target_tier: TrustTier) {
let (shopping_count, skill_count, infra_count, social_count, magnitude_mult) = match target_tier {
TrustTier::Newcomer => (0, 0, 0, 0, 1),
TrustTier::Established => (30, 15, 15, 10, 1), TrustTier::Trusted => (60, 40, 40, 20, 1), TrustTier::Power => (150, 100, 100, 50, 2), TrustTier::Elite => (500, 400, 400, 200, 5), };
let base_mag = 100_000_000u64 * magnitude_mult as u64;
for i in 0..shopping_count {
let att = AttestationRecord {
layer: CaptureLayer::Shopping,
timestamp: 1711497600 + i * 86400,
positive: true,
magnitude: base_mag,
metadata: Some(AttestationMetadata {
lock_duration_days: Some(365),
held_to_maturity: Some(true),
..Default::default()
}),
};
gateway.record_attestation(user, &att).unwrap();
}
for i in 0..skill_count {
let att = AttestationRecord {
layer: CaptureLayer::Skill,
timestamp: 1711497600 + i * 86400,
positive: true,
magnitude: base_mag / 2,
metadata: Some(AttestationMetadata {
accuracy_percent: Some(95),
..Default::default()
}),
};
gateway.record_attestation(user, &att).unwrap();
}
for i in 0..infra_count {
let att = AttestationRecord {
layer: CaptureLayer::Network,
timestamp: 1711497600 + i * 86400,
positive: true,
magnitude: base_mag / 3,
metadata: Some(AttestationMetadata {
uptime_percent: Some(99),
..Default::default()
}),
};
gateway.record_attestation(user, &att).unwrap();
}
for i in 0..social_count {
let att = AttestationRecord {
layer: CaptureLayer::Social,
timestamp: 1711497600 + i * 86400,
positive: true,
magnitude: base_mag / 5,
metadata: None,
};
gateway.record_attestation(user, &att).unwrap();
}
}
#[test]
fn test_trusted_user_sub_agent_limit() {
let gateway = Gateway::new();
let user = "trusted_user";
build_user_to_tier(&gateway, user, TrustTier::Trusted);
let score = gateway.get_reputation(user).unwrap();
println!("Trusted user score: {}, tier: {:?}", score.composite, score.tier);
assert!(score.composite >= 400 && score.composite < 600,
"Expected Trusted tier (400-599), got {}", score.composite);
assert_eq!(score.tier.max_sub_agents(), 3);
for i in 1..=3 {
let agent = format!("sub_agent_{}", i);
let result = gateway.hire_sub_agent(user, &agent, "task_1", 10_000_000);
assert!(result.is_ok(), "Hire {} should succeed", i);
let hire = result.unwrap();
assert!(hire.success);
assert_eq!(hire.current_hired_count, i);
assert_eq!(hire.fee_captured, 100_000); println!("Hired sub-agent {}: fee={}", i, hire.fee_captured);
}
let result = gateway.hire_sub_agent(user, "sub_agent_4", "task_1", 10_000_000);
assert!(result.is_err(), "4th hire should fail for Trusted user");
match result.unwrap_err() {
GatewayError::Forbidden { message, .. } => {
println!("Correctly denied: {}", message);
assert!(message.contains("limit reached"));
}
other => panic!("Expected Forbidden error, got {:?}", other),
}
let result = gateway.hire_sub_agent(user, "sub_agent_5", "task_1", 10_000_000);
assert!(result.is_err(), "5th hire should also fail");
}
#[test]
fn test_high_reputation_user_can_hire_multiple_sub_agents() {
let gateway = Gateway::new();
let user = "high_rep_user";
build_user_to_tier(&gateway, user, TrustTier::Elite);
let score = gateway.get_reputation(user).unwrap();
let max_allowed = score.tier.max_sub_agents();
println!("High rep user score: {}, tier: {:?}, max_agents: {}",
score.composite, score.tier, max_allowed);
assert!(max_allowed >= 3, "Should have at least Trusted tier (3 agents)");
let hire_count = std::cmp::min(5, max_allowed as usize);
let mut total_fees = 0u64;
for i in 1..=hire_count {
let agent = format!("sub_agent_{}", i);
let result = gateway.hire_sub_agent(user, &agent, "complex_task", 100_000_000);
assert!(result.is_ok(), "Hire {} should succeed (max={})", i, max_allowed);
let hire = result.unwrap();
assert!(hire.success);
assert_eq!(hire.current_hired_count, i);
assert_eq!(hire.fee_captured, 1_000_000); assert_eq!(hire.net_budget, 99_000_000); total_fees += hire.fee_captured;
println!(
"Hired {}: fee={}, net={}, total_fees={}",
agent, hire.fee_captured, hire.net_budget, total_fees
);
}
let (hired, fees, _) = gateway.get_sub_agent_status(user).unwrap();
assert_eq!(hired.len(), hire_count);
assert_eq!(fees, 1_000_000 * hire_count as u64);
println!("Final status: {} hired, {} fees captured", hired.len(), fees);
}
#[test]
fn test_fee_capture_calculation() {
let gateway = Gateway::new();
let user = "fee_test_user";
build_user_to_tier(&gateway, user, TrustTier::Elite);
let score = gateway.get_reputation(user).unwrap();
let max_allowed = score.tier.max_sub_agents();
println!("Fee test user: tier {:?}, max_agents {}", score.tier, max_allowed);
let test_cases = [
(1_000_000, 10_000, 990_000), (10_000_000, 100_000, 9_900_000), (100_000_000, 1_000_000, 99_000_000), ];
let test_count = std::cmp::min(test_cases.len(), max_allowed as usize);
for (i, (budget, expected_fee, expected_net)) in test_cases.iter().take(test_count).enumerate() {
let agent = format!("fee_agent_{}", i);
let result = gateway.hire_sub_agent(user, &agent, "fee_task", *budget).unwrap();
assert_eq!(result.fee_captured, *expected_fee,
"Fee mismatch for budget {}", budget);
assert_eq!(result.net_budget, *expected_net,
"Net budget mismatch for budget {}", budget);
assert_eq!(result.fee_captured + result.net_budget, *budget);
println!("Budget {}: fee={} (1%), net={}",
budget, result.fee_captured, result.net_budget);
}
}
#[test]
fn test_swarm_coordination_with_fees() {
let gateway = Gateway::new();
let user = "swarm_coord_user";
build_user_to_tier(&gateway, user, TrustTier::Elite);
let score = gateway.get_reputation(user).unwrap();
println!("Swarm test user: score {}, tier {:?}", score.composite, score.tier);
if score.composite < SWARM_COORDINATOR_THRESHOLD {
println!("SKIP: Score {} < swarm threshold {}. Scoring caps prevent testing.",
score.composite, SWARM_COORDINATOR_THRESHOLD);
let agents: Vec<String> = (1..=3).map(|i| format!("swarm_agent_{}", i)).collect();
let result = gateway.coordinate_swarm(user, "swarm_task_1", &agents, 50_000_000);
assert!(result.is_err(), "Should fail with insufficient reputation");
return;
}
let max_allowed = score.tier.max_sub_agents() as usize;
let agent_count = std::cmp::min(5, max_allowed);
let agents: Vec<String> = (1..=agent_count).map(|i| format!("swarm_agent_{}", i)).collect();
let budget_per = 50_000_000u64;
let result = gateway.coordinate_swarm(user, "swarm_task_1", &agents, budget_per).unwrap();
assert!(result.success);
assert_eq!(result.hired_agents.len(), agent_count);
assert!(result.failed_agents.is_empty());
let expected_fee_per = 500_000u64;
let expected_net_per = 49_500_000u64;
assert_eq!(result.total_fees_captured, expected_fee_per * agent_count as u64);
assert_eq!(result.total_net_budget, expected_net_per * agent_count as u64);
println!("Swarm coordination: {} agents, {} total fees, {} total net",
result.hired_agents.len(), result.total_fees_captured, result.total_net_budget);
}
#[test]
fn test_referral_registration() {
let gateway = Gateway::new();
let result = gateway.register_referral("agent_123", "user_456");
assert!(result.is_ok());
let result = gateway.register_referral("agent_789", "user_456");
assert!(result.is_err());
}
#[test]
fn test_referral_bounty_requires_tier1_credential() {
let gateway = Gateway::new();
build_user_to_tier(&gateway, "referrer_agent", TrustTier::Established);
gateway.register_referral("referrer_agent", "referred_user").unwrap();
let result = gateway.process_referral_bounty("referred_user", 0).unwrap();
assert!(!result.success);
assert!(result.failure_reason.is_some());
println!("Tier 0 correctly rejected: {:?}", result.failure_reason);
let result = gateway.process_referral_bounty("referred_user", 1).unwrap();
assert!(result.success);
assert!(result.reputation_boost > 0);
println!("Bounty awarded: {} reputation boost", result.reputation_boost);
}
#[test]
fn test_referral_bounty_is_10_percent() {
let gateway = Gateway::new();
build_user_to_tier(&gateway, "big_agent", TrustTier::Trusted);
let initial_score = gateway.get_reputation("big_agent").unwrap().composite;
println!("Agent initial score: {}", initial_score);
gateway.register_referral("big_agent", "new_pioneer").unwrap();
let result = gateway.process_referral_bounty("new_pioneer", 2).unwrap();
let expected_bounty = (initial_score * 1000) / 10000;
assert_eq!(result.reputation_boost, expected_bounty);
println!("10% bounty: {} (from base score {})", result.reputation_boost, initial_score);
}
#[test]
fn test_referral_bounty_only_paid_once() {
let gateway = Gateway::new();
build_user_to_tier(&gateway, "agent_once", TrustTier::Established);
gateway.register_referral("agent_once", "user_once").unwrap();
let result1 = gateway.process_referral_bounty("user_once", 1).unwrap();
assert!(result1.success);
let first_boost = result1.reputation_boost;
let result2 = gateway.process_referral_bounty("user_once", 1).unwrap();
assert!(!result2.success);
assert_eq!(result2.reputation_boost, 0);
println!("Correctly prevented double-payment. First boost: {}", first_boost);
}
#[test]
fn test_referral_stats_tracking() {
let gateway = Gateway::new();
build_user_to_tier(&gateway, "stats_agent", TrustTier::Trusted);
for i in 1..=5 {
gateway.register_referral("stats_agent", &format!("referred_{}", i)).unwrap();
}
for i in 1..=3 {
gateway.process_referral_bounty(&format!("referred_{}", i), i as u8).unwrap();
}
let (referrals, total_bounties) = gateway.get_agent_referral_stats("stats_agent").unwrap();
assert_eq!(referrals.len(), 5);
let paid_count = referrals.iter().filter(|r| r.bounty_paid).count();
assert_eq!(paid_count, 3);
assert!(total_bounties > 0);
println!("Agent stats: {} referrals, {} total bounty earned", referrals.len(), total_bounties);
}
#[test]
fn test_referral_bounty_non_referred_user() {
let gateway = Gateway::new();
let result = gateway.process_referral_bounty("random_user", 5).unwrap();
assert!(!result.success);
assert!(result.failure_reason.unwrap().contains("not referred"));
}
}