agent0-sdk 0.2.2__py3-none-any.whl → 0.5__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.
@@ -30,25 +30,38 @@ class SubgraphClient:
30
30
  Returns:
31
31
  JSON response from the subgraph
32
32
  """
33
- try:
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=30
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
- feedbackUri
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
- responseUri
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
- feedbackUri
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
- responseUri
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
- feedbackUri
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
- responseUri
599
+ responseURI
581
600
  responseHash
582
601
  createdAt
583
602
  }}
@@ -716,12 +735,14 @@ class SubgraphClient:
716
735
  return []
717
736
  else:
718
737
  # No feedback filters - query agents directly
719
- agent_filters = []
738
+ # For reputation search, we want agents that have feedback
739
+ # Filter by totalFeedback > 0 to only get agents with feedback
740
+ agent_filters = ['totalFeedback_gt: 0'] # Only agents with feedback (BigInt comparison)
720
741
  if agents is not None and len(agents) > 0:
721
742
  agent_ids = [f'"{aid}"' for aid in agents]
722
743
  agent_filters.append(f'id_in: [{", ".join(agent_ids)}]')
723
744
 
724
- agent_where = f"where: {{ {', '.join(agent_filters)} }}" if agent_filters else ""
745
+ agent_where = f"where: {{ {', '.join(agent_filters)} }}"
725
746
 
726
747
  # Build feedback where for agent query (to calculate scores)
727
748
  feedback_where_for_agents = f"{{ {', '.join(feedback_filters)} }}" if feedback_filters else "{}"
@@ -784,6 +805,12 @@ class SubgraphClient:
784
805
 
785
806
  try:
786
807
  result = self.query(query)
808
+
809
+ # Check for GraphQL errors
810
+ if 'errors' in result:
811
+ logger.error(f"GraphQL errors in search_agents_by_reputation: {result['errors']}")
812
+ return []
813
+
787
814
  agents_result = result.get('agents', [])
788
815
 
789
816
  # Calculate average scores
@@ -806,6 +833,18 @@ class SubgraphClient:
806
833
  if agent.get('averageScore') is not None and agent['averageScore'] >= minAverageScore
807
834
  ]
808
835
 
836
+ # For reputation search, filter logic:
837
+ # - If specific agents were requested, return them even if averageScore is None
838
+ # (the user explicitly asked for these agents, so return them)
839
+ # - If general search (no specific agents), only return agents with reputation data
840
+ if agents is None or len(agents) == 0:
841
+ # General search - only return agents with reputation
842
+ agents_result = [
843
+ agent for agent in agents_result
844
+ if agent.get('averageScore') is not None
845
+ ]
846
+ # else: specific agents requested - return all requested agents (even if averageScore is None)
847
+
809
848
  return agents_result
810
849
 
811
850
  except Exception as e:
@@ -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)