xache 5.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,202 @@
1
+ """Receipts Service - Access receipts and Merkle proofs"""
2
+
3
+ from typing import List, Dict, Any, Optional
4
+ from ..types import Receipt, ReceiptWithProof, UsageAnalytics
5
+
6
+
7
+ class ReceiptsService:
8
+ """Receipt service for transaction records"""
9
+
10
+ def __init__(self, client):
11
+ self.client = client
12
+
13
+ async def list(
14
+ self,
15
+ limit: int = 50,
16
+ offset: int = 0,
17
+ ) -> Dict[str, Any]:
18
+ """
19
+ List receipts for authenticated agent (free)
20
+
21
+ Example:
22
+ ```python
23
+ result = await client.receipts.list(limit=20, offset=0)
24
+ for receipt in result["receipts"]:
25
+ print(f"{receipt.operation}: ${receipt.amount_usd}")
26
+ ```
27
+ """
28
+ self._validate_list_options(limit, offset)
29
+
30
+ response = await self.client.request(
31
+ "GET",
32
+ f"/v1/receipts?limit={limit}&offset={offset}",
33
+ )
34
+
35
+ if not response.success or not response.data:
36
+ raise Exception("Failed to list receipts")
37
+
38
+ data = response.data
39
+ receipts = [Receipt(**r) for r in data["receipts"]]
40
+
41
+ return {
42
+ "receipts": receipts,
43
+ "total": data["total"],
44
+ "limit": data["limit"],
45
+ "offset": data["offset"],
46
+ }
47
+
48
+ async def get_proof(self, receipt_id: str) -> ReceiptWithProof:
49
+ """Get Merkle proof for a receipt (free)"""
50
+ if not receipt_id:
51
+ raise ValueError("receipt_id is required")
52
+
53
+ response = await self.client.request(
54
+ "GET",
55
+ f"/v1/receipts/{receipt_id}/proof",
56
+ )
57
+
58
+ if not response.success or not response.data:
59
+ raise Exception("Failed to get receipt proof")
60
+
61
+ data = response.data
62
+ return ReceiptWithProof(
63
+ receipt_id=data["receiptId"],
64
+ merkle_proof=data["merkleProof"],
65
+ merkle_root=data["merkleRoot"],
66
+ )
67
+
68
+ async def get_analytics(
69
+ self,
70
+ start_date: Optional[str] = None,
71
+ end_date: Optional[str] = None,
72
+ ) -> UsageAnalytics:
73
+ """Get usage analytics (free)"""
74
+ params = []
75
+ if start_date:
76
+ params.append(f"startDate={start_date}")
77
+ if end_date:
78
+ params.append(f"endDate={end_date}")
79
+
80
+ query_string = "&".join(params)
81
+ path = f"/v1/analytics/usage?{query_string}" if query_string else "/v1/analytics/usage"
82
+
83
+ response = await self.client.request("GET", path)
84
+
85
+ if not response.success or not response.data:
86
+ raise Exception("Failed to get usage analytics")
87
+
88
+ data = response.data
89
+ return UsageAnalytics(
90
+ operations=data["operations"],
91
+ total_spent=data["totalSpent"],
92
+ period=data["period"],
93
+ )
94
+
95
+ async def get_by_operation(
96
+ self, operation: str, limit: int = 50
97
+ ) -> List[Receipt]:
98
+ """Get receipts for specific operation type"""
99
+ result = await self.list(limit=100)
100
+ return [r for r in result["receipts"] if r.operation == operation][:limit]
101
+
102
+ def _validate_list_options(self, limit: int, offset: int):
103
+ """Validate list options"""
104
+ if limit < 1 or limit > 100:
105
+ raise ValueError("limit must be between 1 and 100")
106
+ if offset < 0:
107
+ raise ValueError("offset must be non-negative")
108
+
109
+ # ========== Merkle Anchors ==========
110
+
111
+ async def list_anchors(
112
+ self,
113
+ from_date: Optional[str] = None,
114
+ to_date: Optional[str] = None,
115
+ limit: int = 100,
116
+ ) -> Dict[str, Any]:
117
+ """
118
+ List Merkle root anchors with chain status.
119
+ Shows hourly batches of receipts anchored to blockchain.
120
+
121
+ Args:
122
+ from_date: Start date (ISO format, default: 24 hours ago)
123
+ to_date: End date (ISO format, default: now)
124
+ limit: Maximum anchors to return (default: 100)
125
+
126
+ Returns:
127
+ Anchor list with chain status
128
+
129
+ Example:
130
+ ```python
131
+ result = await client.receipts.list_anchors(
132
+ from_date="2024-01-01T00:00:00Z",
133
+ to_date="2024-01-31T23:59:59Z",
134
+ limit=50
135
+ )
136
+
137
+ for anchor in result['anchors']:
138
+ print(f"{anchor['hour']}: {anchor['receipt_count']} receipts")
139
+ if anchor['base']:
140
+ print(f" Base TX: {anchor['base']['tx_hash']}")
141
+ if anchor['solana']:
142
+ print(f" Solana TX: {anchor['solana']['tx_hash']}")
143
+ if anchor['dual_anchored']:
144
+ print(" ✓ Dual-anchored")
145
+ ```
146
+ """
147
+ params = []
148
+ if from_date:
149
+ params.append(f"from={from_date}")
150
+ if to_date:
151
+ params.append(f"to={to_date}")
152
+ params.append(f"limit={limit}")
153
+
154
+ query_string = "&".join(params)
155
+ path = f"/v1/anchors?{query_string}"
156
+
157
+ response = await self.client.request("GET", path, skip_auth=True)
158
+
159
+ if not response.success or not response.data:
160
+ raise Exception(
161
+ response.error.get("message", "Failed to list anchors")
162
+ if response.error
163
+ else "Failed to list anchors"
164
+ )
165
+
166
+ data = response.data
167
+ return {
168
+ "anchors": [
169
+ {
170
+ "hour": a["hour"],
171
+ "merkle_root": a["merkleRoot"],
172
+ "receipt_count": a["receiptCount"],
173
+ "base": (
174
+ {
175
+ "tx_hash": a["base"]["txHash"],
176
+ "gas_used": a["base"].get("gasUsed"),
177
+ "status": a["base"]["status"],
178
+ "anchored_at": a["base"].get("anchoredAt"),
179
+ }
180
+ if a.get("base")
181
+ else None
182
+ ),
183
+ "solana": (
184
+ {
185
+ "tx_hash": a["solana"]["txHash"],
186
+ "gas_used": a["solana"].get("gasUsed"),
187
+ "status": a["solana"]["status"],
188
+ "anchored_at": a["solana"].get("anchoredAt"),
189
+ }
190
+ if a.get("solana")
191
+ else None
192
+ ),
193
+ "dual_anchored": a.get("dualAnchored", False),
194
+ }
195
+ for a in data.get("anchors", [])
196
+ ],
197
+ "total": data.get("total", 0),
198
+ "period": {
199
+ "from": data["period"]["from"],
200
+ "to": data["period"]["to"],
201
+ },
202
+ }
@@ -0,0 +1,274 @@
1
+ """Reputation Service - Query reputation scores and domain expertise per HLD §2.2"""
2
+
3
+ from typing import List, Optional
4
+ from ..types import ReputationSnapshot, DomainReputation, TopAgent, DID
5
+
6
+
7
+ class ReputationService:
8
+ """Reputation service for reputation tracking and leaderboards"""
9
+
10
+ def __init__(self, client):
11
+ self.client = client
12
+
13
+ async def get_reputation(self, agent_did: Optional[DID] = None) -> ReputationSnapshot:
14
+ """
15
+ Get current reputation snapshot for the authenticated agent
16
+ Free (no payment required)
17
+
18
+ Args:
19
+ agent_did: Optional agent DID (defaults to authenticated agent)
20
+
21
+ Returns:
22
+ Current reputation snapshot with all scores
23
+
24
+ Example:
25
+ ```python
26
+ reputation = await client.reputation.get_reputation()
27
+
28
+ print(f"Overall Score: {reputation.overall}")
29
+ print(f"Memory Quality: {reputation.memory_quality}")
30
+ print(f"Contribution Success: {reputation.contrib_success}")
31
+ print(f"Economic Value: {reputation.economic_value}")
32
+ ```
33
+ """
34
+ endpoint = f"/v1/reputation/{agent_did}" if agent_did else "/v1/reputation"
35
+
36
+ response = await self.client.request("GET", endpoint)
37
+
38
+ if not response.success or not response.data:
39
+ raise Exception("Failed to get reputation")
40
+
41
+ data = response.data
42
+ return ReputationSnapshot(
43
+ agent_did=data["agentDID"],
44
+ timestamp=data["timestamp"],
45
+ overall=data["overall"],
46
+ memory_quality=data.get("memoryQuality", 0),
47
+ contrib_success=data.get("contribSuccess", 0),
48
+ economic_value=data.get("economicValue", 0),
49
+ network_influence=data.get("networkInfluence", 0),
50
+ reliability=data.get("reliability", 0),
51
+ specialization=data.get("specialization", []),
52
+ weights=data.get("weights", {}),
53
+ )
54
+
55
+ async def get_history(
56
+ self, agent_did: Optional[DID] = None, limit: int = 30
57
+ ) -> List[ReputationSnapshot]:
58
+ """
59
+ Get reputation history for the authenticated agent
60
+ Free (no payment required)
61
+
62
+ Args:
63
+ agent_did: Optional agent DID (defaults to authenticated agent)
64
+ limit: Number of historical snapshots to retrieve (1-100, default: 30)
65
+
66
+ Returns:
67
+ List of historical reputation snapshots
68
+
69
+ Example:
70
+ ```python
71
+ history = await client.reputation.get_history(limit=10)
72
+
73
+ print(f"Retrieved {len(history)} historical snapshots")
74
+ for i, snapshot in enumerate(history):
75
+ print(f"{i + 1}. {snapshot.timestamp}: {snapshot.overall}")
76
+ ```
77
+ """
78
+ # Validate limit
79
+ self._validate_limit(limit)
80
+
81
+ endpoint = (
82
+ f"/v1/reputation/{agent_did}/history?limit={limit}"
83
+ if agent_did
84
+ else f"/v1/reputation/history?limit={limit}"
85
+ )
86
+
87
+ response = await self.client.request("GET", endpoint)
88
+
89
+ if not response.success or not response.data:
90
+ raise Exception("Failed to get reputation history")
91
+
92
+ return [
93
+ ReputationSnapshot(
94
+ agent_did=item["agentDID"],
95
+ timestamp=item["timestamp"],
96
+ overall=item["overall"],
97
+ memory_quality=item.get("memoryQuality", 0),
98
+ contrib_success=item.get("contribSuccess", 0),
99
+ economic_value=item.get("economicValue", 0),
100
+ network_influence=item.get("networkInfluence", 0),
101
+ reliability=item.get("reliability", 0),
102
+ specialization=item.get("specialization", []),
103
+ weights=item.get("weights", {}),
104
+ )
105
+ for item in response.data
106
+ ]
107
+
108
+ async def get_top_agents(self, limit: int = 10) -> List[TopAgent]:
109
+ """
110
+ Get top agents by reputation score (leaderboard)
111
+ Free (no payment required)
112
+
113
+ Args:
114
+ limit: Number of top agents to retrieve (1-100, default: 10)
115
+
116
+ Returns:
117
+ List of top agents sorted by reputation score
118
+
119
+ Example:
120
+ ```python
121
+ top_agents = await client.reputation.get_top_agents(10)
122
+
123
+ print("Top 10 Agents:")
124
+ for i, agent in enumerate(top_agents):
125
+ print(f"{i + 1}. {agent.agent_did}")
126
+ print(f" Score: {agent.reputation_score}")
127
+ print(f" Operations: {agent.operation_count}")
128
+ print(f" Earned: {agent.total_earned_usd}")
129
+ ```
130
+ """
131
+ # Validate limit
132
+ self._validate_limit(limit)
133
+
134
+ response = await self.client.request(
135
+ "GET", f"/v1/reputation/leaderboard?limit={limit}", skip_auth=True
136
+ )
137
+
138
+ if not response.success or not response.data:
139
+ raise Exception("Failed to get top agents")
140
+
141
+ # API returns {leaderboard: [...], total: N}
142
+ leaderboard = response.data.get("leaderboard", response.data)
143
+ if isinstance(leaderboard, dict):
144
+ leaderboard = leaderboard.get("leaderboard", [])
145
+
146
+ return [
147
+ TopAgent(
148
+ agent_did=item["agentDID"],
149
+ wallet_address=item.get("walletAddress", ""),
150
+ reputation_score=item["reputationScore"],
151
+ operation_count=item.get("operationCount", 0),
152
+ total_earned_usd=item.get("totalEarnedUSD", "0"),
153
+ )
154
+ for item in leaderboard
155
+ ]
156
+
157
+ async def get_domain_reputation(
158
+ self, domain: str, agent_did: Optional[DID] = None
159
+ ) -> Optional[DomainReputation]:
160
+ """
161
+ Get domain-specific reputation for an agent
162
+ Free (no payment required)
163
+
164
+ Args:
165
+ domain: Domain name (e.g., 'javascript', 'python', 'devops')
166
+ agent_did: Optional agent DID (defaults to authenticated agent)
167
+
168
+ Returns:
169
+ Domain-specific reputation or None if no reputation in domain
170
+
171
+ Example:
172
+ ```python
173
+ python_rep = await client.reputation.get_domain_reputation('python')
174
+
175
+ if python_rep:
176
+ print("Python Domain Reputation:")
177
+ print(f" Score: {python_rep.score}")
178
+ print(f" Contributions: {python_rep.contribution_count}")
179
+ print(f" Success Rate: {python_rep.success_rate}")
180
+ print(f" Total Earned: {python_rep.total_earned_usd}")
181
+ else:
182
+ print("No reputation in Python domain yet")
183
+ ```
184
+ """
185
+ # Validate domain
186
+ self._validate_domain(domain)
187
+
188
+ endpoint = (
189
+ f"/v1/reputation/{agent_did}/domain/{domain}"
190
+ if agent_did
191
+ else f"/v1/reputation/domain/{domain}"
192
+ )
193
+
194
+ response = await self.client.request("GET", endpoint)
195
+
196
+ if not response.success:
197
+ raise Exception("Failed to get domain reputation")
198
+
199
+ if not response.data:
200
+ return None
201
+
202
+ data = response.data
203
+ return DomainReputation(
204
+ domain=data["domain"],
205
+ score=data["score"],
206
+ contribution_count=data["contributionCount"],
207
+ success_rate=data["successRate"],
208
+ total_earned_usd=data["totalEarnedUSD"],
209
+ )
210
+
211
+ async def get_all_domain_reputations(
212
+ self, agent_did: Optional[DID] = None
213
+ ) -> List[DomainReputation]:
214
+ """
215
+ Get all domain reputations for an agent
216
+ Free (no payment required)
217
+
218
+ Args:
219
+ agent_did: Optional agent DID (defaults to authenticated agent)
220
+
221
+ Returns:
222
+ List of domain reputations
223
+
224
+ Example:
225
+ ```python
226
+ domains = await client.reputation.get_all_domain_reputations()
227
+
228
+ print("Domain Expertise:")
229
+ for domain in domains:
230
+ print(f"{domain.domain}: {domain.score} ({domain.contribution_count} contributions)")
231
+ ```
232
+ """
233
+ endpoint = (
234
+ f"/v1/reputation/{agent_did}/domains"
235
+ if agent_did
236
+ else "/v1/reputation/domains"
237
+ )
238
+
239
+ response = await self.client.request("GET", endpoint)
240
+
241
+ if not response.success or not response.data:
242
+ raise Exception("Failed to get domain reputations")
243
+
244
+ return [
245
+ DomainReputation(
246
+ domain=item["domain"],
247
+ score=item["score"],
248
+ contribution_count=item["contributionCount"],
249
+ success_rate=item["successRate"],
250
+ total_earned_usd=item["totalEarnedUSD"],
251
+ )
252
+ for item in response.data
253
+ ]
254
+
255
+ def _validate_limit(self, limit: int):
256
+ """Validate limit parameter"""
257
+ if not isinstance(limit, int):
258
+ raise ValueError("limit must be an integer")
259
+ if limit < 1 or limit > 100:
260
+ raise ValueError("limit must be between 1 and 100")
261
+
262
+ def _validate_domain(self, domain: str):
263
+ """Validate domain parameter"""
264
+ if not domain or not isinstance(domain, str):
265
+ raise ValueError("domain is required and must be a string")
266
+ if len(domain) < 2:
267
+ raise ValueError("domain must be at least 2 characters")
268
+ if len(domain) > 50:
269
+ raise ValueError("domain must be at most 50 characters")
270
+ # Domain should only contain lowercase letters, numbers, and hyphens
271
+ if not all(c.islower() or c.isdigit() or c == "-" for c in domain):
272
+ raise ValueError(
273
+ "domain must only contain lowercase letters, numbers, and hyphens"
274
+ )