aotrust-protocol 2.2.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.
- agent_notary/__init__.py +24 -0
- agent_notary/client.py +128 -0
- agent_notary/exceptions.py +25 -0
- agent_notary/models.py +148 -0
- aotrust_protocol-2.2.0.dist-info/METADATA +116 -0
- aotrust_protocol-2.2.0.dist-info/RECORD +9 -0
- aotrust_protocol-2.2.0.dist-info/WHEEL +5 -0
- aotrust_protocol-2.2.0.dist-info/licenses/LICENSE +21 -0
- aotrust_protocol-2.2.0.dist-info/top_level.txt +1 -0
agent_notary/__init__.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""A&O Trust Layer — Agent Notary SDK (asyncio-native)."""
|
|
2
|
+
|
|
3
|
+
from .client import NotaryClient
|
|
4
|
+
from .models import NotarizeRequest, NotarizeResult, PdrResult, StatusResult
|
|
5
|
+
from .exceptions import (
|
|
6
|
+
NotaryError,
|
|
7
|
+
NotaryAuthError,
|
|
8
|
+
NotaryNotFoundError,
|
|
9
|
+
NotaryPaymentError,
|
|
10
|
+
NotaryServerError,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"NotaryClient",
|
|
15
|
+
"NotarizeRequest",
|
|
16
|
+
"NotarizeResult",
|
|
17
|
+
"PdrResult",
|
|
18
|
+
"StatusResult",
|
|
19
|
+
"NotaryError",
|
|
20
|
+
"NotaryAuthError",
|
|
21
|
+
"NotaryNotFoundError",
|
|
22
|
+
"NotaryPaymentError",
|
|
23
|
+
"NotaryServerError",
|
|
24
|
+
]
|
agent_notary/client.py
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Asyncio-native NotaryClient for A&O Trust Layer."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
import os
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
import aiohttp
|
|
10
|
+
|
|
11
|
+
from .models import NotarizeRequest, NotarizeResult, StatusResult, PdrResult
|
|
12
|
+
from .exceptions import (
|
|
13
|
+
NotaryError,
|
|
14
|
+
NotaryAuthError,
|
|
15
|
+
NotaryNotFoundError,
|
|
16
|
+
NotaryPaymentError,
|
|
17
|
+
NotaryServerError,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
DEFAULT_TIMEOUT = 30
|
|
21
|
+
DEFAULT_POLL_INTERVAL = 2.0
|
|
22
|
+
DEFAULT_POLL_TIMEOUT = 120.0
|
|
23
|
+
MAX_RETRIES = 3
|
|
24
|
+
RETRY_DELAYS = (1.0, 2.0, 4.0)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class NotaryClient:
|
|
28
|
+
"""Async client for the A&O Trust Layer Notary API."""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
api_key: Optional[str] = None,
|
|
33
|
+
base_url: Optional[str] = None,
|
|
34
|
+
notary_pubkey: Optional[str] = None,
|
|
35
|
+
):
|
|
36
|
+
self.api_key = api_key or os.environ.get("NOTARY_API_KEY", "")
|
|
37
|
+
self.base_url = (base_url or os.environ.get("NOTARY_API_URL", "https://api.aotrust.link/v1")).rstrip("/")
|
|
38
|
+
self.notary_pubkey = notary_pubkey or os.environ.get("NOTARY_PUBKEY", "9c7d64bbabe90ead8f18982497a47acbd95677cef0d9285caec95377e4ccf6b0")
|
|
39
|
+
|
|
40
|
+
def _headers(self) -> dict:
|
|
41
|
+
h = {"Content-Type": "application/json"}
|
|
42
|
+
if self.api_key:
|
|
43
|
+
h["X-API-Key"] = self.api_key
|
|
44
|
+
return h
|
|
45
|
+
|
|
46
|
+
async def _request(
|
|
47
|
+
self,
|
|
48
|
+
method: str,
|
|
49
|
+
path: str,
|
|
50
|
+
json_data: Optional[dict] = None,
|
|
51
|
+
timeout: float = DEFAULT_TIMEOUT,
|
|
52
|
+
) -> tuple[int, dict]:
|
|
53
|
+
url = f"{self.base_url}{path}"
|
|
54
|
+
last_exc = None
|
|
55
|
+
for attempt in range(MAX_RETRIES):
|
|
56
|
+
try:
|
|
57
|
+
async with aiohttp.ClientSession(timeout=aiohttp.ClientTimeout(total=timeout)) as session:
|
|
58
|
+
async with session.request(method, url, headers=self._headers(), json=json_data) as resp:
|
|
59
|
+
body = await resp.json(content_type=None) if resp.content_type and "json" in resp.content_type else {}
|
|
60
|
+
if resp.status in (502, 503) and attempt < MAX_RETRIES - 1:
|
|
61
|
+
await asyncio.sleep(RETRY_DELAYS[attempt])
|
|
62
|
+
continue
|
|
63
|
+
return resp.status, body
|
|
64
|
+
except aiohttp.ClientError as exc:
|
|
65
|
+
last_exc = exc
|
|
66
|
+
if attempt < MAX_RETRIES - 1:
|
|
67
|
+
await asyncio.sleep(RETRY_DELAYS[attempt])
|
|
68
|
+
raise NotaryServerError(f"Request failed after {MAX_RETRIES} retries: {last_exc}")
|
|
69
|
+
|
|
70
|
+
def _check_status(self, status: int, body: dict) -> None:
|
|
71
|
+
if status == 401:
|
|
72
|
+
raise NotaryAuthError(body.get("error", "Unauthorized"))
|
|
73
|
+
if status == 403:
|
|
74
|
+
detail = body.get("detail", body.get("error", "Forbidden"))
|
|
75
|
+
raise NotaryPaymentError(detail)
|
|
76
|
+
if status == 404:
|
|
77
|
+
raise NotaryNotFoundError(body.get("error", "Not found"))
|
|
78
|
+
if status >= 500:
|
|
79
|
+
raise NotaryServerError(body.get("error", f"Server error {status}"), status_code=status)
|
|
80
|
+
|
|
81
|
+
async def notarize(self, request: NotarizeRequest) -> NotarizeResult:
|
|
82
|
+
status, body = await self._request("POST", "/notarize", json_data=request.to_api_payload())
|
|
83
|
+
self._check_status(status, body)
|
|
84
|
+
if status not in (200, 202):
|
|
85
|
+
raise NotaryError(f"Unexpected status {status}: {body}")
|
|
86
|
+
return NotarizeResult(job_id=body.get("job_id", ""))
|
|
87
|
+
|
|
88
|
+
async def get_status(self, job_id: str) -> StatusResult:
|
|
89
|
+
status, body = await self._request("GET", f"/status/{job_id}")
|
|
90
|
+
self._check_status(status, body)
|
|
91
|
+
result = body.get("result", {})
|
|
92
|
+
return StatusResult(
|
|
93
|
+
status=body.get("status", "unknown"),
|
|
94
|
+
pdr_b64=result.get("pdr_b64"),
|
|
95
|
+
payment_hash=result.get("near_tx_hash") or result.get("payment_hash"),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
async def get_pdr(self, job_id: str) -> PdrResult:
|
|
99
|
+
status, body = await self._request("GET", f"/pdr/{job_id}")
|
|
100
|
+
self._check_status(status, body)
|
|
101
|
+
verify_url = f"{self.base_url}/verify/{job_id}" if "verify_url" not in body else body.get("verify_url")
|
|
102
|
+
return PdrResult(
|
|
103
|
+
pdr_b64=body.get("pdr", body.get("pdr_b64", "")),
|
|
104
|
+
payment_hash=body.get("near_tx_hash") or body.get("payment_hash"),
|
|
105
|
+
verify_url=verify_url,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
async def wait_for_pdr(
|
|
109
|
+
self,
|
|
110
|
+
job_id: str,
|
|
111
|
+
poll_interval: float = DEFAULT_POLL_INTERVAL,
|
|
112
|
+
timeout: float = DEFAULT_POLL_TIMEOUT,
|
|
113
|
+
) -> StatusResult:
|
|
114
|
+
elapsed = 0.0
|
|
115
|
+
while elapsed < timeout:
|
|
116
|
+
result = await self.get_status(job_id)
|
|
117
|
+
if result.is_anchored:
|
|
118
|
+
return result
|
|
119
|
+
if result.status.lower() in ("rejected", "error", "failed"):
|
|
120
|
+
raise NotaryPaymentError(f"Notarization {result.status}")
|
|
121
|
+
await asyncio.sleep(poll_interval)
|
|
122
|
+
elapsed += poll_interval
|
|
123
|
+
raise NotaryServerError(f"Timed out waiting for PDR ({timeout}s)")
|
|
124
|
+
|
|
125
|
+
async def quote(self) -> dict:
|
|
126
|
+
status, body = await self._request("POST", "/notarize/quote")
|
|
127
|
+
self._check_status(status, body)
|
|
128
|
+
return body
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"""Custom exceptions for NotaryClient."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class NotaryError(Exception):
|
|
5
|
+
"""Base exception for all NotaryClient errors."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class NotaryAuthError(NotaryError):
|
|
9
|
+
"""401 Unauthorized — invalid or missing API key."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class NotaryPaymentError(NotaryError):
|
|
13
|
+
"""403 Forbidden — payment verification or agent signature failed."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class NotaryNotFoundError(NotaryError):
|
|
17
|
+
"""404 Not Found — job_id or PDR not found."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class NotaryServerError(NotaryError):
|
|
21
|
+
"""5xx server error with retry context."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, message: str, status_code: int = 0):
|
|
24
|
+
super().__init__(message)
|
|
25
|
+
self.status_code = status_code
|
agent_notary/models.py
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""Pydantic models for NotaryClient request/response schemas."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import base64
|
|
6
|
+
import struct
|
|
7
|
+
from enum import IntEnum
|
|
8
|
+
from typing import Optional
|
|
9
|
+
from pydantic import BaseModel, Field, validator
|
|
10
|
+
|
|
11
|
+
NEP413_TAG = 2147484061
|
|
12
|
+
EXTERNAL_PDR_SIZE = 239
|
|
13
|
+
EXTERNAL_PAYLOAD_SIZE = 175
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class PaymentAnchorType(IntEnum):
|
|
17
|
+
UNPAID = 0x00
|
|
18
|
+
NEAR_DIRECT = 0x01
|
|
19
|
+
AGENT_MARKET_SUBMIT = 0x02
|
|
20
|
+
AGENT_MARKET_RELEASE= 0x03
|
|
21
|
+
NEAR_ESCROW_LOCK = 0x04
|
|
22
|
+
X402_BASE = 0x05
|
|
23
|
+
X402_POLYGON = 0x06
|
|
24
|
+
X402_NEAR = 0x07
|
|
25
|
+
CHAIN_SIG_PAYMENT = 0x08
|
|
26
|
+
NEAR_ESCROW_SETTLED = 0x09
|
|
27
|
+
UNKNOWN = 0xFF
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class NotarizeRequest(BaseModel):
|
|
31
|
+
tx_hash: str = Field(..., min_length=32, max_length=128, description="tx hash (NEAR Base58 or EVM hex)")
|
|
32
|
+
work_hash: Optional[str] = Field(None, description="SHA-256 hex digest (64 chars)")
|
|
33
|
+
agent_sig: Optional[str] = Field(None, description="Base64 Ed25519 signature (sig_A)")
|
|
34
|
+
agent_pubkey: Optional[str] = Field(None, description="Hex Ed25519 public key")
|
|
35
|
+
|
|
36
|
+
def to_api_payload(self) -> dict:
|
|
37
|
+
payload: dict = {"tx_hash": self.tx_hash}
|
|
38
|
+
if self.work_hash:
|
|
39
|
+
payload["payload"] = {"work_hash": self.work_hash}
|
|
40
|
+
if self.agent_sig:
|
|
41
|
+
payload["agent_sig"] = self.agent_sig
|
|
42
|
+
if self.agent_pubkey:
|
|
43
|
+
payload["agent_pubkey"] = self.agent_pubkey
|
|
44
|
+
return payload
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class NotarizeResult(BaseModel):
|
|
48
|
+
job_id: str
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class StatusResult(BaseModel):
|
|
52
|
+
status: str
|
|
53
|
+
pdr_b64: Optional[str] = None
|
|
54
|
+
payment_hash: Optional[str] = None
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def is_anchored(self) -> bool:
|
|
58
|
+
return self.status.lower() == "anchored"
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class PdrResult(BaseModel):
|
|
62
|
+
pdr_b64: str
|
|
63
|
+
payment_hash: Optional[str] = None
|
|
64
|
+
payment_anchor_type: Optional[int] = None
|
|
65
|
+
format: str = "nep413-dualsig-v1"
|
|
66
|
+
verify_url: Optional[str] = None
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def pdr_bytes(self) -> bytes:
|
|
70
|
+
return base64.b64decode(self.pdr_b64)
|
|
71
|
+
|
|
72
|
+
def _resolve_payment_anchor_type(self) -> None:
|
|
73
|
+
if self.payment_anchor_type is not None:
|
|
74
|
+
return
|
|
75
|
+
raw = self.pdr_bytes
|
|
76
|
+
version = raw[0]
|
|
77
|
+
if version == 0x02:
|
|
78
|
+
self.payment_anchor_type = PaymentAnchorType.NEAR_DIRECT
|
|
79
|
+
elif version in (0x03, 0x04):
|
|
80
|
+
pat_byte = raw[2]
|
|
81
|
+
try:
|
|
82
|
+
self.payment_anchor_type = PaymentAnchorType(pat_byte)
|
|
83
|
+
except ValueError:
|
|
84
|
+
self.payment_anchor_type = PaymentAnchorType.UNKNOWN
|
|
85
|
+
else:
|
|
86
|
+
self.payment_anchor_type = PaymentAnchorType.UNKNOWN
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def payload_bytes(self) -> bytes:
|
|
90
|
+
raw = self.pdr_bytes
|
|
91
|
+
version = raw[0]
|
|
92
|
+
if version == 0x02:
|
|
93
|
+
expected = EXTERNAL_PDR_SIZE + 1 # v2.2 was 238 (one byte less than v2.3's 239)
|
|
94
|
+
payload_size = EXTERNAL_PAYLOAD_SIZE + 1 # v2.2 was 174 (one byte less than v2.3's 175)
|
|
95
|
+
elif version in (0x03, 0x04):
|
|
96
|
+
expected = EXTERNAL_PDR_SIZE
|
|
97
|
+
payload_size = EXTERNAL_PAYLOAD_SIZE
|
|
98
|
+
else:
|
|
99
|
+
raise ValueError(f"Unknown PDR version: {version:#04x}")
|
|
100
|
+
if len(raw) != expected:
|
|
101
|
+
raise ValueError(f"Invalid PDR size: {len(raw)}, expected {expected}")
|
|
102
|
+
return raw[:payload_size]
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def signature_bytes(self) -> bytes:
|
|
106
|
+
raw = self.pdr_bytes
|
|
107
|
+
version = raw[0]
|
|
108
|
+
if version == 0x02:
|
|
109
|
+
payload_size = EXTERNAL_PAYLOAD_SIZE + 1 # v2.2 was 174
|
|
110
|
+
elif version in (0x03, 0x04):
|
|
111
|
+
payload_size = EXTERNAL_PAYLOAD_SIZE
|
|
112
|
+
else:
|
|
113
|
+
payload_size = EXTERNAL_PAYLOAD_SIZE
|
|
114
|
+
return raw[payload_size:]
|
|
115
|
+
|
|
116
|
+
def verify(self, notary_pubkey_hex: str) -> bool:
|
|
117
|
+
from nacl.signing import VerifyKey
|
|
118
|
+
from nacl.exceptions import BadSignatureError
|
|
119
|
+
|
|
120
|
+
raw = self.pdr_bytes
|
|
121
|
+
version = raw[0]
|
|
122
|
+
if version == 0x02:
|
|
123
|
+
expected = EXTERNAL_PDR_SIZE + 1 # v2.2 was 238
|
|
124
|
+
payload_size = EXTERNAL_PAYLOAD_SIZE + 1 # v2.2 was 174
|
|
125
|
+
elif version in (0x03, 0x04):
|
|
126
|
+
expected = EXTERNAL_PDR_SIZE
|
|
127
|
+
payload_size = EXTERNAL_PAYLOAD_SIZE
|
|
128
|
+
else:
|
|
129
|
+
return False
|
|
130
|
+
if len(raw) != expected:
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
self._resolve_payment_anchor_type()
|
|
134
|
+
|
|
135
|
+
payload = raw[:payload_size]
|
|
136
|
+
signature = raw[payload_size:]
|
|
137
|
+
verify_key = VerifyKey(bytes.fromhex(notary_pubkey_hex))
|
|
138
|
+
nep413_buffer = struct.pack("<II", NEP413_TAG, len(payload)) + payload
|
|
139
|
+
try:
|
|
140
|
+
verify_key.verify(nep413_buffer, signature)
|
|
141
|
+
except BadSignatureError:
|
|
142
|
+
return False
|
|
143
|
+
|
|
144
|
+
if self.payment_anchor_type not in (None, PaymentAnchorType.UNPAID):
|
|
145
|
+
if self.payment_hash is not None and len(self.payment_hash) < 4:
|
|
146
|
+
return False
|
|
147
|
+
|
|
148
|
+
return True
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aotrust-protocol
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: Asyncio-native SDK for A&O Trust Layer — Agent Notary Service
|
|
5
|
+
Author: A&O Trust Layer
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Development Status :: 4 - Beta
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Framework :: AsyncIO
|
|
14
|
+
Classifier: Topic :: Security :: Cryptography
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: aiohttp>=3.9
|
|
19
|
+
Requires-Dist: pydantic>=2.0
|
|
20
|
+
Requires-Dist: pynacl>=1.5
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
25
|
+
Requires-Dist: aioresponses>=0.7; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# agent-notary SDK
|
|
29
|
+
|
|
30
|
+
Asyncio-native Python SDK for the A&O Trust Layer Notary API.
|
|
31
|
+
|
|
32
|
+
## Install
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install agent-notary
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quickstart
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
import asyncio
|
|
42
|
+
from agent_notary import NotaryClient, NotarizeRequest
|
|
43
|
+
|
|
44
|
+
async def main():
|
|
45
|
+
client = NotaryClient(
|
|
46
|
+
api_key="your-api-key",
|
|
47
|
+
base_url="https://api.aotrust.link/v1"
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# Submit notarization
|
|
51
|
+
req = NotarizeRequest(
|
|
52
|
+
tx_hash="EzrfDW5b...",
|
|
53
|
+
work_hash="599d6999...",
|
|
54
|
+
agent_sig="base64_sig_A...",
|
|
55
|
+
agent_pubkey="aff91a18...",
|
|
56
|
+
)
|
|
57
|
+
result = await client.notarize(req)
|
|
58
|
+
print(f"Job: {result.job_id}")
|
|
59
|
+
|
|
60
|
+
# Poll for PDR
|
|
61
|
+
status = await client.wait_for_pdr(result.job_id, timeout=60)
|
|
62
|
+
if status.is_anchored:
|
|
63
|
+
pdr = await client.get_pdr(result.job_id)
|
|
64
|
+
assert pdr.verify(client.notary_pubkey)
|
|
65
|
+
print("PDR Valid: YES")
|
|
66
|
+
|
|
67
|
+
asyncio.run(main())
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Configuration
|
|
71
|
+
|
|
72
|
+
| Env Var | Constructor Arg | Description |
|
|
73
|
+
|---------|-----------------|-------------|
|
|
74
|
+
| `NOTARY_API_KEY` | `api_key` | API key for auth |
|
|
75
|
+
| `NOTARY_API_URL` | `base_url` | API base URL |
|
|
76
|
+
| `NOTARY_PUBKEY` | `notary_pubkey` | Notary Ed25519 public key (hex) |
|
|
77
|
+
|
|
78
|
+
## Endpoints
|
|
79
|
+
|
|
80
|
+
| Method | Path | Description |
|
|
81
|
+
|--------|------|-------------|
|
|
82
|
+
| POST | `/v1/notarize` | Submit notarization |
|
|
83
|
+
| GET | `/v1/status/{job_id}` | Poll job status |
|
|
84
|
+
| GET | `/v1/pdr/{job_id}` | Get PDR |
|
|
85
|
+
| POST | `/v1/notarize/quote` | Get price quote |
|
|
86
|
+
| GET | `/.well-known/agent.json` | MCP Server Card |
|
|
87
|
+
| GET | `/openapi.json` | OpenAPI 3.0 spec |
|
|
88
|
+
|
|
89
|
+
## PDR Verification
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
pdr = await client.get_pdr(job_id)
|
|
93
|
+
is_valid = pdr.verify(notary_pubkey_hex="9c7d64bb...")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Uses NEP-413 raw-buffer verification. No SHA256 pre-hash.
|
|
97
|
+
|
|
98
|
+
## Error Handling
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from agent_notary.exceptions import NotaryAuthError, NotaryPaymentError, NotaryNotFoundError
|
|
102
|
+
|
|
103
|
+
try:
|
|
104
|
+
result = await client.notarize(req)
|
|
105
|
+
except NotaryAuthError:
|
|
106
|
+
print("Invalid API key")
|
|
107
|
+
except NotaryPaymentError as e:
|
|
108
|
+
print(f"Payment/sig failed: {e}")
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Development
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pip install -e ".[dev]"
|
|
115
|
+
pytest tests/ -v
|
|
116
|
+
```
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
agent_notary/__init__.py,sha256=XFlyetoH5WTncJ2SrDyJzVFt2z6jWlTTD1UxdAwFc1Q,546
|
|
2
|
+
agent_notary/client.py,sha256=0gEDKRMaqgl22bqAnk-7FhF3age_vx0ahXC9_zm-lCI,5077
|
|
3
|
+
agent_notary/exceptions.py,sha256=YE_sKmK7BHnxTWB4wCUeWIyBgu54DknDoIAQgbW6h5Y,657
|
|
4
|
+
agent_notary/models.py,sha256=w522Co863dflEpTlWQVE9Gs6SGLgNMFTiqVr2OStLu0,4908
|
|
5
|
+
aotrust_protocol-2.2.0.dist-info/licenses/LICENSE,sha256=rbAuFQIJDAkZOMMLJ02qyksduBpI2mg2Y4PD26zgsq4,1071
|
|
6
|
+
aotrust_protocol-2.2.0.dist-info/METADATA,sha256=ihm0fG3COkZD1DtXyHYB4vjOUvrB4Y-g5mrQ8VQt5uM,3036
|
|
7
|
+
aotrust_protocol-2.2.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
8
|
+
aotrust_protocol-2.2.0.dist-info/top_level.txt,sha256=bKhWjMrwnDELWzmK7yRXR5W-Wjtl55EdgH_ZimS6l7o,13
|
|
9
|
+
aotrust_protocol-2.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 A&O Trust Layer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
agent_notary
|