agent_wire_storage_market/
lib.rs1use agent_wire_foundation::{
2 CreditAmount, CrossGraphRef, GraphSlug, HandlePath, PriceCurve, SettlementIntent,
3};
4use serde::{Deserialize, Serialize};
5
6#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
7pub struct StorageOfferId(pub String);
8
9#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub struct PinCommitmentId(pub String);
11
12#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
13pub struct RetrievalRequestId(pub String);
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
16pub struct ReplicationFactor(pub u16);
17
18#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
19pub struct RetentionPolicy {
20 pub minimum_seconds: u64,
21 pub renew_before_expiry_seconds: Option<u64>,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
25pub struct CapacityAllocation {
26 pub graph: GraphSlug,
27 pub reserved_bytes: u64,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31pub struct StorageOffer {
32 pub offer_id: StorageOfferId,
33 pub provider: HandlePath,
34 pub capacity_bytes: u64,
35 pub capacity_allocation: Vec<CapacityAllocation>,
36 pub price: PriceCurve,
37 pub replication: ReplicationFactor,
38 pub retention: RetentionPolicy,
39 pub settlement: SettlementIntent,
40}
41
42#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
43pub struct PinCommitmentRequest {
44 pub offer_id: StorageOfferId,
45 pub requester: HandlePath,
46 pub content_ref: CrossGraphRef,
47 pub bytes: u64,
48 pub replication: ReplicationFactor,
49 pub retention: RetentionPolicy,
50 pub settlement: SettlementIntent,
51}
52
53#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
54pub struct PinCommitment {
55 pub commitment_id: PinCommitmentId,
56 pub request: PinCommitmentRequest,
57 pub provider: HandlePath,
58}
59
60#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
61pub struct RetrievalRequest {
62 pub request_id: RetrievalRequestId,
63 pub commitment_id: PinCommitmentId,
64 pub requester: HandlePath,
65 pub content_ref: CrossGraphRef,
66 pub max_price: CreditAmount,
67}
68
69#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
70pub struct RetrievalReceipt {
71 pub request_id: RetrievalRequestId,
72 pub content_ref: CrossGraphRef,
73 pub served_by: HandlePath,
74 pub bytes_served: u64,
75 pub charged: CreditAmount,
76}
77
78pub trait StorageMarket {
79 type Error;
80
81 fn publish_offer(&self, offer: StorageOffer) -> Result<StorageOfferId, Self::Error>;
82 fn commit_pin(&self, request: PinCommitmentRequest) -> Result<PinCommitment, Self::Error>;
83 fn retrieve(&self, request: RetrievalRequest) -> Result<RetrievalReceipt, Self::Error>;
84 fn renew_retention(
85 &self,
86 commitment_id: PinCommitmentId,
87 retention: RetentionPolicy,
88 ) -> Result<PinCommitment, Self::Error>;
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 #[test]
96 fn storage_offer_carries_capacity_retention_and_settlement() {
97 let offer = StorageOffer {
98 offer_id: StorageOfferId("offer-1".to_owned()),
99 provider: HandlePath::new(["agent", "playful", "storage"]).unwrap(),
100 capacity_bytes: 1_000_000,
101 capacity_allocation: vec![CapacityAllocation {
102 graph: GraphSlug::new("kitty").unwrap(),
103 reserved_bytes: 250_000,
104 }],
105 price: PriceCurve {
106 base: CreditAmount::from_sats(10),
107 per_unit: CreditAmount::from_sats(2),
108 },
109 replication: ReplicationFactor(3),
110 retention: RetentionPolicy {
111 minimum_seconds: 86_400,
112 renew_before_expiry_seconds: Some(3_600),
113 },
114 settlement: SettlementIntent {
115 max_price: CreditAmount::from_sats(1_000),
116 escrow_required: true,
117 },
118 };
119
120 assert_eq!(offer.capacity_allocation[0].graph.as_str(), "kitty");
121 assert_eq!(offer.replication, ReplicationFactor(3));
122 assert!(offer.settlement.escrow_required);
123 }
124
125 #[test]
126 fn pin_commitment_request_names_cross_graph_content() {
127 let request = PinCommitmentRequest {
128 offer_id: StorageOfferId("offer-1".to_owned()),
129 requester: HandlePath::new(["agent", "playful", "kramer"]).unwrap(),
130 content_ref: "playful/122/storage/1".parse().unwrap(),
131 bytes: 512,
132 replication: ReplicationFactor(2),
133 retention: RetentionPolicy {
134 minimum_seconds: 600,
135 renew_before_expiry_seconds: None,
136 },
137 settlement: SettlementIntent {
138 max_price: CreditAmount::from_sats(40),
139 escrow_required: false,
140 },
141 };
142
143 assert_eq!(request.content_ref.to_string(), "playful/122/storage/1");
144 assert_eq!(request.bytes, 512);
145 }
146}