uvd-x402-sdk 0.5.6__py3-none-any.whl → 0.7.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.
- uvd_x402_sdk/__init__.py +348 -241
- uvd_x402_sdk/advanced_escrow.py +633 -0
- uvd_x402_sdk/erc8004.py +663 -0
- uvd_x402_sdk/escrow.py +637 -0
- uvd_x402_sdk/networks/__init__.py +9 -9
- uvd_x402_sdk/networks/base.py +3 -0
- uvd_x402_sdk/networks/evm.py +71 -1
- {uvd_x402_sdk-0.5.6.dist-info → uvd_x402_sdk-0.7.0.dist-info}/METADATA +313 -9
- {uvd_x402_sdk-0.5.6.dist-info → uvd_x402_sdk-0.7.0.dist-info}/RECORD +12 -9
- {uvd_x402_sdk-0.5.6.dist-info → uvd_x402_sdk-0.7.0.dist-info}/WHEEL +1 -1
- {uvd_x402_sdk-0.5.6.dist-info → uvd_x402_sdk-0.7.0.dist-info}/LICENSE +0 -0
- {uvd_x402_sdk-0.5.6.dist-info → uvd_x402_sdk-0.7.0.dist-info}/top_level.txt +0 -0
uvd_x402_sdk/erc8004.py
ADDED
|
@@ -0,0 +1,663 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ERC-8004 Trustless Agents client for x402 SDK.
|
|
3
|
+
|
|
4
|
+
This module provides integration with the ERC-8004 reputation system,
|
|
5
|
+
enabling agents to build verifiable reputation through on-chain feedback.
|
|
6
|
+
|
|
7
|
+
Features:
|
|
8
|
+
- Query agent identity from Identity Registry
|
|
9
|
+
- Query agent reputation from Reputation Registry
|
|
10
|
+
- Submit feedback with proof of payment
|
|
11
|
+
- Revoke feedback
|
|
12
|
+
|
|
13
|
+
Example:
|
|
14
|
+
>>> from uvd_x402_sdk.erc8004 import Erc8004Client
|
|
15
|
+
>>>
|
|
16
|
+
>>> client = Erc8004Client()
|
|
17
|
+
>>>
|
|
18
|
+
>>> # Get agent identity
|
|
19
|
+
>>> identity = await client.get_identity("ethereum", 42)
|
|
20
|
+
>>> print(f"Agent: {identity.agent_uri}")
|
|
21
|
+
>>>
|
|
22
|
+
>>> # Get agent reputation
|
|
23
|
+
>>> reputation = await client.get_reputation("ethereum", 42)
|
|
24
|
+
>>> print(f"Score: {reputation.summary.summary_value}")
|
|
25
|
+
>>>
|
|
26
|
+
>>> # Submit feedback after payment
|
|
27
|
+
>>> result = await client.submit_feedback(
|
|
28
|
+
... network="ethereum",
|
|
29
|
+
... agent_id=42,
|
|
30
|
+
... value=95,
|
|
31
|
+
... tag1="quality",
|
|
32
|
+
... proof=settle_response.proof_of_payment,
|
|
33
|
+
... )
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
from enum import Enum
|
|
37
|
+
from typing import Any, Literal, Optional
|
|
38
|
+
|
|
39
|
+
import httpx
|
|
40
|
+
from pydantic import BaseModel, Field
|
|
41
|
+
|
|
42
|
+
# ERC-8004 extension identifier
|
|
43
|
+
ERC8004_EXTENSION_ID = "8004-reputation"
|
|
44
|
+
|
|
45
|
+
# Supported networks for ERC-8004
|
|
46
|
+
Erc8004Network = Literal["ethereum", "ethereum-sepolia", "base-mainnet"]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class Erc8004ContractAddresses(BaseModel):
|
|
50
|
+
"""Contract addresses for ERC-8004 on a network."""
|
|
51
|
+
|
|
52
|
+
identity_registry: Optional[str] = None
|
|
53
|
+
reputation_registry: Optional[str] = None
|
|
54
|
+
validation_registry: Optional[str] = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Contract addresses per network
|
|
58
|
+
ERC8004_CONTRACTS: dict[str, Erc8004ContractAddresses] = {
|
|
59
|
+
"ethereum": Erc8004ContractAddresses(
|
|
60
|
+
identity_registry="0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
|
|
61
|
+
reputation_registry="0x8004BAa17C55a88189AE136b182e5fdA19dE9b63",
|
|
62
|
+
),
|
|
63
|
+
"ethereum-sepolia": Erc8004ContractAddresses(
|
|
64
|
+
identity_registry="0x8004A818BFB912233c491871b3d84c89A494BD9e",
|
|
65
|
+
reputation_registry="0x8004B663056A597Dffe9eCcC1965A193B7388713",
|
|
66
|
+
validation_registry="0x8004Cb1BF31DAf7788923b405b754f57acEB4272",
|
|
67
|
+
),
|
|
68
|
+
# Base Mainnet - Same addresses as Ethereum (CREATE2 deterministic deployment)
|
|
69
|
+
"base-mainnet": Erc8004ContractAddresses(
|
|
70
|
+
identity_registry="0x8004A169FB4a3325136EB29fA0ceB6D2e539a432",
|
|
71
|
+
reputation_registry="0x8004BAa17C55a88189AE136b182e5fdA19dE9b63",
|
|
72
|
+
),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class ProofOfPayment(BaseModel):
|
|
77
|
+
"""
|
|
78
|
+
Cryptographic proof of a settled payment for reputation submission.
|
|
79
|
+
|
|
80
|
+
This proof is returned when settling with the 8004-reputation extension
|
|
81
|
+
and is required for submitting authorized feedback.
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
transaction_hash: str = Field(..., alias="transactionHash")
|
|
85
|
+
block_number: int = Field(..., alias="blockNumber")
|
|
86
|
+
network: str
|
|
87
|
+
payer: str
|
|
88
|
+
payee: str
|
|
89
|
+
amount: str
|
|
90
|
+
token: str
|
|
91
|
+
timestamp: int
|
|
92
|
+
payment_hash: str = Field(..., alias="paymentHash")
|
|
93
|
+
|
|
94
|
+
class Config:
|
|
95
|
+
populate_by_name = True
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class AgentService(BaseModel):
|
|
99
|
+
"""Agent service entry."""
|
|
100
|
+
|
|
101
|
+
name: str
|
|
102
|
+
endpoint: str
|
|
103
|
+
version: Optional[str] = None
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class AgentRegistration(BaseModel):
|
|
107
|
+
"""Agent registration reference."""
|
|
108
|
+
|
|
109
|
+
agent_id: int = Field(..., alias="agentId")
|
|
110
|
+
agent_registry: str = Field(..., alias="agentRegistry")
|
|
111
|
+
|
|
112
|
+
class Config:
|
|
113
|
+
populate_by_name = True
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class AgentIdentity(BaseModel):
|
|
117
|
+
"""Agent identity information from the Identity Registry."""
|
|
118
|
+
|
|
119
|
+
agent_id: int = Field(..., alias="agentId")
|
|
120
|
+
owner: str
|
|
121
|
+
agent_uri: str = Field(..., alias="agentUri")
|
|
122
|
+
agent_wallet: Optional[str] = Field(None, alias="agentWallet")
|
|
123
|
+
network: str
|
|
124
|
+
|
|
125
|
+
class Config:
|
|
126
|
+
populate_by_name = True
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class AgentRegistrationFile(BaseModel):
|
|
130
|
+
"""Agent registration file structure (resolved from agentURI)."""
|
|
131
|
+
|
|
132
|
+
type_: str = Field("https://eips.ethereum.org/EIPS/eip-8004#agent-v1", alias="type")
|
|
133
|
+
name: str
|
|
134
|
+
description: str
|
|
135
|
+
image: Optional[str] = None
|
|
136
|
+
services: list[AgentService] = Field(default_factory=list)
|
|
137
|
+
x402_support: bool = Field(False, alias="x402Support")
|
|
138
|
+
active: bool = True
|
|
139
|
+
registrations: list[AgentRegistration] = Field(default_factory=list)
|
|
140
|
+
supported_trust: list[str] = Field(default_factory=list, alias="supportedTrust")
|
|
141
|
+
|
|
142
|
+
class Config:
|
|
143
|
+
populate_by_name = True
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
class ReputationSummary(BaseModel):
|
|
147
|
+
"""Reputation summary for an agent."""
|
|
148
|
+
|
|
149
|
+
agent_id: int = Field(..., alias="agentId")
|
|
150
|
+
count: int
|
|
151
|
+
summary_value: int = Field(..., alias="summaryValue")
|
|
152
|
+
summary_value_decimals: int = Field(..., alias="summaryValueDecimals")
|
|
153
|
+
network: str
|
|
154
|
+
|
|
155
|
+
class Config:
|
|
156
|
+
populate_by_name = True
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class FeedbackEntry(BaseModel):
|
|
160
|
+
"""Individual feedback entry."""
|
|
161
|
+
|
|
162
|
+
client: str
|
|
163
|
+
feedback_index: int = Field(..., alias="feedbackIndex")
|
|
164
|
+
value: int
|
|
165
|
+
value_decimals: int = Field(..., alias="valueDecimals")
|
|
166
|
+
tag1: str
|
|
167
|
+
tag2: str
|
|
168
|
+
is_revoked: bool = Field(..., alias="isRevoked")
|
|
169
|
+
|
|
170
|
+
class Config:
|
|
171
|
+
populate_by_name = True
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
class ReputationResponse(BaseModel):
|
|
175
|
+
"""Reputation query response."""
|
|
176
|
+
|
|
177
|
+
agent_id: int = Field(..., alias="agentId")
|
|
178
|
+
summary: ReputationSummary
|
|
179
|
+
feedback: Optional[list[FeedbackEntry]] = None
|
|
180
|
+
network: str
|
|
181
|
+
|
|
182
|
+
class Config:
|
|
183
|
+
populate_by_name = True
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class FeedbackParams(BaseModel):
|
|
187
|
+
"""Parameters for submitting reputation feedback."""
|
|
188
|
+
|
|
189
|
+
agent_id: int = Field(..., alias="agentId")
|
|
190
|
+
value: int
|
|
191
|
+
value_decimals: int = Field(0, alias="valueDecimals")
|
|
192
|
+
tag1: str = ""
|
|
193
|
+
tag2: str = ""
|
|
194
|
+
endpoint: str = ""
|
|
195
|
+
feedback_uri: str = Field("", alias="feedbackUri")
|
|
196
|
+
feedback_hash: Optional[str] = Field(None, alias="feedbackHash")
|
|
197
|
+
proof: Optional[ProofOfPayment] = None
|
|
198
|
+
|
|
199
|
+
class Config:
|
|
200
|
+
populate_by_name = True
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
class FeedbackRequest(BaseModel):
|
|
204
|
+
"""Feedback request body for POST /feedback."""
|
|
205
|
+
|
|
206
|
+
x402_version: int = Field(1, alias="x402Version")
|
|
207
|
+
network: str
|
|
208
|
+
feedback: FeedbackParams
|
|
209
|
+
|
|
210
|
+
class Config:
|
|
211
|
+
populate_by_name = True
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
class FeedbackResponse(BaseModel):
|
|
215
|
+
"""Feedback response from POST /feedback."""
|
|
216
|
+
|
|
217
|
+
success: bool
|
|
218
|
+
transaction: Optional[str] = None
|
|
219
|
+
feedback_index: Optional[int] = Field(None, alias="feedbackIndex")
|
|
220
|
+
error: Optional[str] = None
|
|
221
|
+
network: str
|
|
222
|
+
|
|
223
|
+
class Config:
|
|
224
|
+
populate_by_name = True
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class SettleResponseWithProof(BaseModel):
|
|
228
|
+
"""Extended settle response with ERC-8004 proof of payment."""
|
|
229
|
+
|
|
230
|
+
success: bool
|
|
231
|
+
transaction_hash: Optional[str] = Field(None, alias="transactionHash")
|
|
232
|
+
network: Optional[str] = None
|
|
233
|
+
error: Optional[str] = None
|
|
234
|
+
payer: Optional[str] = None
|
|
235
|
+
proof_of_payment: Optional[ProofOfPayment] = Field(None, alias="proofOfPayment")
|
|
236
|
+
|
|
237
|
+
class Config:
|
|
238
|
+
populate_by_name = True
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
class Erc8004Client:
|
|
242
|
+
"""
|
|
243
|
+
Client for ERC-8004 Trustless Agents API.
|
|
244
|
+
|
|
245
|
+
Provides methods for:
|
|
246
|
+
- Querying agent identity
|
|
247
|
+
- Querying agent reputation
|
|
248
|
+
- Submitting reputation feedback
|
|
249
|
+
- Revoking feedback
|
|
250
|
+
|
|
251
|
+
Example:
|
|
252
|
+
>>> client = Erc8004Client()
|
|
253
|
+
>>>
|
|
254
|
+
>>> # Get agent identity
|
|
255
|
+
>>> identity = await client.get_identity("ethereum", 42)
|
|
256
|
+
>>> print(identity.agent_uri)
|
|
257
|
+
>>>
|
|
258
|
+
>>> # Get agent reputation
|
|
259
|
+
>>> reputation = await client.get_reputation("ethereum", 42)
|
|
260
|
+
>>> print(f"Score: {reputation.summary.summary_value}")
|
|
261
|
+
>>>
|
|
262
|
+
>>> # Submit feedback after payment
|
|
263
|
+
>>> result = await client.submit_feedback(
|
|
264
|
+
... network="ethereum",
|
|
265
|
+
... agent_id=42,
|
|
266
|
+
... value=95,
|
|
267
|
+
... tag1="quality",
|
|
268
|
+
... proof=settle_response.proof_of_payment,
|
|
269
|
+
... )
|
|
270
|
+
"""
|
|
271
|
+
|
|
272
|
+
def __init__(
|
|
273
|
+
self,
|
|
274
|
+
base_url: str = "https://facilitator.ultravioletadao.xyz",
|
|
275
|
+
timeout: float = 30.0,
|
|
276
|
+
):
|
|
277
|
+
"""
|
|
278
|
+
Initialize the ERC-8004 client.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
base_url: Base URL of the facilitator API
|
|
282
|
+
timeout: Request timeout in seconds
|
|
283
|
+
"""
|
|
284
|
+
self.base_url = base_url.rstrip("/")
|
|
285
|
+
self.timeout = timeout
|
|
286
|
+
self._client = httpx.AsyncClient(timeout=timeout)
|
|
287
|
+
|
|
288
|
+
async def __aenter__(self) -> "Erc8004Client":
|
|
289
|
+
return self
|
|
290
|
+
|
|
291
|
+
async def __aexit__(self, *args: Any) -> None:
|
|
292
|
+
await self._client.aclose()
|
|
293
|
+
|
|
294
|
+
async def get_identity(
|
|
295
|
+
self,
|
|
296
|
+
network: Erc8004Network,
|
|
297
|
+
agent_id: int,
|
|
298
|
+
) -> AgentIdentity:
|
|
299
|
+
"""
|
|
300
|
+
Get agent identity from the Identity Registry.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
network: Network where agent is registered
|
|
304
|
+
agent_id: Agent's tokenId
|
|
305
|
+
|
|
306
|
+
Returns:
|
|
307
|
+
Agent identity information
|
|
308
|
+
|
|
309
|
+
Raises:
|
|
310
|
+
httpx.HTTPStatusError: If the request fails
|
|
311
|
+
"""
|
|
312
|
+
url = f"{self.base_url}/identity/{network}/{agent_id}"
|
|
313
|
+
response = await self._client.get(url)
|
|
314
|
+
response.raise_for_status()
|
|
315
|
+
return AgentIdentity.model_validate(response.json())
|
|
316
|
+
|
|
317
|
+
async def resolve_agent_uri(self, agent_uri: str) -> AgentRegistrationFile:
|
|
318
|
+
"""
|
|
319
|
+
Resolve agent registration file from agentURI.
|
|
320
|
+
|
|
321
|
+
Args:
|
|
322
|
+
agent_uri: URI pointing to agent registration file
|
|
323
|
+
|
|
324
|
+
Returns:
|
|
325
|
+
Resolved agent registration file
|
|
326
|
+
|
|
327
|
+
Raises:
|
|
328
|
+
httpx.HTTPStatusError: If the request fails
|
|
329
|
+
"""
|
|
330
|
+
# Handle IPFS URIs
|
|
331
|
+
url = agent_uri
|
|
332
|
+
if agent_uri.startswith("ipfs://"):
|
|
333
|
+
cid = agent_uri.replace("ipfs://", "")
|
|
334
|
+
url = f"https://ipfs.io/ipfs/{cid}"
|
|
335
|
+
|
|
336
|
+
response = await self._client.get(url)
|
|
337
|
+
response.raise_for_status()
|
|
338
|
+
return AgentRegistrationFile.model_validate(response.json())
|
|
339
|
+
|
|
340
|
+
async def get_reputation(
|
|
341
|
+
self,
|
|
342
|
+
network: Erc8004Network,
|
|
343
|
+
agent_id: int,
|
|
344
|
+
*,
|
|
345
|
+
tag1: Optional[str] = None,
|
|
346
|
+
tag2: Optional[str] = None,
|
|
347
|
+
include_feedback: bool = False,
|
|
348
|
+
) -> ReputationResponse:
|
|
349
|
+
"""
|
|
350
|
+
Get agent reputation from the Reputation Registry.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
network: Network where agent is registered
|
|
354
|
+
agent_id: Agent's tokenId
|
|
355
|
+
tag1: Filter by primary tag
|
|
356
|
+
tag2: Filter by secondary tag
|
|
357
|
+
include_feedback: Include individual feedback entries
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
Reputation summary and optionally individual feedback entries
|
|
361
|
+
|
|
362
|
+
Raises:
|
|
363
|
+
httpx.HTTPStatusError: If the request fails
|
|
364
|
+
"""
|
|
365
|
+
params: dict[str, Any] = {}
|
|
366
|
+
if tag1:
|
|
367
|
+
params["tag1"] = tag1
|
|
368
|
+
if tag2:
|
|
369
|
+
params["tag2"] = tag2
|
|
370
|
+
if include_feedback:
|
|
371
|
+
params["includeFeedback"] = "true"
|
|
372
|
+
|
|
373
|
+
url = f"{self.base_url}/reputation/{network}/{agent_id}"
|
|
374
|
+
response = await self._client.get(url, params=params or None)
|
|
375
|
+
response.raise_for_status()
|
|
376
|
+
return ReputationResponse.model_validate(response.json())
|
|
377
|
+
|
|
378
|
+
async def submit_feedback(
|
|
379
|
+
self,
|
|
380
|
+
network: Erc8004Network,
|
|
381
|
+
agent_id: int,
|
|
382
|
+
value: int,
|
|
383
|
+
*,
|
|
384
|
+
value_decimals: int = 0,
|
|
385
|
+
tag1: str = "",
|
|
386
|
+
tag2: str = "",
|
|
387
|
+
endpoint: str = "",
|
|
388
|
+
feedback_uri: str = "",
|
|
389
|
+
feedback_hash: Optional[str] = None,
|
|
390
|
+
proof: Optional[ProofOfPayment] = None,
|
|
391
|
+
x402_version: int = 1,
|
|
392
|
+
) -> FeedbackResponse:
|
|
393
|
+
"""
|
|
394
|
+
Submit reputation feedback for an agent.
|
|
395
|
+
|
|
396
|
+
Requires proof of payment for authorized feedback submission.
|
|
397
|
+
|
|
398
|
+
Args:
|
|
399
|
+
network: Network where feedback will be submitted
|
|
400
|
+
agent_id: Agent's tokenId
|
|
401
|
+
value: Feedback value (e.g., 95 for 95/100)
|
|
402
|
+
value_decimals: Decimal places for value interpretation (0-18)
|
|
403
|
+
tag1: Primary categorization tag
|
|
404
|
+
tag2: Secondary categorization tag
|
|
405
|
+
endpoint: Service endpoint that was used
|
|
406
|
+
feedback_uri: URI to off-chain feedback file
|
|
407
|
+
feedback_hash: Keccak256 hash of feedback content
|
|
408
|
+
proof: Proof of payment (required for authorized feedback)
|
|
409
|
+
x402_version: x402 protocol version
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
Feedback response with transaction hash
|
|
413
|
+
|
|
414
|
+
Example:
|
|
415
|
+
>>> # After settling a payment with ERC-8004 extension
|
|
416
|
+
>>> result = await client.submit_feedback(
|
|
417
|
+
... network="ethereum",
|
|
418
|
+
... agent_id=42,
|
|
419
|
+
... value=95,
|
|
420
|
+
... tag1="quality",
|
|
421
|
+
... proof=settle_response.proof_of_payment,
|
|
422
|
+
... )
|
|
423
|
+
"""
|
|
424
|
+
request = FeedbackRequest(
|
|
425
|
+
x402_version=x402_version,
|
|
426
|
+
network=network,
|
|
427
|
+
feedback=FeedbackParams(
|
|
428
|
+
agent_id=agent_id,
|
|
429
|
+
value=value,
|
|
430
|
+
value_decimals=value_decimals,
|
|
431
|
+
tag1=tag1,
|
|
432
|
+
tag2=tag2,
|
|
433
|
+
endpoint=endpoint,
|
|
434
|
+
feedback_uri=feedback_uri,
|
|
435
|
+
feedback_hash=feedback_hash,
|
|
436
|
+
proof=proof,
|
|
437
|
+
),
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
url = f"{self.base_url}/feedback"
|
|
441
|
+
try:
|
|
442
|
+
response = await self._client.post(
|
|
443
|
+
url,
|
|
444
|
+
json=request.model_dump(by_alias=True, exclude_none=True),
|
|
445
|
+
)
|
|
446
|
+
response.raise_for_status()
|
|
447
|
+
return FeedbackResponse.model_validate(response.json())
|
|
448
|
+
except httpx.HTTPStatusError as e:
|
|
449
|
+
return FeedbackResponse(
|
|
450
|
+
success=False,
|
|
451
|
+
error=f"Facilitator error: {e.response.status_code} - {e.response.text}",
|
|
452
|
+
network=network,
|
|
453
|
+
)
|
|
454
|
+
except Exception as e:
|
|
455
|
+
return FeedbackResponse(
|
|
456
|
+
success=False,
|
|
457
|
+
error=str(e),
|
|
458
|
+
network=network,
|
|
459
|
+
)
|
|
460
|
+
|
|
461
|
+
async def revoke_feedback(
|
|
462
|
+
self,
|
|
463
|
+
network: Erc8004Network,
|
|
464
|
+
agent_id: int,
|
|
465
|
+
feedback_index: int,
|
|
466
|
+
*,
|
|
467
|
+
x402_version: int = 1,
|
|
468
|
+
) -> FeedbackResponse:
|
|
469
|
+
"""
|
|
470
|
+
Revoke previously submitted feedback.
|
|
471
|
+
|
|
472
|
+
Only the original submitter can revoke their feedback.
|
|
473
|
+
|
|
474
|
+
Args:
|
|
475
|
+
network: Network where feedback was submitted
|
|
476
|
+
agent_id: Agent ID
|
|
477
|
+
feedback_index: Index of feedback to revoke
|
|
478
|
+
x402_version: x402 protocol version
|
|
479
|
+
|
|
480
|
+
Returns:
|
|
481
|
+
Revocation result
|
|
482
|
+
"""
|
|
483
|
+
url = f"{self.base_url}/feedback/revoke"
|
|
484
|
+
try:
|
|
485
|
+
response = await self._client.post(
|
|
486
|
+
url,
|
|
487
|
+
json={
|
|
488
|
+
"x402Version": x402_version,
|
|
489
|
+
"network": network,
|
|
490
|
+
"agentId": agent_id,
|
|
491
|
+
"feedbackIndex": feedback_index,
|
|
492
|
+
},
|
|
493
|
+
)
|
|
494
|
+
response.raise_for_status()
|
|
495
|
+
return FeedbackResponse.model_validate(response.json())
|
|
496
|
+
except httpx.HTTPStatusError as e:
|
|
497
|
+
return FeedbackResponse(
|
|
498
|
+
success=False,
|
|
499
|
+
error=f"Facilitator error: {e.response.status_code} - {e.response.text}",
|
|
500
|
+
network=network,
|
|
501
|
+
)
|
|
502
|
+
except Exception as e:
|
|
503
|
+
return FeedbackResponse(
|
|
504
|
+
success=False,
|
|
505
|
+
error=str(e),
|
|
506
|
+
network=network,
|
|
507
|
+
)
|
|
508
|
+
|
|
509
|
+
def get_contracts(self, network: Erc8004Network) -> Optional[Erc8004ContractAddresses]:
|
|
510
|
+
"""
|
|
511
|
+
Get ERC-8004 contract addresses for a network.
|
|
512
|
+
|
|
513
|
+
Args:
|
|
514
|
+
network: Network to get contracts for
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
Contract addresses or None if not deployed
|
|
518
|
+
"""
|
|
519
|
+
return ERC8004_CONTRACTS.get(network)
|
|
520
|
+
|
|
521
|
+
def is_available(self, network: str) -> bool:
|
|
522
|
+
"""
|
|
523
|
+
Check if ERC-8004 is available on a network.
|
|
524
|
+
|
|
525
|
+
Args:
|
|
526
|
+
network: Network to check
|
|
527
|
+
|
|
528
|
+
Returns:
|
|
529
|
+
True if ERC-8004 contracts are deployed
|
|
530
|
+
"""
|
|
531
|
+
return network in ERC8004_CONTRACTS
|
|
532
|
+
|
|
533
|
+
async def get_feedback_metadata(self) -> dict[str, Any]:
|
|
534
|
+
"""
|
|
535
|
+
Get feedback endpoint metadata.
|
|
536
|
+
|
|
537
|
+
Returns:
|
|
538
|
+
Endpoint information for /feedback
|
|
539
|
+
"""
|
|
540
|
+
url = f"{self.base_url}/feedback"
|
|
541
|
+
response = await self._client.get(url)
|
|
542
|
+
response.raise_for_status()
|
|
543
|
+
return response.json()
|
|
544
|
+
|
|
545
|
+
async def append_response(
|
|
546
|
+
self,
|
|
547
|
+
network: Erc8004Network,
|
|
548
|
+
agent_id: int,
|
|
549
|
+
feedback_index: int,
|
|
550
|
+
response_text: str,
|
|
551
|
+
*,
|
|
552
|
+
response_uri: Optional[str] = None,
|
|
553
|
+
x402_version: int = 1,
|
|
554
|
+
) -> FeedbackResponse:
|
|
555
|
+
"""
|
|
556
|
+
Append a response to existing feedback.
|
|
557
|
+
|
|
558
|
+
Allows agents to respond to feedback they received.
|
|
559
|
+
Only the agent (identity owner) can append responses.
|
|
560
|
+
|
|
561
|
+
Args:
|
|
562
|
+
network: Network where feedback was submitted
|
|
563
|
+
agent_id: Agent ID
|
|
564
|
+
feedback_index: Index of feedback to respond to
|
|
565
|
+
response_text: Response content
|
|
566
|
+
response_uri: Optional URI to off-chain response file
|
|
567
|
+
x402_version: x402 protocol version
|
|
568
|
+
|
|
569
|
+
Returns:
|
|
570
|
+
Response result
|
|
571
|
+
|
|
572
|
+
Example:
|
|
573
|
+
>>> # Agent responds to feedback
|
|
574
|
+
>>> result = await client.append_response(
|
|
575
|
+
... network="ethereum",
|
|
576
|
+
... agent_id=42,
|
|
577
|
+
... feedback_index=1,
|
|
578
|
+
... response_text="Thank you for your feedback!",
|
|
579
|
+
... )
|
|
580
|
+
"""
|
|
581
|
+
url = f"{self.base_url}/feedback/response"
|
|
582
|
+
payload: dict[str, Any] = {
|
|
583
|
+
"x402Version": x402_version,
|
|
584
|
+
"network": network,
|
|
585
|
+
"agentId": agent_id,
|
|
586
|
+
"feedbackIndex": feedback_index,
|
|
587
|
+
"response": response_text,
|
|
588
|
+
}
|
|
589
|
+
if response_uri:
|
|
590
|
+
payload["responseUri"] = response_uri
|
|
591
|
+
|
|
592
|
+
try:
|
|
593
|
+
response = await self._client.post(url, json=payload)
|
|
594
|
+
response.raise_for_status()
|
|
595
|
+
return FeedbackResponse.model_validate(response.json())
|
|
596
|
+
except httpx.HTTPStatusError as e:
|
|
597
|
+
return FeedbackResponse(
|
|
598
|
+
success=False,
|
|
599
|
+
error=f"Facilitator error: {e.response.status_code} - {e.response.text}",
|
|
600
|
+
network=network,
|
|
601
|
+
)
|
|
602
|
+
except Exception as e:
|
|
603
|
+
return FeedbackResponse(
|
|
604
|
+
success=False,
|
|
605
|
+
error=str(e),
|
|
606
|
+
network=network,
|
|
607
|
+
)
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def build_erc8004_payment_requirements(
|
|
611
|
+
amount: str,
|
|
612
|
+
recipient: str,
|
|
613
|
+
resource: str,
|
|
614
|
+
*,
|
|
615
|
+
network: str = "base",
|
|
616
|
+
description: str = "Payment for resource access",
|
|
617
|
+
mime_type: str = "application/json",
|
|
618
|
+
timeout_seconds: int = 300,
|
|
619
|
+
) -> dict[str, Any]:
|
|
620
|
+
"""
|
|
621
|
+
Build payment requirements with ERC-8004 extension.
|
|
622
|
+
|
|
623
|
+
Adds the 8004-reputation extension to include proof of payment
|
|
624
|
+
in settlement responses for reputation submission.
|
|
625
|
+
|
|
626
|
+
Args:
|
|
627
|
+
amount: Amount in human-readable format (e.g., "1.00")
|
|
628
|
+
recipient: Recipient address
|
|
629
|
+
resource: Resource URL being protected
|
|
630
|
+
network: Chain name (e.g., "base", "ethereum")
|
|
631
|
+
description: Description of the resource
|
|
632
|
+
mime_type: MIME type of the resource
|
|
633
|
+
timeout_seconds: Maximum timeout in seconds
|
|
634
|
+
|
|
635
|
+
Returns:
|
|
636
|
+
Payment requirements with ERC-8004 extension
|
|
637
|
+
|
|
638
|
+
Example:
|
|
639
|
+
>>> requirements = build_erc8004_payment_requirements(
|
|
640
|
+
... amount="1.00",
|
|
641
|
+
... recipient="0x...",
|
|
642
|
+
... resource="https://api.example.com/service",
|
|
643
|
+
... network="ethereum",
|
|
644
|
+
... )
|
|
645
|
+
>>> # Settlement will include proofOfPayment
|
|
646
|
+
>>> result = await facilitator.settle(payment, requirements)
|
|
647
|
+
>>> print(result.proof_of_payment)
|
|
648
|
+
"""
|
|
649
|
+
return {
|
|
650
|
+
"scheme": "exact",
|
|
651
|
+
"network": network,
|
|
652
|
+
"maxAmountRequired": str(int(float(amount) * 1_000_000)), # 6 decimals
|
|
653
|
+
"resource": resource,
|
|
654
|
+
"description": description,
|
|
655
|
+
"mimeType": mime_type,
|
|
656
|
+
"payTo": recipient,
|
|
657
|
+
"maxTimeoutSeconds": timeout_seconds,
|
|
658
|
+
"extra": {
|
|
659
|
+
ERC8004_EXTENSION_ID: {
|
|
660
|
+
"includeProof": True,
|
|
661
|
+
},
|
|
662
|
+
},
|
|
663
|
+
}
|