1use crate::reputation_engine::{
12 ReputationEngine, TrustScore, TrustTier, CaptureLayer, AttestationRecord,
13 SWARM_COORDINATOR_THRESHOLD, COLLATERAL_THRESHOLD,
14};
15use serde::{Deserialize, Serialize};
16use std::collections::{HashMap, HashSet};
17use std::sync::{Arc, RwLock};
18
19pub const SWARM_MANAGEMENT_FEE_BPS: u64 = 100;
26
27pub const MIN_HIRE_AMOUNT: u64 = 1_000_000; pub const REFERRAL_BOUNTY_BPS: u32 = 1000;
32
33pub const REFERRAL_BOUNTY_MIN_CREDENTIAL_TIER: u8 = 1;
35
36#[derive(Debug, Default)]
42pub struct ReferralRegistry {
43 referrals: HashMap<String, ReferralRecord>,
45 agent_bounties: HashMap<String, u32>,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct ReferralRecord {
52 pub referrer_agent: String,
54 pub referred_user: String,
56 pub has_qualified_credential: bool,
58 pub credential_tier: Option<u8>,
60 pub bounty_paid: bool,
62 pub bounty_amount: Option<u32>,
64 pub created_at: u64,
66 pub paid_at: Option<u64>,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct ReferralBountyResult {
73 pub success: bool,
75 pub referrer_agent: String,
77 pub referred_user: String,
79 pub reputation_boost: u32,
81 pub referrer_new_score: Option<u32>,
83 pub failure_reason: Option<String>,
85}
86
87impl ReferralRegistry {
88 pub fn new() -> Self {
89 Self::default()
90 }
91
92 pub fn register_referral(
94 &mut self,
95 referrer_agent: &str,
96 referred_user: &str,
97 timestamp: u64,
98 ) -> Result<(), String> {
99 if self.referrals.contains_key(referred_user) {
101 return Err("User already has a referrer".to_string());
102 }
103
104 self.referrals.insert(
105 referred_user.to_string(),
106 ReferralRecord {
107 referrer_agent: referrer_agent.to_string(),
108 referred_user: referred_user.to_string(),
109 has_qualified_credential: false,
110 credential_tier: None,
111 bounty_paid: false,
112 bounty_amount: None,
113 created_at: timestamp,
114 paid_at: None,
115 },
116 );
117
118 Ok(())
119 }
120
121 pub fn update_credential_tier(
123 &mut self,
124 referred_user: &str,
125 credential_tier: u8,
126 ) -> bool {
127 if let Some(record) = self.referrals.get_mut(referred_user) {
128 record.credential_tier = Some(credential_tier);
129 record.has_qualified_credential = credential_tier >= REFERRAL_BOUNTY_MIN_CREDENTIAL_TIER;
130 return record.has_qualified_credential;
131 }
132 false
133 }
134
135 pub fn get_referrer(&self, user: &str) -> Option<&str> {
137 self.referrals.get(user).map(|r| r.referrer_agent.as_str())
138 }
139
140 pub fn is_bounty_eligible(&self, referred_user: &str) -> bool {
142 self.referrals
143 .get(referred_user)
144 .map(|r| r.has_qualified_credential && !r.bounty_paid)
145 .unwrap_or(false)
146 }
147
148 pub fn mark_bounty_paid(
150 &mut self,
151 referred_user: &str,
152 bounty_amount: u32,
153 timestamp: u64,
154 ) -> bool {
155 if let Some(record) = self.referrals.get_mut(referred_user) {
156 if record.has_qualified_credential && !record.bounty_paid {
157 record.bounty_paid = true;
158 record.bounty_amount = Some(bounty_amount);
159 record.paid_at = Some(timestamp);
160
161 *self
163 .agent_bounties
164 .entry(record.referrer_agent.clone())
165 .or_default() += bounty_amount;
166
167 return true;
168 }
169 }
170 false
171 }
172
173 pub fn get_agent_bounties(&self, agent_pubkey: &str) -> u32 {
175 self.agent_bounties.get(agent_pubkey).copied().unwrap_or(0)
176 }
177
178 pub fn get_agent_referrals(&self, agent_pubkey: &str) -> Vec<&ReferralRecord> {
180 self.referrals
181 .values()
182 .filter(|r| r.referrer_agent == agent_pubkey)
183 .collect()
184 }
185}
186
187#[derive(Debug, Clone, Serialize, Deserialize)]
192pub enum GatewayError {
193 Unauthorized {
195 message: String,
196 },
197 Forbidden {
199 message: String,
200 required_threshold: u32,
201 current_score: u32,
202 required_tier: String,
203 current_tier: String,
204 },
205 BadRequest {
207 message: String,
208 },
209 RateLimited {
211 retry_after_ms: u64,
212 },
213 Internal {
215 message: String,
216 },
217}
218
219impl std::fmt::Display for GatewayError {
220 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
221 match self {
222 GatewayError::Unauthorized { message } => {
223 write!(f, "Unauthorized: {}", message)
224 }
225 GatewayError::Forbidden { message, required_threshold, current_score, .. } => {
226 write!(f, "Forbidden: {} (need {}, have {})", message, required_threshold, current_score)
227 }
228 GatewayError::BadRequest { message } => {
229 write!(f, "Bad Request: {}", message)
230 }
231 GatewayError::RateLimited { retry_after_ms } => {
232 write!(f, "Rate Limited: retry after {}ms", retry_after_ms)
233 }
234 GatewayError::Internal { message } => {
235 write!(f, "Internal Error: {}", message)
236 }
237 }
238 }
239}
240
241impl std::error::Error for GatewayError {}
242
243#[derive(Debug, Default)]
249pub struct SubAgentRegistry {
250 active_hires: HashMap<String, HashSet<String>>,
252 fees_captured: HashMap<String, u64>,
254}
255
256impl SubAgentRegistry {
257 pub fn new() -> Self {
258 Self::default()
259 }
260
261 pub fn hired_count(&self, user_pubkey: &str) -> usize {
263 self.active_hires
264 .get(user_pubkey)
265 .map(|s| s.len())
266 .unwrap_or(0)
267 }
268
269 pub fn is_hired(&self, user_pubkey: &str, sub_agent_pubkey: &str) -> bool {
271 self.active_hires
272 .get(user_pubkey)
273 .map(|s| s.contains(sub_agent_pubkey))
274 .unwrap_or(false)
275 }
276
277 pub fn add_hire(&mut self, user_pubkey: &str, sub_agent_pubkey: &str) {
279 self.active_hires
280 .entry(user_pubkey.to_string())
281 .or_default()
282 .insert(sub_agent_pubkey.to_string());
283 }
284
285 pub fn remove_hire(&mut self, user_pubkey: &str, sub_agent_pubkey: &str) -> bool {
287 self.active_hires
288 .get_mut(user_pubkey)
289 .map(|s| s.remove(sub_agent_pubkey))
290 .unwrap_or(false)
291 }
292
293 pub fn record_fee(&mut self, user_pubkey: &str, fee_lamports: u64) {
295 *self.fees_captured.entry(user_pubkey.to_string()).or_default() += fee_lamports;
296 }
297
298 pub fn total_fees(&self, user_pubkey: &str) -> u64 {
300 self.fees_captured.get(user_pubkey).copied().unwrap_or(0)
301 }
302
303 pub fn list_hires(&self, user_pubkey: &str) -> Vec<String> {
305 self.active_hires
306 .get(user_pubkey)
307 .map(|s| s.iter().cloned().collect())
308 .unwrap_or_default()
309 }
310}
311
312#[derive(Debug, Clone, Serialize, Deserialize)]
314pub struct HireResult {
315 pub success: bool,
317 pub sub_agent_pubkey: String,
319 pub task_id: String,
321 pub net_budget: u64,
323 pub fee_captured: u64,
325 pub current_hired_count: usize,
327 pub max_allowed: u8,
329 pub user_tier: TrustTier,
331}
332
333#[derive(Debug, Clone, Serialize, Deserialize)]
335pub struct SwarmCoordinationResult {
336 pub success: bool,
338 pub task_id: String,
340 pub hired_agents: Vec<String>,
342 pub total_net_budget: u64,
344 pub total_fees_captured: u64,
346 pub failed_agents: Vec<(String, String)>, }
349
350#[derive(Debug, Clone, Serialize, Deserialize)]
356pub struct A2ARequest {
357 pub agent_pubkey: String,
359 pub user_pubkey: String,
361 pub session_token: String,
363 pub action: SwarmAction,
365 pub params: Option<serde_json::Value>,
367 pub timestamp: u64,
369 pub signature: Option<String>,
371}
372
373#[derive(Debug, Clone, Serialize, Deserialize)]
375#[serde(tag = "type")]
376pub enum SwarmAction {
377 CaptureReward {
380 merchant_id: String,
381 amount: u64,
382 transaction_ref: String,
383 },
384 TrackReferral {
386 referral_code: String,
387 conversion_amount: Option<u64>,
388 },
389 SubmitAttention {
391 content_id: String,
392 duration_ms: u64,
393 },
394 LicenseData {
396 data_type: String,
397 buyer: String,
398 terms_hash: String,
399 },
400 JoinInsurancePool {
402 pool_id: String,
403 coverage_amount: u64,
404 },
405
406 RegisterCompute {
409 gpu_type: String,
410 available_hours: u32,
411 },
412 RunNode {
414 node_type: String,
415 uptime_commitment: u8,
416 },
417 EnergyTrade {
419 direction: String, kwh: f64,
421 price_per_kwh: f64,
422 },
423
424 LicenseSkill {
427 skill_type: String,
428 license_terms: String,
429 },
430 SubmitCuration {
432 signal_type: String,
433 signal_data: String,
434 },
435 SocialAction {
437 action_type: String,
438 target: String,
439 },
440
441 DeployLiquidity {
444 pool: String,
445 amount: u64,
446 strategy: String,
447 },
448 CastVote {
450 proposal_id: String,
451 vote: String,
452 delegation_power: u64,
453 },
454 BulkPurchase {
456 merchant_id: String,
457 items: Vec<String>,
458 max_spend: u64,
459 },
460
461 HireSubAgent {
464 sub_agent_pubkey: String,
465 task_description: String,
466 max_budget: u64,
467 deadline: u64,
468 },
469
470 PledgeReputation {
473 protocol: String,
474 loan_amount: u64,
475 collateral_score: u32,
476 },
477
478 CoordinateSwarm {
481 task_id: String,
482 sub_agents: Vec<String>,
483 task_allocation: HashMap<String, String>,
484 total_budget: u64,
485 coordination_fee_bps: u16,
486 },
487 AcceptContract {
489 contract_id: String,
490 requester: String,
491 deliverables: Vec<String>,
492 payment: u64,
493 },
494 DistributeRewards {
496 task_id: String,
497 distributions: HashMap<String, u64>,
498 },
499}
500
501impl SwarmAction {
502 pub fn layer(&self) -> CaptureLayer {
504 match self {
505 SwarmAction::CaptureReward { .. } => CaptureLayer::Shopping,
506 SwarmAction::TrackReferral { .. } => CaptureLayer::Referral,
507 SwarmAction::SubmitAttention { .. } => CaptureLayer::Attention,
508 SwarmAction::LicenseData { .. } => CaptureLayer::Data,
509 SwarmAction::JoinInsurancePool { .. } => CaptureLayer::Insurance,
510 SwarmAction::RegisterCompute { .. } => CaptureLayer::Compute,
511 SwarmAction::RunNode { .. } => CaptureLayer::Network,
512 SwarmAction::EnergyTrade { .. } => CaptureLayer::Energy,
513 SwarmAction::LicenseSkill { .. } => CaptureLayer::Skill,
514 SwarmAction::SubmitCuration { .. } => CaptureLayer::CurationSignal,
515 SwarmAction::SocialAction { .. } => CaptureLayer::Social,
516 SwarmAction::DeployLiquidity { .. } => CaptureLayer::Liquidity,
517 SwarmAction::CastVote { .. } => CaptureLayer::GovernanceProxy,
518 SwarmAction::BulkPurchase { .. } => CaptureLayer::InventoryArbitrage,
519 SwarmAction::HireSubAgent { .. } => CaptureLayer::SubAgentManager,
520 SwarmAction::PledgeReputation { .. } => CaptureLayer::ReputationCollateral,
521 SwarmAction::CoordinateSwarm { .. } => CaptureLayer::SwarmCoordinationFee,
522 SwarmAction::AcceptContract { .. } => CaptureLayer::SwarmCoordinationFee,
523 SwarmAction::DistributeRewards { .. } => CaptureLayer::SwarmCoordinationFee,
524 }
525 }
526
527 pub fn required_score(&self) -> u32 {
529 match self.layer() {
530 CaptureLayer::Shopping
532 | CaptureLayer::Referral
533 | CaptureLayer::Attention
534 | CaptureLayer::Data
535 | CaptureLayer::Insurance
536 | CaptureLayer::Compute
537 | CaptureLayer::Network
538 | CaptureLayer::Energy
539 | CaptureLayer::DePINAggregator
540 | CaptureLayer::InferenceArbitrage
541 | CaptureLayer::StorageDePIN
542 | CaptureLayer::Skill
543 | CaptureLayer::CurationSignal
544 | CaptureLayer::Social
545 | CaptureLayer::KnowledgeAPI
546 | CaptureLayer::PersonalModelLicensing => 0,
547
548 CaptureLayer::Liquidity
550 | CaptureLayer::GovernanceProxy
551 | CaptureLayer::InventoryArbitrage
552 | CaptureLayer::SubAgentManager
553 | CaptureLayer::ReputationCollateral => COLLATERAL_THRESHOLD,
554
555 CaptureLayer::SwarmCoordinationFee => SWARM_COORDINATOR_THRESHOLD,
557 }
558 }
559
560 pub fn is_swarm_action(&self) -> bool {
562 matches!(
563 self,
564 SwarmAction::CoordinateSwarm { .. }
565 | SwarmAction::AcceptContract { .. }
566 | SwarmAction::DistributeRewards { .. }
567 )
568 }
569
570 pub fn requires_collateral(&self) -> bool {
572 self.required_score() >= COLLATERAL_THRESHOLD
573 }
574}
575
576#[derive(Debug, Clone, Serialize, Deserialize)]
581pub struct A2AResponse {
582 pub success: bool,
584 pub request_id: String,
586 pub data: Option<serde_json::Value>,
588 pub error: Option<GatewayError>,
590 pub processing_ms: u64,
592 pub reputation_delta: Option<i32>,
594}
595
596#[derive(Debug, Clone, Serialize, Deserialize)]
601pub struct PermissionCheck {
602 pub allowed: bool,
604 pub layer: String,
606 pub required_score: u32,
608 pub current_score: u32,
610 pub current_tier: TrustTier,
612 pub required_tier: TrustTier,
614 pub threshold_checked: Option<String>,
616 pub points_needed: Option<u32>,
618 pub unlock_suggestions: Vec<String>,
620}
621
622pub struct Gateway {
628 reputation_cache: Arc<RwLock<HashMap<String, ReputationEngine>>>,
630 rate_limits: Arc<RwLock<HashMap<String, RateLimitState>>>,
632 max_requests_per_minute: u32,
634 sub_agent_registry: Arc<RwLock<SubAgentRegistry>>,
636 referral_registry: Arc<RwLock<ReferralRegistry>>,
638}
639
640struct RateLimitState {
641 count: u32,
642 window_start: u64,
643}
644
645impl Gateway {
646 pub fn new() -> Self {
648 Self {
649 reputation_cache: Arc::new(RwLock::new(HashMap::new())),
650 rate_limits: Arc::new(RwLock::new(HashMap::new())),
651 max_requests_per_minute: 100,
652 sub_agent_registry: Arc::new(RwLock::new(SubAgentRegistry::new())),
653 referral_registry: Arc::new(RwLock::new(ReferralRegistry::new())),
654 }
655 }
656
657 pub fn with_rate_limit(max_requests_per_minute: u32) -> Self {
659 Self {
660 reputation_cache: Arc::new(RwLock::new(HashMap::new())),
661 rate_limits: Arc::new(RwLock::new(HashMap::new())),
662 max_requests_per_minute,
663 sub_agent_registry: Arc::new(RwLock::new(SubAgentRegistry::new())),
664 referral_registry: Arc::new(RwLock::new(ReferralRegistry::new())),
665 }
666 }
667
668 pub fn check_permission(
695 &self,
696 user_pubkey: &str,
697 action: &SwarmAction,
698 ) -> Result<PermissionCheck, GatewayError> {
699 let mut cache = self.reputation_cache.write().map_err(|_| {
701 GatewayError::Internal {
702 message: "Failed to acquire reputation cache lock".to_string(),
703 }
704 })?;
705
706 let engine = cache
707 .entry(user_pubkey.to_string())
708 .or_insert_with(|| ReputationEngine::new(user_pubkey.to_string()));
709
710 let trust_score = engine.calculate_trust_score();
712 let current_score = trust_score.composite;
713 let current_tier = trust_score.tier;
714
715 let required_score = action.required_score();
717 let required_tier = TrustTier::from_score(required_score);
718 let layer = format!("{:?}", action.layer());
719
720 let allowed = current_score >= required_score;
724
725 let threshold_checked = if action.is_swarm_action() {
727 Some("swarm_coordinator".to_string())
728 } else if action.requires_collateral() {
729 Some("collateral_eligible".to_string())
730 } else {
731 None
732 };
733
734 let (points_needed, unlock_suggestions) = if !allowed {
736 let needed = required_score - current_score;
737 let suggestions = self.generate_unlock_suggestions(needed, &trust_score);
738 (Some(needed), suggestions)
739 } else {
740 (None, vec![])
741 };
742
743 Ok(PermissionCheck {
744 allowed,
745 layer,
746 required_score,
747 current_score,
748 current_tier,
749 required_tier,
750 threshold_checked,
751 points_needed,
752 unlock_suggestions,
753 })
754 }
755
756 pub fn enforce_permission(
760 &self,
761 user_pubkey: &str,
762 action: &SwarmAction,
763 ) -> Result<(), GatewayError> {
764 let check = self.check_permission(user_pubkey, action)?;
765
766 if !check.allowed {
767 return Err(GatewayError::Forbidden {
768 message: format!(
769 "Insufficient reputation for {:?} (Layer {}). Need {} points, have {}.",
770 action.layer(),
771 action.layer() as u8,
772 check.required_score,
773 check.current_score
774 ),
775 required_threshold: check.required_score,
776 current_score: check.current_score,
777 required_tier: format!("{:?}", check.required_tier),
778 current_tier: format!("{:?}", check.current_tier),
779 });
780 }
781
782 Ok(())
783 }
784
785 pub fn enforce_swarm_permission(&self, user_pubkey: &str) -> Result<(), GatewayError> {
789 let mut cache = self.reputation_cache.write().map_err(|_| {
790 GatewayError::Internal {
791 message: "Failed to acquire reputation cache lock".to_string(),
792 }
793 })?;
794
795 let engine = cache
796 .entry(user_pubkey.to_string())
797 .or_insert_with(|| ReputationEngine::new(user_pubkey.to_string()));
798
799 let trust_score = engine.calculate_trust_score();
800
801 let swarm_allowed = trust_score
803 .threshold_proofs
804 .get("swarm_coordinator")
805 .copied()
806 .unwrap_or(false);
807
808 if !swarm_allowed {
809 return Err(GatewayError::Forbidden {
810 message: format!(
811 "Swarm coordination requires {} reputation points. Current: {}. Tier: {:?}.",
812 SWARM_COORDINATOR_THRESHOLD,
813 trust_score.composite,
814 trust_score.tier
815 ),
816 required_threshold: SWARM_COORDINATOR_THRESHOLD,
817 current_score: trust_score.composite,
818 required_tier: "Trusted".to_string(),
819 current_tier: format!("{:?}", trust_score.tier),
820 });
821 }
822
823 Ok(())
824 }
825
826 pub fn check_rate_limit(&self, agent_pubkey: &str) -> Result<(), GatewayError> {
832 let now = std::time::SystemTime::now()
833 .duration_since(std::time::UNIX_EPOCH)
834 .unwrap()
835 .as_millis() as u64;
836
837 let mut limits = self.rate_limits.write().map_err(|_| {
838 GatewayError::Internal {
839 message: "Failed to acquire rate limit lock".to_string(),
840 }
841 })?;
842
843 let state = limits
844 .entry(agent_pubkey.to_string())
845 .or_insert(RateLimitState {
846 count: 0,
847 window_start: now,
848 });
849
850 if now - state.window_start > 60_000 {
852 state.count = 0;
853 state.window_start = now;
854 }
855
856 if state.count >= self.max_requests_per_minute {
858 let retry_after = 60_000 - (now - state.window_start);
859 return Err(GatewayError::RateLimited {
860 retry_after_ms: retry_after,
861 });
862 }
863
864 state.count += 1;
866
867 Ok(())
868 }
869
870 pub fn process_request(&self, request: &A2ARequest) -> Result<PermissionCheck, GatewayError> {
876 self.check_rate_limit(&request.agent_pubkey)?;
878
879 let permission = self.check_permission(&request.user_pubkey, &request.action)?;
881
882 if !permission.allowed {
884 return Err(GatewayError::Forbidden {
885 message: format!(
886 "Action {:?} requires {} reputation. User has {}.",
887 request.action.layer(),
888 permission.required_score,
889 permission.current_score
890 ),
891 required_threshold: permission.required_score,
892 current_score: permission.current_score,
893 required_tier: format!("{:?}", permission.required_tier),
894 current_tier: format!("{:?}", permission.current_tier),
895 });
896 }
897
898 Ok(permission)
899 }
900
901 pub fn get_reputation(&self, user_pubkey: &str) -> Result<TrustScore, GatewayError> {
907 let mut cache = self.reputation_cache.write().map_err(|_| {
908 GatewayError::Internal {
909 message: "Failed to acquire reputation cache lock".to_string(),
910 }
911 })?;
912
913 let engine = cache
914 .entry(user_pubkey.to_string())
915 .or_insert_with(|| ReputationEngine::new(user_pubkey.to_string()));
916
917 Ok(engine.calculate_trust_score())
918 }
919
920 pub fn record_attestation(
922 &self,
923 user_pubkey: &str,
924 attestation: &crate::reputation_engine::AttestationRecord,
925 ) -> Result<TrustScore, GatewayError> {
926 let mut cache = self.reputation_cache.write().map_err(|_| {
927 GatewayError::Internal {
928 message: "Failed to acquire reputation cache lock".to_string(),
929 }
930 })?;
931
932 let engine = cache
933 .entry(user_pubkey.to_string())
934 .or_insert_with(|| ReputationEngine::new(user_pubkey.to_string()));
935
936 engine.process_attestation(attestation);
937 Ok(engine.calculate_trust_score())
938 }
939
940 pub fn hire_sub_agent(
958 &self,
959 user_pubkey: &str,
960 sub_agent_pubkey: &str,
961 task_id: &str,
962 budget: u64,
963 ) -> Result<HireResult, GatewayError> {
964 if budget < MIN_HIRE_AMOUNT {
966 return Err(GatewayError::BadRequest {
967 message: format!(
968 "Budget too low. Minimum: {} lamports, provided: {}",
969 MIN_HIRE_AMOUNT, budget
970 ),
971 });
972 }
973
974 let trust_score = self.get_reputation(user_pubkey)?;
976 let max_allowed = trust_score.tier.max_sub_agents();
977
978 let action = SwarmAction::HireSubAgent {
980 sub_agent_pubkey: sub_agent_pubkey.to_string(),
981 task_description: task_id.to_string(),
982 max_budget: budget,
983 deadline: 0, };
985 self.enforce_permission(user_pubkey, &action)?;
986
987 let mut registry = self.sub_agent_registry.write().map_err(|_| {
989 GatewayError::Internal {
990 message: "Failed to acquire sub-agent registry lock".to_string(),
991 }
992 })?;
993
994 let current_count = registry.hired_count(user_pubkey);
995
996 if current_count >= max_allowed as usize {
1000 return Err(GatewayError::Forbidden {
1001 message: format!(
1002 "Sub-agent limit reached. Tier {:?} allows max {} sub-agents. Currently hired: {}.",
1003 trust_score.tier, max_allowed, current_count
1004 ),
1005 required_threshold: SWARM_COORDINATOR_THRESHOLD,
1006 current_score: trust_score.composite,
1007 required_tier: format!("{:?}", TrustTier::from_score(SWARM_COORDINATOR_THRESHOLD)),
1008 current_tier: format!("{:?}", trust_score.tier),
1009 });
1010 }
1011
1012 if registry.is_hired(user_pubkey, sub_agent_pubkey) {
1014 return Err(GatewayError::BadRequest {
1015 message: format!("Sub-agent {} is already hired", sub_agent_pubkey),
1016 });
1017 }
1018
1019 let fee = (budget * SWARM_MANAGEMENT_FEE_BPS) / 10_000;
1023 let net_budget = budget - fee;
1024
1025 registry.add_hire(user_pubkey, sub_agent_pubkey);
1027 registry.record_fee(user_pubkey, fee);
1028
1029 Ok(HireResult {
1030 success: true,
1031 sub_agent_pubkey: sub_agent_pubkey.to_string(),
1032 task_id: task_id.to_string(),
1033 net_budget,
1034 fee_captured: fee,
1035 current_hired_count: current_count + 1,
1036 max_allowed,
1037 user_tier: trust_score.tier,
1038 })
1039 }
1040
1041 pub fn coordinate_swarm(
1046 &self,
1047 user_pubkey: &str,
1048 task_id: &str,
1049 sub_agents: &[String],
1050 budget_per_agent: u64,
1051 ) -> Result<SwarmCoordinationResult, GatewayError> {
1052 self.enforce_swarm_permission(user_pubkey)?;
1054
1055 let trust_score = self.get_reputation(user_pubkey)?;
1056 let max_allowed = trust_score.tier.max_sub_agents();
1057
1058 let mut registry = self.sub_agent_registry.write().map_err(|_| {
1060 GatewayError::Internal {
1061 message: "Failed to acquire sub-agent registry lock".to_string(),
1062 }
1063 })?;
1064
1065 let current_count = registry.hired_count(user_pubkey);
1066 let total_after = current_count + sub_agents.len();
1067
1068 if total_after > max_allowed as usize {
1069 return Err(GatewayError::Forbidden {
1070 message: format!(
1071 "Swarm size exceeds limit. Tier {:?} allows {} sub-agents. Current: {}, Requested: {}.",
1072 trust_score.tier, max_allowed, current_count, sub_agents.len()
1073 ),
1074 required_threshold: SWARM_COORDINATOR_THRESHOLD,
1075 current_score: trust_score.composite,
1076 required_tier: "Elite".to_string(),
1077 current_tier: format!("{:?}", trust_score.tier),
1078 });
1079 }
1080
1081 let mut hired_agents = Vec::new();
1082 let mut failed_agents = Vec::new();
1083 let mut total_fees = 0u64;
1084 let mut total_net = 0u64;
1085
1086 for sub_agent in sub_agents {
1088 if registry.is_hired(user_pubkey, sub_agent) {
1089 failed_agents.push((sub_agent.clone(), "Already hired".to_string()));
1090 continue;
1091 }
1092
1093 let fee = (budget_per_agent * SWARM_MANAGEMENT_FEE_BPS) / 10_000;
1095 let net = budget_per_agent - fee;
1096
1097 registry.add_hire(user_pubkey, sub_agent);
1099 registry.record_fee(user_pubkey, fee);
1100
1101 hired_agents.push(sub_agent.clone());
1102 total_fees += fee;
1103 total_net += net;
1104 }
1105
1106 Ok(SwarmCoordinationResult {
1107 success: !hired_agents.is_empty(),
1108 task_id: task_id.to_string(),
1109 hired_agents,
1110 total_net_budget: total_net,
1111 total_fees_captured: total_fees,
1112 failed_agents,
1113 })
1114 }
1115
1116 pub fn release_sub_agent(
1118 &self,
1119 user_pubkey: &str,
1120 sub_agent_pubkey: &str,
1121 ) -> Result<bool, GatewayError> {
1122 let mut registry = self.sub_agent_registry.write().map_err(|_| {
1123 GatewayError::Internal {
1124 message: "Failed to acquire sub-agent registry lock".to_string(),
1125 }
1126 })?;
1127
1128 Ok(registry.remove_hire(user_pubkey, sub_agent_pubkey))
1129 }
1130
1131 pub fn get_sub_agent_status(&self, user_pubkey: &str) -> Result<(Vec<String>, u64, u8), GatewayError> {
1133 let trust_score = self.get_reputation(user_pubkey)?;
1134 let max_allowed = trust_score.tier.max_sub_agents();
1135
1136 let registry = self.sub_agent_registry.read().map_err(|_| {
1137 GatewayError::Internal {
1138 message: "Failed to acquire sub-agent registry lock".to_string(),
1139 }
1140 })?;
1141
1142 let hired = registry.list_hires(user_pubkey);
1143 let fees = registry.total_fees(user_pubkey);
1144
1145 Ok((hired, fees, max_allowed))
1146 }
1147
1148 pub fn register_referral(
1156 &self,
1157 referrer_agent: &str,
1158 referred_user: &str,
1159 ) -> Result<(), GatewayError> {
1160 let now = std::time::SystemTime::now()
1161 .duration_since(std::time::UNIX_EPOCH)
1162 .unwrap()
1163 .as_secs();
1164
1165 let mut registry = self.referral_registry.write().map_err(|_| {
1166 GatewayError::Internal {
1167 message: "Failed to acquire referral registry lock".to_string(),
1168 }
1169 })?;
1170
1171 registry
1172 .register_referral(referrer_agent, referred_user, now)
1173 .map_err(|msg| GatewayError::BadRequest { message: msg })
1174 }
1175
1176 pub fn process_referral_bounty(
1187 &self,
1188 referred_user: &str,
1189 verified_credential_tier: u8,
1190 ) -> Result<ReferralBountyResult, GatewayError> {
1191 let now = std::time::SystemTime::now()
1192 .duration_since(std::time::UNIX_EPOCH)
1193 .unwrap()
1194 .as_secs();
1195
1196 let referrer_agent = {
1198 let registry = self.referral_registry.read().map_err(|_| {
1199 GatewayError::Internal {
1200 message: "Failed to acquire referral registry lock".to_string(),
1201 }
1202 })?;
1203
1204 match registry.get_referrer(referred_user) {
1205 Some(agent) => agent.to_string(),
1206 None => {
1207 return Ok(ReferralBountyResult {
1208 success: false,
1209 referrer_agent: String::new(),
1210 referred_user: referred_user.to_string(),
1211 reputation_boost: 0,
1212 referrer_new_score: None,
1213 failure_reason: Some("User was not referred by an agent".to_string()),
1214 });
1215 }
1216 }
1217 };
1218
1219 {
1221 let mut registry = self.referral_registry.write().map_err(|_| {
1222 GatewayError::Internal {
1223 message: "Failed to acquire referral registry lock".to_string(),
1224 }
1225 })?;
1226
1227 registry.update_credential_tier(referred_user, verified_credential_tier);
1228
1229 if !registry.is_bounty_eligible(referred_user) {
1230 return Ok(ReferralBountyResult {
1231 success: false,
1232 referrer_agent: referrer_agent.clone(),
1233 referred_user: referred_user.to_string(),
1234 reputation_boost: 0,
1235 referrer_new_score: None,
1236 failure_reason: Some(format!(
1237 "Credential tier {} does not meet minimum requirement of Tier {}",
1238 verified_credential_tier, REFERRAL_BOUNTY_MIN_CREDENTIAL_TIER
1239 )),
1240 });
1241 }
1242 }
1243
1244 let referrer_score = self.get_reputation(&referrer_agent)?;
1246 let bounty_amount = (referrer_score.composite * REFERRAL_BOUNTY_BPS) / 10000;
1247
1248 let attestation = AttestationRecord {
1250 layer: CaptureLayer::Referral,
1251 timestamp: now,
1252 positive: true,
1253 magnitude: bounty_amount as u64 * 1_000_000, metadata: Some(crate::reputation_engine::AttestationMetadata {
1255 referral_successful: Some(true),
1256 ..Default::default()
1257 }),
1258 };
1259
1260 let new_score = self.record_attestation(&referrer_agent, &attestation)?;
1261
1262 {
1264 let mut registry = self.referral_registry.write().map_err(|_| {
1265 GatewayError::Internal {
1266 message: "Failed to acquire referral registry lock".to_string(),
1267 }
1268 })?;
1269
1270 registry.mark_bounty_paid(referred_user, bounty_amount, now);
1271 }
1272
1273 Ok(ReferralBountyResult {
1274 success: true,
1275 referrer_agent,
1276 referred_user: referred_user.to_string(),
1277 reputation_boost: bounty_amount,
1278 referrer_new_score: Some(new_score.composite),
1279 failure_reason: None,
1280 })
1281 }
1282
1283 pub fn get_agent_referral_stats(
1285 &self,
1286 agent_pubkey: &str,
1287 ) -> Result<(Vec<ReferralRecord>, u32), GatewayError> {
1288 let registry = self.referral_registry.read().map_err(|_| {
1289 GatewayError::Internal {
1290 message: "Failed to acquire referral registry lock".to_string(),
1291 }
1292 })?;
1293
1294 let referrals: Vec<ReferralRecord> = registry
1295 .get_agent_referrals(agent_pubkey)
1296 .into_iter()
1297 .cloned()
1298 .collect();
1299
1300 let total_bounties = registry.get_agent_bounties(agent_pubkey);
1301
1302 Ok((referrals, total_bounties))
1303 }
1304
1305 fn generate_unlock_suggestions(&self, points_needed: u32, score: &TrustScore) -> Vec<String> {
1311 let mut suggestions = vec![];
1312
1313 if score.reliability < 300 {
1315 suggestions.push(format!(
1316 "Stack CRED for 90+ days to boost Reliability (+{} potential points)",
1317 300 - score.reliability
1318 ));
1319 }
1320
1321 if score.skill < 200 {
1322 suggestions.push(format!(
1323 "Submit high-accuracy data captures to boost Skill (+{} potential points)",
1324 200 - score.skill
1325 ));
1326 }
1327
1328 if score.infrastructure < 200 {
1329 suggestions.push(
1330 "Run a network node or provide compute to boost Infrastructure".to_string(),
1331 );
1332 }
1333
1334 if score.social < 150 {
1335 suggestions.push("Invite friends or complete referrals to boost Social".to_string());
1336 }
1337
1338 if points_needed > 200 {
1340 suggestions.push(format!(
1341 "You need {} more points. Focus on your weakest dimension.",
1342 points_needed
1343 ));
1344 }
1345
1346 suggestions
1347 }
1348}
1349
1350impl Default for Gateway {
1351 fn default() -> Self {
1352 Self::new()
1353 }
1354}
1355
1356#[cfg(test)]
1361mod tests {
1362 use super::*;
1363 use crate::reputation_engine::{AttestationMetadata, AttestationRecord};
1364
1365 #[test]
1366 fn test_layer_1_always_allowed() {
1367 let gateway = Gateway::new();
1368
1369 let action = SwarmAction::CaptureReward {
1370 merchant_id: "merchant123".to_string(),
1371 amount: 1000,
1372 transaction_ref: "tx123".to_string(),
1373 };
1374
1375 let check = gateway.check_permission("new_user", &action).unwrap();
1376 assert!(check.allowed);
1377 assert_eq!(check.required_score, 0);
1378 }
1379
1380 #[test]
1381 fn test_layer_22_requires_swarm_threshold() {
1382 let gateway = Gateway::new();
1383
1384 let action = SwarmAction::CoordinateSwarm {
1385 task_id: "task123".to_string(),
1386 sub_agents: vec!["agent1".to_string(), "agent2".to_string()],
1387 task_allocation: HashMap::new(),
1388 total_budget: 10000,
1389 coordination_fee_bps: 500,
1390 };
1391
1392 let check = gateway.check_permission("new_user", &action).unwrap();
1394 assert!(!check.allowed);
1395 assert_eq!(check.required_score, SWARM_COORDINATOR_THRESHOLD);
1396 assert!(check.points_needed.is_some());
1397 }
1398
1399 #[test]
1400 fn test_enforce_swarm_returns_403() {
1401 let gateway = Gateway::new();
1402
1403 let result = gateway.enforce_swarm_permission("new_user");
1404 assert!(result.is_err());
1405
1406 match result.unwrap_err() {
1407 GatewayError::Forbidden { required_threshold, .. } => {
1408 assert_eq!(required_threshold, SWARM_COORDINATOR_THRESHOLD);
1409 }
1410 _ => panic!("Expected Forbidden error"),
1411 }
1412 }
1413
1414 #[test]
1415 fn test_reputation_increases_access() {
1416 let gateway = Gateway::new();
1417
1418 for i in 0..100 {
1420 let attestation = AttestationRecord {
1421 layer: CaptureLayer::Shopping,
1422 timestamp: 1711497600 + i * 86400,
1423 positive: true,
1424 magnitude: 100_000_000,
1425 metadata: Some(AttestationMetadata {
1426 lock_duration_days: Some(365),
1427 held_to_maturity: Some(true),
1428 ..Default::default()
1429 }),
1430 };
1431 gateway.record_attestation("power_user", &attestation).unwrap();
1432 }
1433
1434 for i in 0..50 {
1436 let attestation = AttestationRecord {
1437 layer: CaptureLayer::Skill,
1438 timestamp: 1711497600 + i * 86400,
1439 positive: true,
1440 magnitude: 10_000_000,
1441 metadata: Some(AttestationMetadata {
1442 accuracy_percent: Some(95),
1443 ..Default::default()
1444 }),
1445 };
1446 gateway.record_attestation("power_user", &attestation).unwrap();
1447 }
1448
1449 let score = gateway.get_reputation("power_user").unwrap();
1450 println!("Power user score: {}", score.composite);
1451
1452 assert!(score.composite > 300);
1454 }
1455
1456 #[test]
1457 fn test_rate_limiting() {
1458 let gateway = Gateway::with_rate_limit(5);
1459
1460 for _ in 0..5 {
1462 assert!(gateway.check_rate_limit("agent1").is_ok());
1463 }
1464
1465 let result = gateway.check_rate_limit("agent1");
1467 assert!(result.is_err());
1468
1469 match result.unwrap_err() {
1470 GatewayError::RateLimited { .. } => {}
1471 _ => panic!("Expected RateLimited error"),
1472 }
1473 }
1474
1475 #[test]
1476 fn test_action_layer_mapping() {
1477 let capture = SwarmAction::CaptureReward {
1478 merchant_id: "m".to_string(),
1479 amount: 100,
1480 transaction_ref: "t".to_string(),
1481 };
1482 assert_eq!(capture.layer(), CaptureLayer::Shopping);
1483 assert_eq!(capture.required_score(), 0);
1484
1485 let swarm = SwarmAction::CoordinateSwarm {
1486 task_id: "t".to_string(),
1487 sub_agents: vec![],
1488 task_allocation: HashMap::new(),
1489 total_budget: 0,
1490 coordination_fee_bps: 0,
1491 };
1492 assert_eq!(swarm.layer(), CaptureLayer::SwarmCoordinationFee);
1493 assert_eq!(swarm.required_score(), SWARM_COORDINATOR_THRESHOLD);
1494 assert!(swarm.is_swarm_action());
1495 }
1496
1497 fn build_user_to_tier(gateway: &Gateway, user: &str, target_tier: TrustTier) {
1503 let (shopping_count, skill_count, infra_count, social_count, magnitude_mult) = match target_tier {
1507 TrustTier::Newcomer => (0, 0, 0, 0, 1),
1508 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), };
1513
1514 let base_mag = 100_000_000u64 * magnitude_mult as u64;
1515
1516 for i in 0..shopping_count {
1517 let att = AttestationRecord {
1518 layer: CaptureLayer::Shopping,
1519 timestamp: 1711497600 + i * 86400,
1520 positive: true,
1521 magnitude: base_mag,
1522 metadata: Some(AttestationMetadata {
1523 lock_duration_days: Some(365),
1524 held_to_maturity: Some(true),
1525 ..Default::default()
1526 }),
1527 };
1528 gateway.record_attestation(user, &att).unwrap();
1529 }
1530
1531 for i in 0..skill_count {
1532 let att = AttestationRecord {
1533 layer: CaptureLayer::Skill,
1534 timestamp: 1711497600 + i * 86400,
1535 positive: true,
1536 magnitude: base_mag / 2,
1537 metadata: Some(AttestationMetadata {
1538 accuracy_percent: Some(95),
1539 ..Default::default()
1540 }),
1541 };
1542 gateway.record_attestation(user, &att).unwrap();
1543 }
1544
1545 for i in 0..infra_count {
1546 let att = AttestationRecord {
1547 layer: CaptureLayer::Network,
1548 timestamp: 1711497600 + i * 86400,
1549 positive: true,
1550 magnitude: base_mag / 3,
1551 metadata: Some(AttestationMetadata {
1552 uptime_percent: Some(99),
1553 ..Default::default()
1554 }),
1555 };
1556 gateway.record_attestation(user, &att).unwrap();
1557 }
1558
1559 for i in 0..social_count {
1560 let att = AttestationRecord {
1561 layer: CaptureLayer::Social,
1562 timestamp: 1711497600 + i * 86400,
1563 positive: true,
1564 magnitude: base_mag / 5,
1565 metadata: None,
1566 };
1567 gateway.record_attestation(user, &att).unwrap();
1568 }
1569 }
1570
1571 #[test]
1572 fn test_trusted_user_sub_agent_limit() {
1573 let gateway = Gateway::new();
1575 let user = "trusted_user";
1576
1577 build_user_to_tier(&gateway, user, TrustTier::Trusted);
1579
1580 let score = gateway.get_reputation(user).unwrap();
1581 println!("Trusted user score: {}, tier: {:?}", score.composite, score.tier);
1582 assert!(score.composite >= 400 && score.composite < 600,
1583 "Expected Trusted tier (400-599), got {}", score.composite);
1584 assert_eq!(score.tier.max_sub_agents(), 3);
1585
1586 for i in 1..=3 {
1588 let agent = format!("sub_agent_{}", i);
1589 let result = gateway.hire_sub_agent(user, &agent, "task_1", 10_000_000);
1590 assert!(result.is_ok(), "Hire {} should succeed", i);
1591
1592 let hire = result.unwrap();
1593 assert!(hire.success);
1594 assert_eq!(hire.current_hired_count, i);
1595 assert_eq!(hire.fee_captured, 100_000); println!("Hired sub-agent {}: fee={}", i, hire.fee_captured);
1597 }
1598
1599 let result = gateway.hire_sub_agent(user, "sub_agent_4", "task_1", 10_000_000);
1601 assert!(result.is_err(), "4th hire should fail for Trusted user");
1602
1603 match result.unwrap_err() {
1604 GatewayError::Forbidden { message, .. } => {
1605 println!("Correctly denied: {}", message);
1606 assert!(message.contains("limit reached"));
1607 }
1608 other => panic!("Expected Forbidden error, got {:?}", other),
1609 }
1610
1611 let result = gateway.hire_sub_agent(user, "sub_agent_5", "task_1", 10_000_000);
1613 assert!(result.is_err(), "5th hire should also fail");
1614 }
1615
1616 #[test]
1617 fn test_high_reputation_user_can_hire_multiple_sub_agents() {
1618 let gateway = Gateway::new();
1620 let user = "high_rep_user";
1621
1622 build_user_to_tier(&gateway, user, TrustTier::Elite);
1624
1625 let score = gateway.get_reputation(user).unwrap();
1626 let max_allowed = score.tier.max_sub_agents();
1627 println!("High rep user score: {}, tier: {:?}, max_agents: {}",
1628 score.composite, score.tier, max_allowed);
1629
1630 assert!(max_allowed >= 3, "Should have at least Trusted tier (3 agents)");
1633
1634 let hire_count = std::cmp::min(5, max_allowed as usize);
1636 let mut total_fees = 0u64;
1637
1638 for i in 1..=hire_count {
1639 let agent = format!("sub_agent_{}", i);
1640 let result = gateway.hire_sub_agent(user, &agent, "complex_task", 100_000_000);
1641 assert!(result.is_ok(), "Hire {} should succeed (max={})", i, max_allowed);
1642
1643 let hire = result.unwrap();
1644 assert!(hire.success);
1645 assert_eq!(hire.current_hired_count, i);
1646 assert_eq!(hire.fee_captured, 1_000_000); assert_eq!(hire.net_budget, 99_000_000); total_fees += hire.fee_captured;
1649
1650 println!(
1651 "Hired {}: fee={}, net={}, total_fees={}",
1652 agent, hire.fee_captured, hire.net_budget, total_fees
1653 );
1654 }
1655
1656 let (hired, fees, _) = gateway.get_sub_agent_status(user).unwrap();
1658 assert_eq!(hired.len(), hire_count);
1659 assert_eq!(fees, 1_000_000 * hire_count as u64);
1660
1661 println!("Final status: {} hired, {} fees captured", hired.len(), fees);
1662 }
1663
1664 #[test]
1665 fn test_fee_capture_calculation() {
1666 let gateway = Gateway::new();
1667 let user = "fee_test_user";
1668
1669 build_user_to_tier(&gateway, user, TrustTier::Elite);
1671
1672 let score = gateway.get_reputation(user).unwrap();
1673 let max_allowed = score.tier.max_sub_agents();
1674 println!("Fee test user: tier {:?}, max_agents {}", score.tier, max_allowed);
1675
1676 let test_cases = [
1678 (1_000_000, 10_000, 990_000), (10_000_000, 100_000, 9_900_000), (100_000_000, 1_000_000, 99_000_000), ];
1682
1683 let test_count = std::cmp::min(test_cases.len(), max_allowed as usize);
1684
1685 for (i, (budget, expected_fee, expected_net)) in test_cases.iter().take(test_count).enumerate() {
1686 let agent = format!("fee_agent_{}", i);
1687 let result = gateway.hire_sub_agent(user, &agent, "fee_task", *budget).unwrap();
1688
1689 assert_eq!(result.fee_captured, *expected_fee,
1690 "Fee mismatch for budget {}", budget);
1691 assert_eq!(result.net_budget, *expected_net,
1692 "Net budget mismatch for budget {}", budget);
1693
1694 assert_eq!(result.fee_captured + result.net_budget, *budget);
1696
1697 println!("Budget {}: fee={} (1%), net={}",
1698 budget, result.fee_captured, result.net_budget);
1699 }
1700 }
1701
1702 #[test]
1703 fn test_swarm_coordination_with_fees() {
1704 let gateway = Gateway::new();
1705 let user = "swarm_coord_user";
1706
1707 build_user_to_tier(&gateway, user, TrustTier::Elite);
1709
1710 let score = gateway.get_reputation(user).unwrap();
1711 println!("Swarm test user: score {}, tier {:?}", score.composite, score.tier);
1712
1713 if score.composite < SWARM_COORDINATOR_THRESHOLD {
1716 println!("SKIP: Score {} < swarm threshold {}. Scoring caps prevent testing.",
1717 score.composite, SWARM_COORDINATOR_THRESHOLD);
1718 let agents: Vec<String> = (1..=3).map(|i| format!("swarm_agent_{}", i)).collect();
1720 let result = gateway.coordinate_swarm(user, "swarm_task_1", &agents, 50_000_000);
1721 assert!(result.is_err(), "Should fail with insufficient reputation");
1722 return;
1723 }
1724
1725 let max_allowed = score.tier.max_sub_agents() as usize;
1727 let agent_count = std::cmp::min(5, max_allowed);
1728 let agents: Vec<String> = (1..=agent_count).map(|i| format!("swarm_agent_{}", i)).collect();
1729 let budget_per = 50_000_000u64; let result = gateway.coordinate_swarm(user, "swarm_task_1", &agents, budget_per).unwrap();
1732
1733 assert!(result.success);
1734 assert_eq!(result.hired_agents.len(), agent_count);
1735 assert!(result.failed_agents.is_empty());
1736
1737 let expected_fee_per = 500_000u64;
1739 let expected_net_per = 49_500_000u64;
1740
1741 assert_eq!(result.total_fees_captured, expected_fee_per * agent_count as u64);
1742 assert_eq!(result.total_net_budget, expected_net_per * agent_count as u64);
1743
1744 println!("Swarm coordination: {} agents, {} total fees, {} total net",
1745 result.hired_agents.len(), result.total_fees_captured, result.total_net_budget);
1746 }
1747
1748 #[test]
1753 fn test_referral_registration() {
1754 let gateway = Gateway::new();
1755
1756 let result = gateway.register_referral("agent_123", "user_456");
1758 assert!(result.is_ok());
1759
1760 let result = gateway.register_referral("agent_789", "user_456");
1762 assert!(result.is_err());
1763 }
1764
1765 #[test]
1766 fn test_referral_bounty_requires_tier1_credential() {
1767 let gateway = Gateway::new();
1768
1769 build_user_to_tier(&gateway, "referrer_agent", TrustTier::Established);
1771
1772 gateway.register_referral("referrer_agent", "referred_user").unwrap();
1774
1775 let result = gateway.process_referral_bounty("referred_user", 0).unwrap();
1777 assert!(!result.success);
1778 assert!(result.failure_reason.is_some());
1779 println!("Tier 0 correctly rejected: {:?}", result.failure_reason);
1780
1781 let result = gateway.process_referral_bounty("referred_user", 1).unwrap();
1783 assert!(result.success);
1784 assert!(result.reputation_boost > 0);
1785 println!("Bounty awarded: {} reputation boost", result.reputation_boost);
1786 }
1787
1788 #[test]
1789 fn test_referral_bounty_is_10_percent() {
1790 let gateway = Gateway::new();
1791
1792 build_user_to_tier(&gateway, "big_agent", TrustTier::Trusted);
1794
1795 let initial_score = gateway.get_reputation("big_agent").unwrap().composite;
1797 println!("Agent initial score: {}", initial_score);
1798
1799 gateway.register_referral("big_agent", "new_pioneer").unwrap();
1801 let result = gateway.process_referral_bounty("new_pioneer", 2).unwrap();
1802
1803 let expected_bounty = (initial_score * 1000) / 10000;
1805 assert_eq!(result.reputation_boost, expected_bounty);
1806 println!("10% bounty: {} (from base score {})", result.reputation_boost, initial_score);
1807 }
1808
1809 #[test]
1810 fn test_referral_bounty_only_paid_once() {
1811 let gateway = Gateway::new();
1812
1813 build_user_to_tier(&gateway, "agent_once", TrustTier::Established);
1815 gateway.register_referral("agent_once", "user_once").unwrap();
1816
1817 let result1 = gateway.process_referral_bounty("user_once", 1).unwrap();
1819 assert!(result1.success);
1820 let first_boost = result1.reputation_boost;
1821
1822 let result2 = gateway.process_referral_bounty("user_once", 1).unwrap();
1824 assert!(!result2.success);
1825 assert_eq!(result2.reputation_boost, 0);
1826 println!("Correctly prevented double-payment. First boost: {}", first_boost);
1827 }
1828
1829 #[test]
1830 fn test_referral_stats_tracking() {
1831 let gateway = Gateway::new();
1832
1833 build_user_to_tier(&gateway, "stats_agent", TrustTier::Trusted);
1835
1836 for i in 1..=5 {
1838 gateway.register_referral("stats_agent", &format!("referred_{}", i)).unwrap();
1839 }
1840
1841 for i in 1..=3 {
1843 gateway.process_referral_bounty(&format!("referred_{}", i), i as u8).unwrap();
1844 }
1845
1846 let (referrals, total_bounties) = gateway.get_agent_referral_stats("stats_agent").unwrap();
1848 assert_eq!(referrals.len(), 5); let paid_count = referrals.iter().filter(|r| r.bounty_paid).count();
1851 assert_eq!(paid_count, 3); assert!(total_bounties > 0);
1854 println!("Agent stats: {} referrals, {} total bounty earned", referrals.len(), total_bounties);
1855 }
1856
1857 #[test]
1858 fn test_referral_bounty_non_referred_user() {
1859 let gateway = Gateway::new();
1860
1861 let result = gateway.process_referral_bounty("random_user", 5).unwrap();
1863 assert!(!result.success);
1864 assert!(result.failure_reason.unwrap().contains("not referred"));
1865 }
1866}