xproof 0.2.2__tar.gz → 0.2.4__tar.gz

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.
Files changed (26) hide show
  1. {xproof-0.2.2 → xproof-0.2.4}/PKG-INFO +3 -1
  2. {xproof-0.2.2 → xproof-0.2.4}/pyproject.toml +4 -1
  3. {xproof-0.2.2 → xproof-0.2.4}/xproof/__init__.py +1 -1
  4. {xproof-0.2.2 → xproof-0.2.4}/xproof/client.py +30 -1
  5. {xproof-0.2.2 → xproof-0.2.4}/xproof/integrations/__init__.py +5 -0
  6. xproof-0.2.4/xproof/integrations/fetchai.py +398 -0
  7. {xproof-0.2.2 → xproof-0.2.4}/xproof.egg-info/PKG-INFO +3 -1
  8. {xproof-0.2.2 → xproof-0.2.4}/xproof.egg-info/SOURCES.txt +1 -0
  9. {xproof-0.2.2 → xproof-0.2.4}/xproof.egg-info/requires.txt +3 -0
  10. {xproof-0.2.2 → xproof-0.2.4}/LICENSE +0 -0
  11. {xproof-0.2.2 → xproof-0.2.4}/README.md +0 -0
  12. {xproof-0.2.2 → xproof-0.2.4}/setup.cfg +0 -0
  13. {xproof-0.2.2 → xproof-0.2.4}/tests/test_client.py +0 -0
  14. {xproof-0.2.2 → xproof-0.2.4}/tests/test_integration.py +0 -0
  15. {xproof-0.2.2 → xproof-0.2.4}/xproof/exceptions.py +0 -0
  16. {xproof-0.2.2 → xproof-0.2.4}/xproof/integrations/autogen.py +0 -0
  17. {xproof-0.2.2 → xproof-0.2.4}/xproof/integrations/crewai.py +0 -0
  18. {xproof-0.2.2 → xproof-0.2.4}/xproof/integrations/deerflow.py +0 -0
  19. {xproof-0.2.2 → xproof-0.2.4}/xproof/integrations/langchain.py +0 -0
  20. {xproof-0.2.2 → xproof-0.2.4}/xproof/integrations/llamaindex.py +0 -0
  21. {xproof-0.2.2 → xproof-0.2.4}/xproof/integrations/openai_agents.py +0 -0
  22. {xproof-0.2.2 → xproof-0.2.4}/xproof/models.py +0 -0
  23. {xproof-0.2.2 → xproof-0.2.4}/xproof/py.typed +0 -0
  24. {xproof-0.2.2 → xproof-0.2.4}/xproof/utils.py +0 -0
  25. {xproof-0.2.2 → xproof-0.2.4}/xproof.egg-info/dependency_links.txt +0 -0
  26. {xproof-0.2.2 → xproof-0.2.4}/xproof.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xproof
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Python SDK for xProof — blockchain-anchored proof-of-existence for AI agents on MultiversX
5
5
  Author-email: xProof <contact@xproof.app>
6
6
  License-Expression: MIT
@@ -37,6 +37,8 @@ Requires-Dist: pyautogen>=0.2.0; extra == "autogen"
37
37
  Provides-Extra: openai-agents
38
38
  Requires-Dist: openai-agents>=0.0.3; extra == "openai-agents"
39
39
  Provides-Extra: deerflow
40
+ Provides-Extra: fetchai
41
+ Requires-Dist: uagents>=0.9.0; extra == "fetchai"
40
42
  Dynamic: license-file
41
43
 
42
44
  # xproof
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "xproof"
7
- version = "0.2.2"
7
+ version = "0.2.4"
8
8
  description = "Python SDK for xProof — blockchain-anchored proof-of-existence for AI agents on MultiversX"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -50,6 +50,9 @@ openai-agents = [
50
50
  "openai-agents>=0.0.3",
51
51
  ]
52
52
  deerflow = []
53
+ fetchai = [
54
+ "uagents>=0.9.0",
55
+ ]
53
56
 
