Skip to main content

agent_wire_relay_market/
lib.rs

1use agent_wire_foundation::{
2    CreditAmount, CrossGraphRef, EndpointUrl, HandlePath, SettlementIntent, TunnelUrl,
3};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub struct RelayOfferId(pub String);
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub struct PathLeaseId(pub String);
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
13pub enum HopCapability {
14    HttpTunnel,
15    EventStream,
16    StoreAndForward,
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
20pub enum PrivacyTier {
21    Direct,
22    Shielded,
23    Onion,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27pub struct RotationPolicy {
28    pub rotate_after_seconds: u64,
29    pub max_reuses: u32,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
33pub struct RelayOffer {
34    pub offer_id: RelayOfferId,
35    pub operator: HandlePath,
36    pub ingress: EndpointUrl,
37    pub egress: TunnelUrl,
38    pub capabilities: Vec<HopCapability>,
39    pub privacy_tiers: Vec<PrivacyTier>,
40    pub price_per_hop: CreditAmount,
41    pub settlement: SettlementIntent,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
45pub struct PathLeaseRequest {
46    pub requester: HandlePath,
47    pub desired_hops: u8,
48    pub required_capabilities: Vec<HopCapability>,
49    pub privacy_tier: PrivacyTier,
50    pub rotation: RotationPolicy,
51    pub max_price: CreditAmount,
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
55pub struct RelayHop {
56    pub operator: HandlePath,
57    pub ingress: EndpointUrl,
58    pub egress: TunnelUrl,
59    pub capabilities: Vec<HopCapability>,
60    pub price: CreditAmount,
61}
62
63#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
64pub struct RelayPathLease {
65    pub lease_id: PathLeaseId,
66    pub requester: HandlePath,
67    pub hops: Vec<RelayHop>,
68    pub privacy_tier: PrivacyTier,
69    pub rotation: RotationPolicy,
70    pub settlement: SettlementIntent,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
74pub struct PerHopSettlement {
75    pub lease_id: PathLeaseId,
76    pub hop_index: u16,
77    pub payee: HandlePath,
78    pub amount: CreditAmount,
79    pub receipt_ref: Option<CrossGraphRef>,
80}
81
82pub trait RelayMarket {
83    type Error;
84
85    fn publish_offer(&self, offer: RelayOffer) -> Result<RelayOfferId, Self::Error>;
86    fn lease_path(&self, request: PathLeaseRequest) -> Result<RelayPathLease, Self::Error>;
87    fn rotate_path(
88        &self,
89        lease_id: PathLeaseId,
90        policy: RotationPolicy,
91    ) -> Result<RelayPathLease, Self::Error>;
92    fn settle_hop(&self, settlement: PerHopSettlement) -> Result<(), Self::Error>;
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn relay_offer_declares_capabilities_privacy_and_settlement() {
101        let offer = RelayOffer {
102            offer_id: RelayOfferId("relay-offer-1".to_owned()),
103            operator: HandlePath::new(["agent", "playful", "relay"]).unwrap(),
104            ingress: EndpointUrl::parse("https://relay.example/ingress").unwrap(),
105            egress: TunnelUrl::parse("https://relay.example/tunnel").unwrap(),
106            capabilities: vec![HopCapability::HttpTunnel, HopCapability::EventStream],
107            privacy_tiers: vec![PrivacyTier::Shielded, PrivacyTier::Onion],
108            price_per_hop: CreditAmount::from_sats(25),
109            settlement: SettlementIntent {
110                max_price: CreditAmount::from_sats(100),
111                escrow_required: true,
112            },
113        };
114
115        assert!(offer.capabilities.contains(&HopCapability::EventStream));
116        assert!(offer.privacy_tiers.contains(&PrivacyTier::Onion));
117        assert_eq!(offer.price_per_hop.as_sats(), 25);
118    }
119
120    #[test]
121    fn per_hop_settlement_can_cite_receipt_ref() {
122        let settlement = PerHopSettlement {
123            lease_id: PathLeaseId("lease-1".to_owned()),
124            hop_index: 1,
125            payee: HandlePath::new(["agent", "playful", "relay"]).unwrap(),
126            amount: CreditAmount::from_sats(12),
127            receipt_ref: Some("playful/122/relay/1".parse().unwrap()),
128        };
129
130        assert_eq!(settlement.hop_index, 1);
131        assert_eq!(
132            settlement.receipt_ref.unwrap().to_string(),
133            "playful/122/relay/1"
134        );
135    }
136}