agent0-sdk 0.31__py3-none-any.whl → 1.0.1__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.
- agent0_sdk/__init__.py +1 -1
- agent0_sdk/core/agent.py +172 -30
- agent0_sdk/core/contracts.py +93 -58
- agent0_sdk/core/feedback_manager.py +90 -161
- agent0_sdk/core/indexer.py +54 -26
- agent0_sdk/core/models.py +6 -19
- agent0_sdk/core/oasf_validator.py +1 -1
- agent0_sdk/core/sdk.py +31 -16
- agent0_sdk/core/subgraph_client.py +34 -15
- agent0_sdk/core/web3_client.py +184 -17
- {agent0_sdk-0.31.dist-info → agent0_sdk-1.0.1.dist-info}/METADATA +22 -14
- agent0_sdk-1.0.1.dist-info/RECORD +19 -0
- {agent0_sdk-0.31.dist-info → agent0_sdk-1.0.1.dist-info}/top_level.txt +0 -1
- agent0_sdk-0.31.dist-info/RECORD +0 -33
- tests/__init__.py +0 -1
- tests/config.py +0 -46
- tests/conftest.py +0 -22
- tests/discover_test_data.py +0 -445
- tests/test_feedback.py +0 -417
- tests/test_models.py +0 -224
- tests/test_multi_chain.py +0 -588
- tests/test_oasf_management.py +0 -404
- tests/test_real_public_servers.py +0 -103
- tests/test_registration.py +0 -267
- tests/test_registrationIpfs.py +0 -227
- tests/test_sdk.py +0 -240
- tests/test_search.py +0 -415
- tests/test_transfer.py +0 -255
- {agent0_sdk-0.31.dist-info → agent0_sdk-1.0.1.dist-info}/WHEEL +0 -0
- {agent0_sdk-0.31.dist-info → agent0_sdk-1.0.1.dist-info}/licenses/LICENSE +0 -0
agent0_sdk/core/sdk.py
CHANGED
|
@@ -278,7 +278,12 @@ class SDK:
|
|
|
278
278
|
return Agent(sdk=self, registration_file=registration_file)
|
|
279
279
|
|
|
280
280
|
def loadAgent(self, agentId: AgentId) -> Agent:
|
|
281
|
-
"""Load an existing agent (hydrates from registration file if registered).
|
|
281
|
+
"""Load an existing agent (hydrates from registration file if registered).
|
|
282
|
+
|
|
283
|
+
Note: Agents can be minted with an empty token URI (e.g. IPFS flow where publish fails).
|
|
284
|
+
In that case we return a partially-hydrated Agent with an empty registration file so the
|
|
285
|
+
caller can resume publishing and set the URI later.
|
|
286
|
+
"""
|
|
282
287
|
# Convert agentId to string if it's an integer
|
|
283
288
|
agentId = str(agentId)
|
|
284
289
|
|
|
@@ -292,16 +297,22 @@ class SDK:
|
|
|
292
297
|
|
|
293
298
|
# Get token URI from contract
|
|
294
299
|
try:
|
|
295
|
-
|
|
296
|
-
self.identity_registry, "tokenURI", int(token_id)
|
|
300
|
+
agent_uri = self.web3_client.call_contract(
|
|
301
|
+
self.identity_registry, "tokenURI", int(token_id) # tokenURI is ERC-721 standard, but represents agentURI
|
|
297
302
|
)
|
|
298
303
|
except Exception as e:
|
|
299
304
|
raise ValueError(f"Failed to load agent {agentId}: {e}")
|
|
300
305
|
|
|
301
|
-
# Load registration file
|
|
302
|
-
registration_file = self._load_registration_file(
|
|
306
|
+
# Load registration file (or fall back to a minimal file if agent URI is missing)
|
|
307
|
+
registration_file = self._load_registration_file(agent_uri)
|
|
303
308
|
registration_file.agentId = agentId
|
|
304
|
-
registration_file.agentURI =
|
|
309
|
+
registration_file.agentURI = agent_uri if agent_uri else None
|
|
310
|
+
|
|
311
|
+
if not agent_uri or not str(agent_uri).strip():
|
|
312
|
+
logger.warning(
|
|
313
|
+
f"Agent {agentId} has no agentURI set on-chain yet. "
|
|
314
|
+
"Returning a partial agent; update info and call registerIPFS() to publish and set URI."
|
|
315
|
+
)
|
|
305
316
|
|
|
306
317
|
# Store registry address for proper JSON generation
|
|
307
318
|
registry_address = self._registries.get("IDENTITY")
|
|
@@ -315,7 +326,13 @@ class SDK:
|
|
|
315
326
|
return Agent(sdk=self, registration_file=registration_file)
|
|
316
327
|
|
|
317
328
|
def _load_registration_file(self, uri: str) -> RegistrationFile:
|
|
318
|
-
"""Load registration file from URI.
|
|
329
|
+
"""Load registration file from URI.
|
|
330
|
+
|
|
331
|
+
If uri is empty/None/whitespace, returns an empty RegistrationFile to allow resume flows.
|
|
332
|
+
"""
|
|
333
|
+
if not uri or not str(uri).strip():
|
|
334
|
+
return RegistrationFile()
|
|
335
|
+
|
|
319
336
|
if uri.startswith("ipfs://"):
|
|
320
337
|
if not self.ipfs_client:
|
|
321
338
|
raise ValueError("IPFS client not configured")
|
|
@@ -346,21 +363,20 @@ class SDK:
|
|
|
346
363
|
# For now, we'll leave it empty
|
|
347
364
|
registration_file.operators = []
|
|
348
365
|
|
|
349
|
-
# Hydrate
|
|
366
|
+
# Hydrate agentWallet from on-chain (now uses getAgentWallet() instead of metadata)
|
|
350
367
|
agent_id = token_id
|
|
351
368
|
try:
|
|
352
|
-
#
|
|
353
|
-
|
|
354
|
-
self.identity_registry, "
|
|
369
|
+
# Get agentWallet using the new dedicated function
|
|
370
|
+
wallet_address = self.web3_client.call_contract(
|
|
371
|
+
self.identity_registry, "getAgentWallet", agent_id
|
|
355
372
|
)
|
|
356
|
-
if
|
|
357
|
-
wallet_address = "0x" + wallet_bytes.hex()
|
|
373
|
+
if wallet_address and wallet_address != "0x0000000000000000000000000000000000000000":
|
|
358
374
|
registration_file.walletAddress = wallet_address
|
|
359
375
|
# If wallet is read from on-chain, use current chain ID
|
|
360
376
|
# (the chain ID from the registration file might be outdated)
|
|
361
377
|
registration_file.walletChainId = self.chainId
|
|
362
378
|
except Exception as e:
|
|
363
|
-
# No on-chain wallet, will fall back to registration file
|
|
379
|
+
# No on-chain wallet set, will fall back to registration file
|
|
364
380
|
pass
|
|
365
381
|
|
|
366
382
|
try:
|
|
@@ -904,11 +920,10 @@ class SDK:
|
|
|
904
920
|
agentId: "AgentId",
|
|
905
921
|
feedbackFile: Dict[str, Any],
|
|
906
922
|
idem: Optional["IdemKey"] = None,
|
|
907
|
-
feedbackAuth: Optional[bytes] = None,
|
|
908
923
|
) -> "Feedback":
|
|
909
924
|
"""Give feedback (maps 8004 endpoint)."""
|
|
910
925
|
return self.feedback_manager.giveFeedback(
|
|
911
|
-
agentId, feedbackFile, idem
|
|
926
|
+
agentId, feedbackFile, idem
|
|
912
927
|
)
|
|
913
928
|
|
|
914
929
|
def getFeedback(
|
|
@@ -30,25 +30,38 @@ class SubgraphClient:
|
|
|
30
30
|
Returns:
|
|
31
31
|
JSON response from the subgraph
|
|
32
32
|
"""
|
|
33
|
-
|
|
33
|
+
def _do_query(q: str) -> Dict[str, Any]:
|
|
34
34
|
response = requests.post(
|
|
35
35
|
self.subgraph_url,
|
|
36
|
-
json={
|
|
37
|
-
'query': query,
|
|
38
|
-
'variables': variables or {}
|
|
39
|
-
},
|
|
36
|
+
json={'query': q, 'variables': variables or {}},
|
|
40
37
|
headers={'Content-Type': 'application/json'},
|
|
41
|
-
timeout=10
|
|
38
|
+
timeout=10,
|
|
42
39
|
)
|
|
43
40
|
response.raise_for_status()
|
|
44
41
|
result = response.json()
|
|
45
|
-
|
|
46
|
-
# Check for GraphQL errors
|
|
47
42
|
if 'errors' in result:
|
|
48
43
|
error_messages = [err.get('message', 'Unknown error') for err in result['errors']]
|
|
49
44
|
raise ValueError(f"GraphQL errors: {', '.join(error_messages)}")
|
|
50
|
-
|
|
51
45
|
return result.get('data', {})
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
return _do_query(query)
|
|
49
|
+
except ValueError as e:
|
|
50
|
+
# Backwards/forwards compatibility for hosted subgraphs:
|
|
51
|
+
# Some deployments still expose `responseUri` instead of `responseURI`.
|
|
52
|
+
msg = str(e)
|
|
53
|
+
if ("has no field" in msg and "responseURI" in msg) and ("responseURI" in query):
|
|
54
|
+
logger.debug("Subgraph schema missing responseURI; retrying query with responseUri")
|
|
55
|
+
return _do_query(query.replace("responseURI", "responseUri"))
|
|
56
|
+
# Some deployments don't expose agentWallet fields on AgentRegistrationFile.
|
|
57
|
+
if (
|
|
58
|
+
"Type `AgentRegistrationFile` has no field `agentWallet`" in msg
|
|
59
|
+
or "Type `AgentRegistrationFile` has no field `agentWalletChainId`" in msg
|
|
60
|
+
):
|
|
61
|
+
logger.debug("Subgraph schema missing agentWallet fields; retrying query without them")
|
|
62
|
+
q2 = query.replace("agentWalletChainId", "").replace("agentWallet", "")
|
|
63
|
+
return _do_query(q2)
|
|
64
|
+
raise
|
|
52
65
|
except requests.exceptions.RequestException as e:
|
|
53
66
|
raise ConnectionError(f"Failed to query subgraph: {e}")
|
|
54
67
|
|
|
@@ -248,10 +261,12 @@ class SubgraphClient:
|
|
|
248
261
|
) {{
|
|
249
262
|
id
|
|
250
263
|
score
|
|
264
|
+
feedbackIndex
|
|
251
265
|
tag1
|
|
252
266
|
tag2
|
|
267
|
+
endpoint
|
|
253
268
|
clientAddress
|
|
254
|
-
|
|
269
|
+
feedbackURI
|
|
255
270
|
feedbackURIType
|
|
256
271
|
feedbackHash
|
|
257
272
|
isRevoked
|
|
@@ -276,7 +291,7 @@ class SubgraphClient:
|
|
|
276
291
|
responses {{
|
|
277
292
|
id
|
|
278
293
|
responder
|
|
279
|
-
|
|
294
|
+
responseURI
|
|
280
295
|
responseHash
|
|
281
296
|
createdAt
|
|
282
297
|
}}
|
|
@@ -400,10 +415,12 @@ class SubgraphClient:
|
|
|
400
415
|
id
|
|
401
416
|
agent { id agentId chainId }
|
|
402
417
|
clientAddress
|
|
418
|
+
feedbackIndex
|
|
403
419
|
score
|
|
404
420
|
tag1
|
|
405
421
|
tag2
|
|
406
|
-
|
|
422
|
+
endpoint
|
|
423
|
+
feedbackURI
|
|
407
424
|
feedbackURIType
|
|
408
425
|
feedbackHash
|
|
409
426
|
isRevoked
|
|
@@ -429,7 +446,7 @@ class SubgraphClient:
|
|
|
429
446
|
responses {
|
|
430
447
|
id
|
|
431
448
|
responder
|
|
432
|
-
|
|
449
|
+
responseURI
|
|
433
450
|
responseHash
|
|
434
451
|
createdAt
|
|
435
452
|
}
|
|
@@ -548,10 +565,12 @@ class SubgraphClient:
|
|
|
548
565
|
id
|
|
549
566
|
agent {{ id agentId chainId }}
|
|
550
567
|
clientAddress
|
|
568
|
+
feedbackIndex
|
|
551
569
|
score
|
|
552
570
|
tag1
|
|
553
571
|
tag2
|
|
554
|
-
|
|
572
|
+
endpoint
|
|
573
|
+
feedbackURI
|
|
555
574
|
feedbackURIType
|
|
556
575
|
feedbackHash
|
|
557
576
|
isRevoked
|
|
@@ -577,7 +596,7 @@ class SubgraphClient:
|
|
|
577
596
|
responses {{
|
|
578
597
|
id
|
|
579
598
|
responder
|
|
580
|
-
|
|
599
|
+
responseURI
|
|
581
600
|
responseHash
|
|
582
601
|
createdAt
|
|
583
602
|
}}
|
agent0_sdk/core/web3_client.py
CHANGED
|
@@ -5,7 +5,7 @@ Web3 integration layer for smart contract interactions.
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
7
|
import json
|
|
8
|
-
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
8
|
+
from typing import Any, Dict, List, Optional, Tuple, Union, Callable
|
|
9
9
|
|
|
10
10
|
try:
|
|
11
11
|
from web3 import Web3
|
|
@@ -123,22 +123,6 @@ class Web3Client:
|
|
|
123
123
|
|
|
124
124
|
return event_filter.get_all_entries()
|
|
125
125
|
|
|
126
|
-
def encodeFeedbackAuth(
|
|
127
|
-
self,
|
|
128
|
-
agentId: int,
|
|
129
|
-
clientAddress: str,
|
|
130
|
-
indexLimit: int,
|
|
131
|
-
expiry: int,
|
|
132
|
-
chainId: int,
|
|
133
|
-
identityRegistry: str,
|
|
134
|
-
signerAddress: str
|
|
135
|
-
) -> bytes:
|
|
136
|
-
"""Encode feedback authorization data."""
|
|
137
|
-
return self.w3.codec.encode(
|
|
138
|
-
['uint256', 'address', 'uint64', 'uint256', 'uint256', 'address', 'address'],
|
|
139
|
-
[agentId, clientAddress, indexLimit, expiry, chainId, identityRegistry, signerAddress]
|
|
140
|
-
)
|
|
141
|
-
|
|
142
126
|
def signMessage(self, message: bytes) -> bytes:
|
|
143
127
|
"""Sign a message with the account's private key."""
|
|
144
128
|
# Create a SignableMessage from the raw bytes
|
|
@@ -190,3 +174,186 @@ class Web3Client:
|
|
|
190
174
|
def get_transaction_count(self, address: str) -> int:
|
|
191
175
|
"""Get transaction count (nonce) of an address."""
|
|
192
176
|
return self.w3.eth.get_transaction_count(address)
|
|
177
|
+
|
|
178
|
+
def encodeEIP712Domain(
|
|
179
|
+
self,
|
|
180
|
+
name: str,
|
|
181
|
+
version: str,
|
|
182
|
+
chain_id: int,
|
|
183
|
+
verifying_contract: str
|
|
184
|
+
) -> Dict[str, Any]:
|
|
185
|
+
"""Encode EIP-712 domain separator.
|
|
186
|
+
|
|
187
|
+
Args:
|
|
188
|
+
name: Contract name
|
|
189
|
+
version: Contract version
|
|
190
|
+
chain_id: Chain ID
|
|
191
|
+
verifying_contract: Contract address
|
|
192
|
+
|
|
193
|
+
Returns:
|
|
194
|
+
Domain separator dictionary
|
|
195
|
+
"""
|
|
196
|
+
return {
|
|
197
|
+
"name": name,
|
|
198
|
+
"version": version,
|
|
199
|
+
"chainId": chain_id,
|
|
200
|
+
"verifyingContract": verifying_contract
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
def build_agent_wallet_set_typed_data(
|
|
204
|
+
self,
|
|
205
|
+
agent_id: int,
|
|
206
|
+
new_wallet: str,
|
|
207
|
+
owner: str,
|
|
208
|
+
deadline: int,
|
|
209
|
+
verifying_contract: str,
|
|
210
|
+
chain_id: int,
|
|
211
|
+
) -> Dict[str, Any]:
|
|
212
|
+
"""Build EIP-712 typed data for ERC-8004 IdentityRegistry setAgentWallet.
|
|
213
|
+
|
|
214
|
+
Contract expects:
|
|
215
|
+
- domain: name="ERC8004IdentityRegistry", version="1"
|
|
216
|
+
- primaryType: "AgentWalletSet"
|
|
217
|
+
- message: { agentId, newWallet, owner, deadline }
|
|
218
|
+
"""
|
|
219
|
+
domain = self.encodeEIP712Domain(
|
|
220
|
+
name="ERC8004IdentityRegistry",
|
|
221
|
+
version="1",
|
|
222
|
+
chain_id=chain_id,
|
|
223
|
+
verifying_contract=verifying_contract,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
message_types = {
|
|
227
|
+
"AgentWalletSet": [
|
|
228
|
+
{"name": "agentId", "type": "uint256"},
|
|
229
|
+
{"name": "newWallet", "type": "address"},
|
|
230
|
+
{"name": "owner", "type": "address"},
|
|
231
|
+
{"name": "deadline", "type": "uint256"},
|
|
232
|
+
]
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
message = {
|
|
236
|
+
"agentId": agent_id,
|
|
237
|
+
"newWallet": new_wallet,
|
|
238
|
+
"owner": owner,
|
|
239
|
+
"deadline": deadline,
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
# eth_account.messages.encode_typed_data expects the "full_message" format
|
|
243
|
+
return {
|
|
244
|
+
"types": {
|
|
245
|
+
"EIP712Domain": [
|
|
246
|
+
{"name": "name", "type": "string"},
|
|
247
|
+
{"name": "version", "type": "string"},
|
|
248
|
+
{"name": "chainId", "type": "uint256"},
|
|
249
|
+
{"name": "verifyingContract", "type": "address"},
|
|
250
|
+
],
|
|
251
|
+
**message_types,
|
|
252
|
+
},
|
|
253
|
+
"domain": domain,
|
|
254
|
+
"primaryType": "AgentWalletSet",
|
|
255
|
+
"message": message,
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
def sign_typed_data(
|
|
259
|
+
self,
|
|
260
|
+
full_message: Dict[str, Any],
|
|
261
|
+
signer: Union[str, BaseAccount],
|
|
262
|
+
) -> bytes:
|
|
263
|
+
"""Sign EIP-712 typed data with a provided signer (EOA).
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
full_message: Typed data dict compatible with encode_typed_data(full_message=...)
|
|
267
|
+
signer: Private key string or eth_account BaseAccount/LocalAccount
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Signature bytes
|
|
271
|
+
"""
|
|
272
|
+
from eth_account.messages import encode_typed_data
|
|
273
|
+
|
|
274
|
+
if isinstance(signer, str):
|
|
275
|
+
acct: BaseAccount = Account.from_key(signer)
|
|
276
|
+
else:
|
|
277
|
+
acct = signer
|
|
278
|
+
|
|
279
|
+
encoded = encode_typed_data(full_message=full_message)
|
|
280
|
+
signed = acct.sign_message(encoded)
|
|
281
|
+
return signed.signature
|
|
282
|
+
|
|
283
|
+
def signEIP712Message(
|
|
284
|
+
self,
|
|
285
|
+
domain: Dict[str, Any],
|
|
286
|
+
message_types: Dict[str, List[Dict[str, str]]],
|
|
287
|
+
message: Dict[str, Any]
|
|
288
|
+
) -> bytes:
|
|
289
|
+
"""Sign an EIP-712 typed message.
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
domain: EIP-712 domain separator
|
|
293
|
+
message_types: Type definitions for the message
|
|
294
|
+
message: Message data to sign
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
Signature bytes
|
|
298
|
+
"""
|
|
299
|
+
if not self.account:
|
|
300
|
+
raise ValueError("Cannot sign message: SDK is in read-only mode. Provide a signer to enable signing.")
|
|
301
|
+
|
|
302
|
+
from eth_account.messages import encode_typed_data
|
|
303
|
+
|
|
304
|
+
structured_data = {
|
|
305
|
+
"types": {
|
|
306
|
+
"EIP712Domain": [
|
|
307
|
+
{"name": "name", "type": "string"},
|
|
308
|
+
{"name": "version", "type": "string"},
|
|
309
|
+
{"name": "chainId", "type": "uint256"},
|
|
310
|
+
{"name": "verifyingContract", "type": "address"}
|
|
311
|
+
],
|
|
312
|
+
**message_types
|
|
313
|
+
},
|
|
314
|
+
"domain": domain,
|
|
315
|
+
"primaryType": list(message_types.keys())[0] if message_types else "Message",
|
|
316
|
+
"message": message
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
encoded = encode_typed_data(full_message=structured_data)
|
|
320
|
+
signed = self.account.sign_message(encoded)
|
|
321
|
+
return signed.signature
|
|
322
|
+
|
|
323
|
+
def verifyEIP712Signature(
|
|
324
|
+
self,
|
|
325
|
+
domain: Dict[str, Any],
|
|
326
|
+
message_types: Dict[str, List[Dict[str, str]]],
|
|
327
|
+
message: Dict[str, Any],
|
|
328
|
+
signature: bytes
|
|
329
|
+
) -> str:
|
|
330
|
+
"""Verify an EIP-712 signature and recover the signer address.
|
|
331
|
+
|
|
332
|
+
Args:
|
|
333
|
+
domain: EIP-712 domain separator
|
|
334
|
+
message_types: Type definitions for the message
|
|
335
|
+
message: Message data that was signed
|
|
336
|
+
signature: Signature bytes to verify
|
|
337
|
+
|
|
338
|
+
Returns:
|
|
339
|
+
Recovered signer address
|
|
340
|
+
"""
|
|
341
|
+
from eth_account.messages import encode_typed_data
|
|
342
|
+
|
|
343
|
+
structured_data = {
|
|
344
|
+
"types": {
|
|
345
|
+
"EIP712Domain": [
|
|
346
|
+
{"name": "name", "type": "string"},
|
|
347
|
+
{"name": "version", "type": "string"},
|
|
348
|
+
{"name": "chainId", "type": "uint256"},
|
|
349
|
+
{"name": "verifyingContract", "type": "address"}
|
|
350
|
+
],
|
|
351
|
+
**message_types
|
|
352
|
+
},
|
|
353
|
+
"domain": domain,
|
|
354
|
+
"primaryType": list(message_types.keys())[0] if message_types else "Message",
|
|
355
|
+
"message": message
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
encoded = encode_typed_data(full_message=structured_data)
|
|
359
|
+
return self.w3.eth.account.recover_message(encoded, signature=signature)
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent0-sdk
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: Python SDK for agent portability, discovery and trust based on ERC-8004
|
|
5
|
-
Author-email: Marco De Rossi <marco
|
|
5
|
+
Author-email: Marco De Rossi <marco@ag0.xyz>
|
|
6
6
|
License: MIT License
|
|
7
7
|
|
|
8
8
|
Copyright (c) 2025 Marco De Rossi
|
|
@@ -76,7 +76,7 @@ Agent0 is the SDK for agentic economies. It enables agents to register, advertis
|
|
|
76
76
|
|
|
77
77
|
## What Does Agent0 SDK Do?
|
|
78
78
|
|
|
79
|
-
Agent0 SDK
|
|
79
|
+
Agent0 SDK enables you to:
|
|
80
80
|
|
|
81
81
|
- **Create and manage agent identities** - Register your AI agent on-chain with a unique identity, configure presentation fields (name, description, image), set wallet addresses, and manage trust models with x402 support
|
|
82
82
|
- **Advertise agent capabilities** - Publish MCP and A2A endpoints, with automated extraction of MCP tools and A2A skills from endpoints
|
|
@@ -86,11 +86,7 @@ Agent0 SDK v0.31 enables you to:
|
|
|
86
86
|
- **Cross-chain registration** - One-line registration with IPFS nodes, Pinata, Filecoin, or HTTP URIs
|
|
87
87
|
- **Public indexing** - Subgraph indexing both on-chain and IPFS data for fast search and retrieval
|
|
88
88
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
Agent0 SDK v0.31 is in **alpha** with bugs and is not production ready. We're actively testing and improving it.
|
|
92
|
-
|
|
93
|
-
**Bug reports & feedback:** GitHub: [Report issues](https://github.com/agent0lab/agent0-py/issues) | Telegram: [@marcoderossi](https://t.me/marcoderossi) | Email: marco.derossi@consensys.net
|
|
89
|
+
**Bug reports & feedback:** GitHub: [Report issues](https://github.com/agent0lab/agent0-py/issues) | Telegram: [@marcoderossi](https://t.me/marcoderossi) | Email: marco@ag0.xyz
|
|
94
90
|
|
|
95
91
|
## Installation
|
|
96
92
|
|
|
@@ -152,12 +148,10 @@ agent.setENS("myagent.eth")
|
|
|
152
148
|
|
|
153
149
|
# Add OASF skills and domains (standardized taxonomies)
|
|
154
150
|
agent.addSkill("data_engineering/data_transformation_pipeline", validate_oasf=True)
|
|
155
|
-
agent.addSkill("natural_language_processing/summarization", validate_oasf=True)
|
|
151
|
+
agent.addSkill("natural_language_processing/natural_language_generation/summarization", validate_oasf=True)
|
|
156
152
|
agent.addDomain("finance_and_business/investment_services", validate_oasf=True)
|
|
157
|
-
agent.addDomain("technology/data_science", validate_oasf=True)
|
|
153
|
+
agent.addDomain("technology/data_science/data_science", validate_oasf=True)
|
|
158
154
|
|
|
159
|
-
# Configure wallet and trust
|
|
160
|
-
agent.setAgentWallet("0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb", chainId=11155111)
|
|
161
155
|
agent.setTrust(reputation=True, cryptoEconomic=True)
|
|
162
156
|
|
|
163
157
|
# Add metadata and set status
|
|
@@ -168,6 +162,16 @@ agent.setActive(True)
|
|
|
168
162
|
agent.registerIPFS()
|
|
169
163
|
print(f"Agent registered: {agent.agentId}") # e.g., "11155111:123"
|
|
170
164
|
print(f"Agent URI: {agent.agentURI}") # e.g., "ipfs://Qm..."
|
|
165
|
+
|
|
166
|
+
# (Optional) Change the agent wallet after registration
|
|
167
|
+
# - On mint/registration, `agentWallet` defaults to the current owner address.
|
|
168
|
+
# - Call this only if you want a DIFFERENT wallet (or after a transfer, since the wallet resets to zero).
|
|
169
|
+
# - Transaction is sent by the SDK signer (agent owner), but the signature must be produced by the NEW wallet.
|
|
170
|
+
agent.setAgentWallet(
|
|
171
|
+
"0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
|
|
172
|
+
chainId=11155111,
|
|
173
|
+
new_wallet_signer=os.getenv("NEW_WALLET_PRIVATE_KEY"),
|
|
174
|
+
)
|
|
171
175
|
```
|
|
172
176
|
|
|
173
177
|
### 3. Load and Edit Agent
|
|
@@ -213,7 +217,8 @@ agent_summary = sdk.getAgent("11155111:123")
|
|
|
213
217
|
feedback_file = sdk.prepareFeedback(
|
|
214
218
|
agentId="11155111:123",
|
|
215
219
|
score=85, # 0-100 (mandatory)
|
|
216
|
-
tags=["data_analyst", "finance"], # Optional
|
|
220
|
+
tags=["data_analyst", "finance"], # Optional: tags are now strings (not bytes32)
|
|
221
|
+
endpoint="https://example.com/endpoint", # Optional: endpoint URI associated with feedback
|
|
217
222
|
capability="tools", # Optional: MCP capability
|
|
218
223
|
name="code_generation", # Optional: MCP tool name
|
|
219
224
|
skill="python" # Optional: A2A skill
|
|
@@ -307,7 +312,7 @@ OASF skills and domains appear in your agent's registration file:
|
|
|
307
312
|
],
|
|
308
313
|
"domains": [
|
|
309
314
|
"finance_and_business/investment_services",
|
|
310
|
-
"technology/data_science"
|
|
315
|
+
"technology/data_science/data_science"
|
|
311
316
|
]
|
|
312
317
|
}
|
|
313
318
|
]
|
|
@@ -347,6 +352,9 @@ Complete working examples are available in the `tests/` directory:
|
|
|
347
352
|
- `test_feedback.py` - Complete feedback flow with IPFS storage
|
|
348
353
|
- `test_search.py` - Agent search and discovery
|
|
349
354
|
- `test_transfer.py` - Agent ownership transfer
|
|
355
|
+
- `test_oasf_management.py` - OASF skills/domains management (unit tests)
|
|
356
|
+
- `test_real_public_servers.py` - Endpoint crawler against real public MCP/A2A servers
|
|
357
|
+
- `test_multi_chain.py` - Multi-chain read-only operations (subgraph-based)
|
|
350
358
|
|
|
351
359
|
## Documentation
|
|
352
360
|
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
agent0_sdk/__init__.py,sha256=Rtb9_6FYcrqQw0sV7gmox6nj3W3IawaY4LLZriMbb20,919
|
|
2
|
+
agent0_sdk/core/agent.py,sha256=L8pDTXUWZ5ZmFgGjBwdheKTvMcGu9SlUP9MQD9TQ0MU,45061
|
|
3
|
+
agent0_sdk/core/contracts.py,sha256=euFfjnmpYHNfzzQFvYTvSI-xJGgzFTo2CNny6YaUKTg,21618
|
|
4
|
+
agent0_sdk/core/endpoint_crawler.py,sha256=QBkFc3tBSQqHj6PtSTZ5D3_HVB00KJZJdxE3uYpI9po,13611
|
|
5
|
+
agent0_sdk/core/feedback_manager.py,sha256=ndGROPISrQvfKjwrWiB_xJ9QIh0cLs4oHh666Ru-A_8,37650
|
|
6
|
+
agent0_sdk/core/indexer.py,sha256=BUL4QbbL9sN8eZ1osUdIn_Kgj-MF0SET8RtjbBERQm0,70326
|
|
7
|
+
agent0_sdk/core/ipfs_client.py,sha256=fml1ai1BdBkgb95xAkf-ft8QsahV1HL30hBYRz7rQwI,13929
|
|
8
|
+
agent0_sdk/core/models.py,sha256=1BSAX2LVbw0kL_qHK7DxBrIFx8PF3wQvzkzblcQTMUg,12042
|
|
9
|
+
agent0_sdk/core/oasf_validator.py,sha256=ZOtYYzQd7cJj3eJegi7Ch5ydoapJEjaJSxMvwzKSp5o,2980
|
|
10
|
+
agent0_sdk/core/sdk.py,sha256=c9vSKTer50aJr8VcJ6huj6N1pTRwCUtbCxl5G_5oXhk,40955
|
|
11
|
+
agent0_sdk/core/subgraph_client.py,sha256=VSK9gCB5uYc2OqAVQ659IgeF0N-tXwxPUbv7NK8Kz0U,29575
|
|
12
|
+
agent0_sdk/core/web3_client.py,sha256=h7s-Al3E1xfbb3QNcPvmQBotKJRg23Jm9xot4emr-hU,12283
|
|
13
|
+
agent0_sdk/taxonomies/all_domains.json,sha256=buM8_6mpY8_AMbBIPzM-gtu14Tl30QDmhuQxxrlJU4c,74625
|
|
14
|
+
agent0_sdk/taxonomies/all_skills.json,sha256=WVsutw3fqoj1jfDPru3CyWTr1kc1a5-EhBOWPfXnEi8,47483
|
|
15
|
+
agent0_sdk-1.0.1.dist-info/licenses/LICENSE,sha256=rhZZbZm_Ovz4Oa9LNQ-ms8a1tA36wWh90ZkC0OR7WMw,1072
|
|
16
|
+
agent0_sdk-1.0.1.dist-info/METADATA,sha256=WkgYGLtZzuaE57yJqmVS0pyNPcb0ByNfaWNBHfR0qI0,14268
|
|
17
|
+
agent0_sdk-1.0.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
18
|
+
agent0_sdk-1.0.1.dist-info/top_level.txt,sha256=p4520WUKNfhU0lVWJgkrB_jdeUfvHSY3K18k4oYLNfI,11
|
|
19
|
+
agent0_sdk-1.0.1.dist-info/RECORD,,
|
agent0_sdk-0.31.dist-info/RECORD
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
agent0_sdk/__init__.py,sha256=b1gWy_m93dJucBqmwz9zx2M3zs8JTO-S0CN1B0xBHcQ,918
|
|
2
|
-
agent0_sdk/core/agent.py,sha256=vqgQRQVrCBV0zAGkZeYHeYvWlRAG9-m0Wy61DxvEiD4,38001
|
|
3
|
-
agent0_sdk/core/contracts.py,sha256=LJY07PIyAb3ZSH_m2xD4OdXawsXKxaTUGhYHwGWUpmI,20435
|
|
4
|
-
agent0_sdk/core/endpoint_crawler.py,sha256=QBkFc3tBSQqHj6PtSTZ5D3_HVB00KJZJdxE3uYpI9po,13611
|
|
5
|
-
agent0_sdk/core/feedback_manager.py,sha256=MV3srP-rgFaDXXELeLTJBj2aDUPdENy0tzWUsJUqrL4,39860
|
|
6
|
-
agent0_sdk/core/indexer.py,sha256=erkDKrtyvAtfrblz0YHekPB2Hpw7tJm-1KJjafTMaHc,68470
|
|
7
|
-
agent0_sdk/core/ipfs_client.py,sha256=fml1ai1BdBkgb95xAkf-ft8QsahV1HL30hBYRz7rQwI,13929
|
|
8
|
-
agent0_sdk/core/models.py,sha256=SnHmy9nmKHnD5hMIv-r8Ztj1zfXF_1qxHwfnBFAjkAA,12505
|
|
9
|
-
agent0_sdk/core/oasf_validator.py,sha256=X_V6mZsrp9z2So2fAJ66fm5EypiVEl4Fxtg10Ot3cgQ,2952
|
|
10
|
-
agent0_sdk/core/sdk.py,sha256=CQUpKaP7g1Lu7aokRYs-QChv78XIdkfz6Yax6nhaWqQ,40166
|
|
11
|
-
agent0_sdk/core/subgraph_client.py,sha256=Iw-YEtT1-Rm9f-kY4EXZlCuwEkJQAq5-bHDpxrMVyGg,28419
|
|
12
|
-
agent0_sdk/core/web3_client.py,sha256=859ntu5dAmNlcJ3YM1w_VV2gI3mpCC9QEr-GN1236zU,6850
|
|
13
|
-
agent0_sdk/taxonomies/all_domains.json,sha256=buM8_6mpY8_AMbBIPzM-gtu14Tl30QDmhuQxxrlJU4c,74625
|
|
14
|
-
agent0_sdk/taxonomies/all_skills.json,sha256=WVsutw3fqoj1jfDPru3CyWTr1kc1a5-EhBOWPfXnEi8,47483
|
|
15
|
-
agent0_sdk-0.31.dist-info/licenses/LICENSE,sha256=rhZZbZm_Ovz4Oa9LNQ-ms8a1tA36wWh90ZkC0OR7WMw,1072
|
|
16
|
-
tests/__init__.py,sha256=60ffheccPhuMCtwiiKP1X-CJJXKpxJ_Ywa0aXGHR9bY,23
|
|
17
|
-
tests/config.py,sha256=1uePvkLBNubOQsvYkQSno0m007PMD1VACgm33fCYY6s,1429
|
|
18
|
-
tests/conftest.py,sha256=P-HCtVVYwSvscuaJqhrgZcv39XXNnr932ekEamzIqis,589
|
|
19
|
-
tests/discover_test_data.py,sha256=Fu0uQKnFk8m7qEqEp293BWbo_mT1CK5szVZcdcdlJQw,17678
|
|
20
|
-
tests/test_feedback.py,sha256=7lszWYSmseJE0I4BhKzZdBiIzf2bgpPqZTZvhRrCTjY,14638
|
|
21
|
-
tests/test_models.py,sha256=kCZdoPasIIcOjEw7ToPldqARdbGVK8v8byOhFwVo7OI,7115
|
|
22
|
-
tests/test_multi_chain.py,sha256=6C-HD037Lsjd7_H0pwp7NDU04jLTjhardT1LHBjahFM,26711
|
|
23
|
-
tests/test_oasf_management.py,sha256=SIC_YgnuapNXAstjm9y8gky3cLG1FCcujZ-y5dlHxYo,17738
|
|
24
|
-
tests/test_real_public_servers.py,sha256=pCo4aLSCG9qv4D6T7jbyVmP1gt3r1jWxdes6z5XSNhU,3433
|
|
25
|
-
tests/test_registration.py,sha256=pYanDPLAFETIfabBUvO34ZDmyD0Rbcv8vecSfgSrQ70,9542
|
|
26
|
-
tests/test_registrationIpfs.py,sha256=9O3IBiN2CVMKzB19bqb-jN-nhqsN22kQINMpe9THqiI,8400
|
|
27
|
-
tests/test_sdk.py,sha256=dALLFm_A6aXx0ec-CNOLGQoadaSPZ08EEeCS6Tgnm0M,9362
|
|
28
|
-
tests/test_search.py,sha256=SiUio8H-M6Za8OXD_h9tUZdln0ayzkPJ3doTrkHv-Fs,18382
|
|
29
|
-
tests/test_transfer.py,sha256=zRBllpoMs6NhagAmaZWmD4ckbYjSvsSUerBK4oS-HlA,9258
|
|
30
|
-
agent0_sdk-0.31.dist-info/METADATA,sha256=xsGC93w2qox04MG419dufzAAQO-l5W9TpNXMHomzbwA,13623
|
|
31
|
-
agent0_sdk-0.31.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
32
|
-
agent0_sdk-0.31.dist-info/top_level.txt,sha256=rgGBfOJlLi1zInQ85jBL2MpDu_ZJNbPjIGz-3Vn5rZs,17
|
|
33
|
-
agent0_sdk-0.31.dist-info/RECORD,,
|
tests/__init__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
# Tests for Agent0 SDK
|
tests/config.py
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Shared configuration loader for test examples.
|
|
3
|
-
Loads configuration from environment variables (.env file).
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import os
|
|
7
|
-
from pathlib import Path
|
|
8
|
-
from dotenv import load_dotenv
|
|
9
|
-
|
|
10
|
-
# Load environment variables from .env file
|
|
11
|
-
# Look for .env in parent directory (project root)
|
|
12
|
-
env_path = Path(__file__).parent.parent.parent / ".env"
|
|
13
|
-
load_dotenv(dotenv_path=env_path)
|
|
14
|
-
|
|
15
|
-
# Chain Configuration
|
|
16
|
-
CHAIN_ID = int(os.getenv("CHAIN_ID", "11155111"))
|
|
17
|
-
RPC_URL = os.getenv(
|
|
18
|
-
"RPC_URL",
|
|
19
|
-
"https://eth-sepolia.g.alchemy.com/v2/7nkA4bJ0tKWcl2-5Wn15c5eRdpGZ8DDr"
|
|
20
|
-
)
|
|
21
|
-
AGENT_PRIVATE_KEY = os.getenv("AGENT_PRIVATE_KEY", "")
|
|
22
|
-
|
|
23
|
-
# IPFS Configuration (Pinata)
|
|
24
|
-
PINATA_JWT = os.getenv("PINATA_JWT", "")
|
|
25
|
-
|
|
26
|
-
# Subgraph Configuration
|
|
27
|
-
SUBGRAPH_URL = os.getenv(
|
|
28
|
-
"SUBGRAPH_URL",
|
|
29
|
-
"https://gateway.thegraph.com/api/00a452ad3cd1900273ea62c1bf283f93/subgraphs/id/6wQRC7geo9XYAhckfmfo8kbMRLeWU8KQd3XsJqFKmZLT"
|
|
30
|
-
)
|
|
31
|
-
|
|
32
|
-
# Agent ID for testing (can be overridden via env)
|
|
33
|
-
AGENT_ID = os.getenv("AGENT_ID", "11155111:374")
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def print_config():
|
|
37
|
-
"""Print current configuration (hiding sensitive values)."""
|
|
38
|
-
print("Configuration:")
|
|
39
|
-
print(f" CHAIN_ID: {CHAIN_ID}")
|
|
40
|
-
print(f" RPC_URL: {RPC_URL[:50]}...")
|
|
41
|
-
print(f" AGENT_PRIVATE_KEY: {'***' if AGENT_PRIVATE_KEY else 'NOT SET'}")
|
|
42
|
-
print(f" PINATA_JWT: {'***' if PINATA_JWT else 'NOT SET'}")
|
|
43
|
-
print(f" SUBGRAPH_URL: {SUBGRAPH_URL[:50]}...")
|
|
44
|
-
print(f" AGENT_ID: {AGENT_ID}")
|
|
45
|
-
print()
|
|
46
|
-
|
tests/conftest.py
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Pytest configuration file for Agent0 SDK tests.
|
|
3
|
-
Sets up logging to debug level for agent0_sdk only.
|
|
4
|
-
"""
|
|
5
|
-
|
|
6
|
-
import logging
|
|
7
|
-
import sys
|
|
8
|
-
|
|
9
|
-
# Configure logging: root logger at WARNING to suppress noisy dependencies
|
|
10
|
-
logging.basicConfig(
|
|
11
|
-
level=logging.WARNING,
|
|
12
|
-
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
13
|
-
datefmt='%Y-%m-%d %H:%M:%S',
|
|
14
|
-
handlers=[
|
|
15
|
-
logging.StreamHandler(sys.stdout)
|
|
16
|
-
]
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
# Set debug level ONLY for agent0_sdk loggers
|
|
20
|
-
logging.getLogger('agent0_sdk').setLevel(logging.DEBUG)
|
|
21
|
-
logging.getLogger('agent0_sdk.core').setLevel(logging.DEBUG)
|
|
22
|
-
|