54
57
  [project.urls]
55
58
  Homepage = "https://xproof.app"
@@ -21,7 +21,7 @@ from .models import (
21
21
  )
22
22
  from .utils import hash_bytes, hash_file
23
23
 
24
- __version__ = "0.2.2"
24
+ __version__ = "0.2.3"
25
25
 
26
26
  __all__ = [
27
27
  "XProofClient",
@@ -17,7 +17,7 @@ from .exceptions import (
17
17
  from .models import BatchResult, Certification, PricingInfo, RegistrationResult
18
18
  from .utils import hash_file
19
19
 
20
- __version__ = "0.1.0"
20
+ __version__ = "0.2.4"
21
21
 
22
22
  DEFAULT_BASE_URL = "https://xproof.app"
23
23
  DEFAULT_TIMEOUT = 30
@@ -378,6 +378,35 @@ class XProofClient:
378
378
  )
379
379
  return data
380
380
 
381
+ def get_context_drift(self, decision_id: str) -> Dict[str, Any]:
382
+ """Detect execution context drift across a decision chain.
383
+
384
+ Compares ``model_hash``, ``tools_version``, ``strategy_snapshot``, and
385
+ ``operator_scope`` between consecutive proofs in the chain. Returns a
386
+ coherence score and per-stage breakdown of which fields changed.
387
+
388
+ Args:
389
+ decision_id: The shared identifier linking proofs in the chain.
390
+
391
+ Returns:
392
+ A dictionary with:
393
+
394
+ - ``context_coherent`` (bool): True if no drift was detected.
395
+ - ``drift_score`` (float): 0.0 = fully coherent, 1.0 = total drift.
396
+ - ``fields_drifted`` (list[str]): Fields that changed at least once.
397
+ - ``fields_stable`` (list[str]): Fields present in all stages and unchanged.
398
+ - ``fields_absent`` (list[str]): Fields never populated in any stage.
399
+ - ``stages`` (list[dict]): Per-stage context with ``context_break``
400
+ and ``drifted_fields`` flags.
401
+ """
402
+ from urllib.parse import quote
403
+ data = self._request(
404
+ "GET",
405
+ f"/api/context-drift/{quote(decision_id, safe='')}",
406
+ auth_required=False,
407
+ )
408
+ return data
409
+
381
410
  def batch_certify(
382
411
  self,
383
412
  files: List[Dict[str, Any]],
@@ -35,3 +35,8 @@ try:
35
35
  from .deerflow import XProofDeerFlowSkill
36
36
  except ImportError:
37
37
  pass
38
+
39
+ try:
40
+ from .fetchai import XProofuAgentMiddleware, xproof_handler, wrap_agent
41
+ except ImportError:
42
+ pass
@@ -0,0 +1,398 @@
1
+ """Fetch.ai uAgents integration for automatic xProof certification.
2
+
3
+ Wraps uAgent message handlers and interval tasks to anchor the 4W audit
4
+ trail (Who, What, When, Why) on MultiversX mainnet before and after every
5
+ agent action — without modifying application logic.
6
+
7
+ Quickstart::
8
+
9
+ from uagents import Agent, Context
10
+ from xproof.integrations.fetchai import xproof_handler, XProofuAgentMiddleware
11
+
12
+ agent = Agent(name="research-agent", seed="my-seed")
13
+ middleware = XProofuAgentMiddleware(api_key="pm_...", agent_name="research-agent")
14
+
15
+ @agent.on_message(model=QueryMessage)
16
+ @xproof_handler(middleware)
17
+ async def handle_query(ctx: Context, sender: str, msg: QueryMessage):
18
+ response = await do_research(msg.query)
19
+ await ctx.send(sender, ResponseMessage(result=response))
20
+
21
+ Compatible with uAgents >= 0.9.x.
22
+ """
23
+
24
+ import functools
25
+ import hashlib
26
+ import json
27
+ import uuid
28
+ from datetime import datetime, timezone
29
+ from typing import Any, Callable, Dict, List, Optional
30
+
31
+ from ..client import XProofClient
32
+
33
+
34
+ def _hash_data(data: Any) -> str:
35
+ """Stable SHA-256 of any JSON-serialisable value."""
36
+ serialized = json.dumps(data, sort_keys=True, default=str)
37
+ return hashlib.sha256(serialized.encode()).hexdigest()
38
+
39
+
40
+ def _now_iso() -> str:
41
+ return datetime.now(timezone.utc).isoformat()
42
+
43
+
44
+ class XProofuAgentMiddleware:
45
+ """Central xProof certification middleware for a uAgent.
46
+
47
+ Instantiate once per agent, then pass it to :func:`xproof_handler`
48
+ or call :meth:`certify_incoming` / :meth:`certify_outgoing` directly.
49
+
50
+ Args:
51
+ api_key: xProof API key (``pm_...``). Ignored when *client* is given.
52
+ client: Pre-configured :class:`~xproof.client.XProofClient`.
53
+ agent_name: Used as WHO in the 4W metadata. Defaults to ``"uagent"``.
54
+ certify_incoming: Certify the incoming message (the WHY). Default ``True``.
55
+ certify_outgoing: Certify the outgoing response (the WHAT). Default ``True``.
56
+ batch_mode: Buffer certifications and flush with :meth:`flush`. Default ``False``.
57
+
58
+ Example::
59
+
60
+ from xproof.integrations.fetchai import XProofuAgentMiddleware
61
+
62
+ middleware = XProofuAgentMiddleware(
63
+ api_key="pm_...",
64
+ agent_name="trading-agent",
65
+ )
66
+ proof = middleware.certify_incoming(
67
+ message={"query": "What is the BTC price?"},
68
+ sender="agent1abcd",
69
+ context="Market data query",
70
+ )
71
+ print(proof["proof_id"])
72
+ """
73
+
74
+ def __init__(
75
+ self,
76
+ api_key: str = "",
77
+ client: Optional[XProofClient] = None,
78
+ agent_name: str = "uagent",
79
+ certify_incoming: bool = True,
80
+ certify_outgoing: bool = True,
81
+ batch_mode: bool = False,
82
+ ) -> None:
83
+ self.client = client or XProofClient(api_key=api_key)
84
+ self.agent_name = agent_name
85
+ self.certify_incoming = certify_incoming
86
+ self.certify_outgoing = certify_outgoing
87
+ self.batch_mode = batch_mode
88
+ self._pending: List[Dict[str, Any]] = []
89
+
90
+ def _certify(
91
+ self,
92
+ file_hash: str,
93
+ file_name: str,
94
+ action_type: str,
95
+ context: str,
96
+ extra_metadata: Optional[Dict[str, Any]] = None,
97
+ decision_id: Optional[str] = None,
98
+ ) -> Optional[Dict[str, Any]]:
99
+ metadata: Dict[str, Any] = {
100
+ "who": self.agent_name,
101
+ "what": file_hash,
102
+ "when": _now_iso(),
103
+ "why": context,
104
+ "action_type": action_type,
105
+ "framework": "fetchai-uagents",
106
+ }
107
+ if decision_id:
108
+ metadata["decision_id"] = decision_id
109
+ if extra_metadata:
110
+ metadata.update(extra_metadata)
111
+
112
+ entry = {
113
+ "file_hash": file_hash,
114
+ "file_name": file_name,
115
+ "author": self.agent_name,
116
+ "metadata": metadata,
117
+ }
118
+
119
+ if self.batch_mode:
120
+ self._pending.append(entry)
121
+ return {"queued": True, "file_hash": file_hash}
122
+
123
+ cert = self.client.certify_hash(
124
+ file_hash=file_hash,
125
+ file_name=file_name,
126
+ author=self.agent_name,
127
+ metadata=metadata,
128
+ )
129
+ return {
130
+ "proof_id": cert.id,
131
+ "file_hash": cert.file_hash,
132
+ "transaction_hash": cert.transaction_hash,
133
+ "verify_url": f"https://xproof.app/proof/{cert.id}",
134
+ }
135
+
136
+ def certify_incoming(
137
+ self,
138
+ message: Any,
139
+ sender: str = "unknown",
140
+ context: str = "Incoming uAgent message",
141
+ decision_id: Optional[str] = None,
142
+ ) -> Optional[Dict[str, Any]]:
143
+ """Certify an incoming message as WHY (trigger / justification).
144
+
145
+ Args:
146
+ message: The incoming message object or dict.
147
+ sender: Sender address for traceability metadata.
148
+ context: Human-readable description of the trigger.
149
+ decision_id: Shared ID linking this WHY to its WHAT pair.
150
+
151
+ Returns:
152
+ Dict with ``proof_id``, ``file_hash``, ``transaction_hash``,
153
+ ``verify_url``; or ``None`` if *certify_incoming* is disabled.
154
+ """
155
+ if not self.certify_incoming:
156
+ return None
157
+
158
+ msg_dict = (
159
+ message.__dict__ if hasattr(message, "__dict__") else
160
+ {"content": str(message)}
161
+ )
162
+ msg_hash = _hash_data({"sender": sender, "message": msg_dict})
163
+
164
+ return self._certify(
165
+ file_hash=msg_hash,
166
+ file_name=f"incoming-{self.agent_name}-{msg_hash[:8]}.json",
167
+ action_type="message_received",
168
+ context=context,
169
+ extra_metadata={"sender": sender},
170
+ decision_id=decision_id,
171
+ )
172
+
173
+ def certify_outgoing(
174
+ self,
175
+ response: Any,
176
+ recipient: str = "unknown",
177
+ context: str = "uAgent response",
178
+ decision_id: Optional[str] = None,
179
+ confidence_level: Optional[float] = None,
180
+ ) -> Optional[Dict[str, Any]]:
181
+ """Certify an outgoing response as WHAT (the output to prove).
182
+
183
+ Args:
184
+ response: The response object or dict to hash and certify.
185
+ recipient: Destination address for traceability metadata.
186
+ context: Human-readable description of the output.
187
+ decision_id: Shared ID linking this WHAT to its WHY pair.
188
+ confidence_level: Optional float 0.0-1.0 (agent self-reported confidence).
189
+
190
+ Returns:
191
+ Dict with ``proof_id``, ``file_hash``, ``transaction_hash``,
192
+ ``verify_url``; or ``None`` if *certify_outgoing* is disabled.
193
+ """
194
+ if not self.certify_outgoing:
195
+ return None
196
+
197
+ resp_dict = (
198
+ response.__dict__ if hasattr(response, "__dict__") else
199
+ {"content": str(response)}
200
+ )
201
+ resp_hash = _hash_data({"recipient": recipient, "response": resp_dict})
202
+
203
+ extra: Dict[str, Any] = {"recipient": recipient}
204
+ if confidence_level is not None:
205
+ extra["confidence_level"] = float(confidence_level)
206
+
207
+ return self._certify(
208
+ file_hash=resp_hash,
209
+ file_name=f"outgoing-{self.agent_name}-{resp_hash[:8]}.json",
210
+ action_type="message_sent",
211
+ context=context,
212
+ extra_metadata=extra,
213
+ decision_id=decision_id,
214
+ )
215
+
216
+ def certify_action(
217
+ self,
218
+ action_name: str,
219
+ inputs: Any,
220
+ outputs: Any,
221
+ why: str = "",
222
+ confidence_level: Optional[float] = None,
223
+ ) -> Dict[str, Any]:
224
+ """Certify a complete agent action as a WHY+WHAT pair.
225
+
226
+ Creates two linked proofs sharing a ``decision_id`` — the canonical
227
+ xProof dual-certification pattern for verifiable agent accountability.
228
+
229
+ Args:
230
+ action_name: Descriptive name of the action (used in file names).
231
+ inputs: Input data / trigger (hashed as WHY).
232
+ outputs: Output data / result (hashed as WHAT).
233
+ why: Human-readable mandate or justification.
234
+ confidence_level: Optional float 0.0-1.0.
235
+
236
+ Returns:
237
+ Dict with ``decision_id``, ``why_proof`` and ``what_proof`` dicts.
238
+
239
+ Example::
240
+
241
+ result = middleware.certify_action(
242
+ action_name="price-lookup",
243
+ inputs={"query": "BTC/USDT"},
244
+ outputs={"price": 67800.0},
245
+ why="Market price requested by trading strategy",
246
+ confidence_level=0.95,
247
+ )
248
+ print(result["why_proof"]["proof_id"])
249
+ print(result["what_proof"]["proof_id"])
250
+ """
251
+ decision_id = str(uuid.uuid4())
252
+ inputs_hash = _hash_data(inputs)
253
+ outputs_hash = _hash_data(outputs)
254
+
255
+ why_extra: Dict[str, Any] = {"action_name": action_name}
256
+ if confidence_level is not None:
257
+ why_extra["confidence_level"] = float(confidence_level)
258
+
259
+ why_proof = self._certify(
260
+ file_hash=inputs_hash,
261
+ file_name=f"why-{action_name}-{inputs_hash[:8]}.json",
262
+ action_type="decision",
263
+ context=why or f"Reasoning before {action_name}",
264
+ extra_metadata=why_extra,
265
+ decision_id=decision_id,
266
+ )
267
+
268
+ what_proof = self._certify(
269
+ file_hash=outputs_hash,
270
+ file_name=f"what-{action_name}-{outputs_hash[:8]}.json",
271
+ action_type="output",
272
+ context=f"Output of {action_name}",
273
+ extra_metadata={"action_name": action_name},
274
+ decision_id=decision_id,
275
+ )
276
+
277
+ return {
278
+ "decision_id": decision_id,
279
+ "why_proof": why_proof,
280
+ "what_proof": what_proof,
281
+ }
282
+
283
+ def flush(self) -> Optional[Dict[str, Any]]:
284
+ """Send all pending certifications in a single batch.
285
+
286
+ Only relevant when *batch_mode* is ``True``.
287
+
288
+ Returns:
289
+ The batch API response dict, or ``None`` if the queue was empty.
290
+ """
291
+ if not self._pending:
292
+ return None
293
+ result = self.client.batch_certify(self._pending)
294
+ self._pending.clear()
295
+ return result
296
+
297
+
298
+ def xproof_handler(
299
+ middleware: XProofuAgentMiddleware,
300
+ incoming_context: str = "Incoming uAgent message",
301
+ outgoing_context: str = "uAgent response",
302
+ ) -> Callable:
303
+ """Decorator that wraps a uAgent ``on_message`` handler with xProof certification.
304
+
305
+ Certifies the incoming message (WHY) before the handler runs, then
306
+ certifies the response (WHAT) if the handler returns a value.
307
+
308
+ Both proofs share a ``decision_id`` so the full reasoning chain is
309
+ verifiable on-chain.
310
+
311
+ Args:
312
+ middleware: A configured :class:`XProofuAgentMiddleware` instance.
313
+ incoming_context: Description attached to the WHY proof.
314
+ outgoing_context: Description attached to the WHAT proof.
315
+
316
+ Example::
317
+
318
+ from uagents import Agent, Context
319
+ from xproof.integrations.fetchai import XProofuAgentMiddleware, xproof_handler
320
+
321
+ agent = Agent(name="research-agent", seed="test-seed")
322
+ xp = XProofuAgentMiddleware(api_key="pm_...", agent_name="research-agent")
323
+
324
+ @agent.on_message(model=Query)
325
+ @xproof_handler(xp, incoming_context="Research query received")
326
+ async def handle_query(ctx: Context, sender: str, msg: Query):
327
+ result = await run_research(msg.topic)
328
+ return result # returned value is certified as WHAT
329
+ """
330
+ def decorator(fn: Callable) -> Callable:
331
+ @functools.wraps(fn)
332
+ async def wrapper(ctx: Any, sender: str, msg: Any) -> Any:
333
+ decision_id = str(uuid.uuid4())
334
+
335
+ middleware.certify_incoming(
336
+ message=msg,
337
+ sender=sender,
338
+ context=incoming_context,
339
+ decision_id=decision_id,
340
+ )
341
+
342
+ result = await fn(ctx, sender, msg)
343
+
344
+ if result is not None:
345
+ middleware.certify_outgoing(
346
+ response=result,
347
+ recipient=sender,
348
+ context=outgoing_context,
349
+ decision_id=decision_id,
350
+ )
351
+
352
+ return result
353
+ return wrapper
354
+ return decorator
355
+
356
+
357
+ def wrap_agent(
358
+ agent: Any,
359
+ api_key: str = "",
360
+ client: Optional[XProofClient] = None,
361
+ agent_name: Optional[str] = None,
362
+ certify_incoming: bool = True,
363
+ certify_outgoing: bool = True,
364
+ batch_mode: bool = False,
365
+ ) -> "XProofuAgentMiddleware":
366
+ """Convenience function — creates middleware bound to a uAgent instance.
367
+
368
+ Reads ``agent.name`` automatically so you don't have to specify it.
369
+
370
+ Args:
371
+ agent: A uAgents ``Agent`` instance.
372
+ api_key: xProof API key. Ignored when *client* is provided.
373
+ client: Pre-configured :class:`~xproof.client.XProofClient`.
374
+ agent_name: Override the name used in 4W metadata.
375
+ certify_incoming: Certify incoming messages. Default ``True``.
376
+ certify_outgoing: Certify outgoing responses. Default ``True``.
377
+ batch_mode: Buffer certifications for batch sending. Default ``False``.
378
+
379
+ Returns:
380
+ A ready-to-use :class:`XProofuAgentMiddleware` instance.
381
+
382
+ Example::
383
+
384
+ from uagents import Agent
385
+ from xproof.integrations.fetchai import wrap_agent
386
+
387
+ agent = Agent(name="price-oracle", seed="secret")
388
+ xp = wrap_agent(agent, api_key="pm_...")
389
+ """
390
+ name = agent_name or getattr(agent, "name", "uagent")
391
+ return XProofuAgentMiddleware(
392
+ api_key=api_key,
393
+ client=client,
394
+ agent_name=name,
395
+ certify_incoming=certify_incoming,
396
+ certify_outgoing=certify_outgoing,
397
+ batch_mode=batch_mode,
398
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: xproof
3
- Version: 0.2.2
3
+ Version: 0.2.4
4
4
  Summary: Python SDK for xProof — blockchain-anchored proof-of-existence for AI agents on MultiversX
5
5
  Author-email: xProof <contact@xproof.app>
6
6
  License-Expression: MIT
@@ -37,6 +37,8 @@ Requires-Dist: pyautogen>=0.2.0; extra == "autogen"
37
37
  Provides-Extra: openai-agents
38
38
  Requires-Dist: openai-agents>=0.0.3; extra == "openai-agents"
39
39
  Provides-Extra: deerflow
40
+ Provides-Extra: fetchai
41
+ Requires-Dist: uagents>=0.9.0; extra == "fetchai"
40
42
  Dynamic: license-file
41
43
 
42
44
  # xproof
@@ -18,6 +18,7 @@ xproof/integrations/__init__.py
18
18
  xproof/integrations/autogen.py
19
19
  xproof/integrations/crewai.py
20
20
  xproof/integrations/deerflow.py
21
+ xproof/integrations/fetchai.py
21
22
  xproof/integrations/langchain.py
22
23
  xproof/integrations/llamaindex.py
23
24
  xproof/integrations/openai_agents.py
@@ -9,6 +9,9 @@ pyautogen>=0.2.0
9
9
  pytest>=7.0
10
10
  responses>=0.20.0
11
11
 
12
+ [fetchai]
13
+ uagents>=0.9.0
14
+
12
15
  [llamaindex]
13
16
  llama-index-core>=0.13.0
14
17
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes