blindoracle-sdk 0.4.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.
@@ -0,0 +1,74 @@
1
+ """
2
+ BlindOracle SDK
3
+ Chainlink-verified prediction markets for autonomous agents.
4
+
5
+ Usage:
6
+ from blindoracle_sdk import BlindOracleClient
7
+
8
+ client = BlindOracleClient(api_key="your_key")
9
+
10
+ # Get active markets
11
+ markets = client.markets.list()
12
+
13
+ # Run DeFi compliance check
14
+ result = client.compliance.check("0xProtocolAddress")
15
+
16
+ # Get market signal
17
+ signal = client.signals.latest()
18
+ """
19
+
20
+ from blindoracle_sdk.client import BlindOracleClient
21
+ from blindoracle_sdk.aio import AsyncBlindOracleClient
22
+ from blindoracle_sdk.markets import Market
23
+ from blindoracle_sdk.exceptions import (
24
+ BlindOracleError,
25
+ AuthenticationError,
26
+ RateLimitError,
27
+ MarketNotFoundError,
28
+ PaymentRequiredError,
29
+ ValidationError,
30
+ PassportRequiredError,
31
+ CredentialNotFoundError,
32
+ )
33
+ from blindoracle_sdk.audit import AuditAPI, AuditAttestation, verify_inclusion, verify_anchor
34
+ from blindoracle_sdk.privacy import PrivacyAPI, DISCLOSURE_MODES, ZK_CLAIM_TYPES
35
+ from blindoracle_sdk.metrics import MetricsAPI
36
+ from blindoracle_sdk.delegation import (
37
+ DelegationLog,
38
+ verify_signature,
39
+ delegation_signature,
40
+ delegator_passport_hash,
41
+ DELEGATION_KIND,
42
+ )
43
+
44
+ __version__ = "0.4.0"
45
+ __author__ = "Craig Brown"
46
+ __email__ = "craigmbrown@gmail.com"
47
+ __url__ = "https://craigmbrown.com/blindoracle"
48
+
49
+ __all__ = [
50
+ "BlindOracleClient",
51
+ "AsyncBlindOracleClient",
52
+ "Market",
53
+ "BlindOracleError",
54
+ "AuthenticationError",
55
+ "RateLimitError",
56
+ "MarketNotFoundError",
57
+ "PaymentRequiredError",
58
+ "ValidationError",
59
+ "PassportRequiredError",
60
+ "CredentialNotFoundError",
61
+ "AuditAPI",
62
+ "AuditAttestation",
63
+ "verify_inclusion",
64
+ "verify_anchor",
65
+ "PrivacyAPI",
66
+ "DISCLOSURE_MODES",
67
+ "ZK_CLAIM_TYPES",
68
+ "MetricsAPI",
69
+ "DelegationLog",
70
+ "verify_signature",
71
+ "delegation_signature",
72
+ "delegator_passport_hash",
73
+ "DELEGATION_KIND",
74
+ ]
@@ -0,0 +1,100 @@
1
+ """BlindOracle Agents API — ERC-8004 passport, reputation, ProofDB."""
2
+
3
+ from typing import Optional, List
4
+
5
+
6
+ class AgentPassport:
7
+ """ERC-8004 agent passport and reputation record."""
8
+ def __init__(self, data: dict):
9
+ self.agent_id = data.get("agent_id")
10
+ self.name = data.get("name")
11
+ self.tier = data.get("tier") # "explorer"|"contributor"|"operator"|"partner"
12
+ self.reputation_score = data.get("reputation_score", 0)
13
+ self.proofs_published = data.get("proofs_published", 0)
14
+ self.accuracy_rate = data.get("accuracy_rate") # 0.0-1.0
15
+ self.status = data.get("status") # "active"|"revoked"|"suspended"
16
+ self.raw = data
17
+
18
+ def __repr__(self):
19
+ return (
20
+ f"<AgentPassport id={self.agent_id!r} tier={self.tier!r} "
21
+ f"rep={self.reputation_score} accuracy={self.accuracy_rate}>"
22
+ )
23
+
24
+
25
+ class AgentsAPI:
26
+ """
27
+ Agent identity, reputation, and ProofDB operations.
28
+
29
+ Example:
30
+ # Get your agent's passport
31
+ me = client.agents.me()
32
+ print(me.tier, me.accuracy_rate)
33
+
34
+ # Publish a ProofOfAccuracy
35
+ client.agents.publish_proof(
36
+ kind="ProofOfAccuracy",
37
+ market_id="mkt_abc123",
38
+ outcome="yes",
39
+ resolution="yes",
40
+ )
41
+ """
42
+
43
+ def __init__(self, client):
44
+ self._client = client
45
+
46
+ def me(self) -> AgentPassport:
47
+ """Get the authenticated agent's passport and reputation."""
48
+ data = self._client.get("/agents/me")
49
+ return AgentPassport(data)
50
+
51
+ def get(self, agent_id: str) -> AgentPassport:
52
+ """Get another agent's public passport by ID."""
53
+ data = self._client.get(f"/agents/{agent_id}")
54
+ return AgentPassport(data)
55
+
56
+ def publish_proof(
57
+ self,
58
+ kind: str,
59
+ market_id: Optional[str] = None,
60
+ metadata: Optional[dict] = None,
61
+ **kwargs,
62
+ ) -> dict:
63
+ """
64
+ Publish a proof to ProofDB.
65
+
66
+ Args:
67
+ kind: Proof kind — "ProofOfAccuracy" | "ProofOfWin" | "ProofOfDelegation"
68
+ | "ProofOfCompliance" | "ProofOfMemoryIntegrity"
69
+ market_id: Related market ID (for accuracy/win proofs)
70
+ metadata: Additional proof metadata
71
+ **kwargs: Additional proof fields
72
+
73
+ Returns:
74
+ dict with proof_id, kind, published_at, signature
75
+ """
76
+ body = {"kind": kind, **(metadata or {}), **kwargs}
77
+ if market_id:
78
+ body["market_id"] = market_id
79
+ return self._client.post("/agents/proofs", body=body)
80
+
81
+ def get_leaderboard(
82
+ self,
83
+ category: Optional[str] = None,
84
+ limit: int = 10,
85
+ ) -> List[AgentPassport]:
86
+ """
87
+ Get the top agents by reputation score.
88
+
89
+ Args:
90
+ category: Filter by agent category
91
+ limit: Max results (default 10)
92
+
93
+ Returns:
94
+ List of AgentPassport ordered by reputation_score desc
95
+ """
96
+ params = {"limit": limit}
97
+ if category:
98
+ params["category"] = category
99
+ data = self._client.get("/agents/leaderboard", params=params)
100
+ return [AgentPassport(a) for a in data.get("agents", [])]
blindoracle_sdk/aio.py ADDED
@@ -0,0 +1,117 @@
1
+ """Async client — ``AsyncBlindOracleClient``.
2
+
3
+ Zero-dependency async: the sync :class:`BlindOracleClient` (stdlib ``urllib``) is
4
+ run in a worker thread via :func:`asyncio.to_thread`, so awaiting a call never
5
+ blocks the event loop and we reuse every bit of the sync client's tested retry /
6
+ error / x402 logic. No httpx, no aiohttp.
7
+
8
+ import asyncio
9
+ from blindoracle_sdk.aio import AsyncBlindOracleClient
10
+
11
+ async def main():
12
+ bo = await AsyncBlindOracleClient.register("my-agent", ["verified-introduction"])
13
+ ms = await bo.markets.list(status="active", limit=5)
14
+ async for m in bo.markets.aiter(status="active", max_results=20):
15
+ print(m.title)
16
+
17
+ asyncio.run(main())
18
+ """
19
+
20
+ import asyncio
21
+ from typing import AsyncIterator
22
+
23
+ from blindoracle_sdk.client import BlindOracleClient
24
+
25
+ _NAMESPACES = (
26
+ "markets",
27
+ "compliance",
28
+ "signals",
29
+ "agents",
30
+ "audit",
31
+ "privacy",
32
+ "metrics",
33
+ "introductions",
34
+ "attestation",
35
+ )
36
+
37
+
38
+ class _AsyncProxy:
39
+ """Wrap a sync namespace so its methods become awaitable.
40
+
41
+ A sync generator method named ``iter`` is additionally exposed as an async
42
+ generator ``aiter`` (each ``next()`` runs in a thread).
43
+ """
44
+
45
+ def __init__(self, target):
46
+ self._t = target
47
+
48
+ def __getattr__(self, name):
49
+ attr = getattr(self._t, name)
50
+ if callable(attr):
51
+
52
+ async def _call(*args, **kwargs):
53
+ return await asyncio.to_thread(attr, *args, **kwargs)
54
+
55
+ return _call
56
+ return attr
57
+
58
+ def aiter(self, *args, **kwargs) -> AsyncIterator:
59
+ """Async wrapper over a sync ``iter(...)`` generator (e.g. markets.aiter)."""
60
+ gen = self._t.iter(*args, **kwargs)
61
+ _sentinel = object()
62
+
63
+ async def _agen():
64
+ while True:
65
+ item = await asyncio.to_thread(next, gen, _sentinel)
66
+ if item is _sentinel:
67
+ return
68
+ yield item
69
+
70
+ return _agen()
71
+
72
+
73
+ class AsyncBlindOracleClient:
74
+ """Async facade over :class:`BlindOracleClient`. Same args, same namespaces."""
75
+
76
+ def __init__(self, *args, **kwargs):
77
+ self._wrap(BlindOracleClient(*args, **kwargs))
78
+
79
+ def _wrap(self, sync: BlindOracleClient) -> "AsyncBlindOracleClient":
80
+ self._sync = sync
81
+ for ns in _NAMESPACES:
82
+ setattr(self, ns, _AsyncProxy(getattr(sync, ns)))
83
+ return self
84
+
85
+ # passthrough identity / config
86
+ @property
87
+ def api_key(self):
88
+ return self._sync.api_key
89
+
90
+ @property
91
+ def agent_id(self):
92
+ return self._sync.agent_id
93
+
94
+ @property
95
+ def registration(self):
96
+ return self._sync.registration
97
+
98
+ @classmethod
99
+ async def register(
100
+ cls,
101
+ name,
102
+ capabilities,
103
+ evm_address: str = "",
104
+ base_url: str = BlindOracleClient.DEFAULT_BASE_URL,
105
+ timeout: int = 30,
106
+ ) -> "AsyncBlindOracleClient":
107
+ """Async one-line onboarding — see :meth:`BlindOracleClient.register`."""
108
+ sync = await asyncio.to_thread(
109
+ BlindOracleClient.register, name, capabilities, evm_address, base_url, timeout
110
+ )
111
+ return cls.__new__(cls)._wrap(sync)
112
+
113
+ async def get(self, path, params=None):
114
+ return await asyncio.to_thread(self._sync.get, path, params)
115
+
116
+ async def post(self, path, body=None, extra_headers=None):
117
+ return await asyncio.to_thread(self._sync.post, path, body, extra_headers)
@@ -0,0 +1,78 @@
1
+ """BlindOracle Attestation API — request a portable W3C Verifiable Credential
2
+ for a finished agent-security audit, via the public MCP endpoint.
3
+
4
+ REQUIRED FLOW (enforced server-side; this client surfaces it):
5
+ 1. Onboard + activate the agent -> ERC-8004 passport (client.agents)
6
+ 2. Run a BlindOracle audit -> ProofOfAuditReport kind 30105
7
+ 3. request_credential(proof_id) -> W3C VC (this module)
8
+
9
+ A credential is ONLY issued/served when the audited agent holds an activated,
10
+ non-revoked passport AND a real audit proof exists. Skipping step 1 or 2 raises
11
+ PassportRequiredError / CredentialNotFoundError.
12
+ """
13
+ import json
14
+ import urllib.request
15
+ import urllib.error
16
+
17
+ from blindoracle_sdk.exceptions import (
18
+ PassportRequiredError,
19
+ CredentialNotFoundError,
20
+ BlindOracleError,
21
+ )
22
+
23
+ DEFAULT_MCP_URL = "https://api.craigmbrown.com/mcp/attestation"
24
+
25
+
26
+ class AttestationAPI:
27
+ """Client for the BlindOracle Attestation MCP endpoint (get_audit_credential)."""
28
+
29
+ def __init__(self, client, mcp_url: str = DEFAULT_MCP_URL):
30
+ self._client = client
31
+ self._mcp_url = mcp_url
32
+
33
+ def _rpc(self, method: str, params: dict | None = None) -> dict:
34
+ body = json.dumps({"jsonrpc": "2.0", "id": 1, "method": method,
35
+ "params": params or {}}).encode()
36
+ req = urllib.request.Request(
37
+ self._mcp_url, data=body,
38
+ headers={"Content-Type": "application/json",
39
+ "User-Agent": "blindoracle-sdk/1.x"}, method="POST")
40
+ try:
41
+ with urllib.request.urlopen(req, timeout=getattr(self._client, "timeout", 30)) as r:
42
+ return json.loads(r.read())
43
+ except urllib.error.URLError as e: # noqa: BLE001
44
+ raise BlindOracleError(f"attestation endpoint unreachable: {e}")
45
+
46
+ def list_tools(self) -> list:
47
+ """MCP tools/list — discover the attestation tools."""
48
+ return self._rpc("tools/list").get("result", {}).get("tools", [])
49
+
50
+ def request_credential(self, proof_id: str) -> dict:
51
+ """Return the W3C Verifiable Credential for a finished audit's proof_id.
52
+
53
+ Raises:
54
+ PassportRequiredError — agent lacks an activated ERC-8004 passport.
55
+ CredentialNotFoundError — no credential for proof_id yet (run the audit).
56
+ """
57
+ if not proof_id:
58
+ raise ValueError("proof_id is required")
59
+ resp = self._rpc("tools/call", {"name": "get_audit_credential",
60
+ "arguments": {"proof_id": proof_id}})
61
+ if "error" in resp:
62
+ raise BlindOracleError(f"attestation error: {resp['error']}")
63
+ result = resp.get("result", {})
64
+ text = (result.get("content") or [{}])[0].get("text", "")
65
+ if result.get("isError"):
66
+ low = text.lower()
67
+ if "no credential found" in low or "not found" in low:
68
+ raise CredentialNotFoundError(text)
69
+ if "passport" in low:
70
+ raise PassportRequiredError(text)
71
+ raise CredentialNotFoundError(text)
72
+ try:
73
+ return json.loads(text)
74
+ except json.JSONDecodeError:
75
+ raise BlindOracleError(f"unexpected attestation response: {text[:160]}")
76
+
77
+ # alias — the name external callers expect
78
+ get_audit_credential = request_credential
@@ -0,0 +1,127 @@
1
+ """BlindOracle Audit API — verifiable, on-chain-anchored agent audits.
2
+
3
+ Exposes the verifiable-anchoring layer (shipped 2026-05-23): retrieve an agent's audit report +
4
+ attestation, and INDEPENDENTLY verify it — inclusion proofs are checked client-side (don't trust
5
+ the server), anchor receipts via any public RPC / Nostr relay.
6
+ """
7
+ import hashlib
8
+ import json
9
+ import urllib.request
10
+ from typing import Optional
11
+
12
+ # public Base RPCs for keyless anchor read-back (fallback chain)
13
+ _BASE_MAINNET_RPC = ["https://mainnet.base.org", "https://base.llamarpc.com"]
14
+ _BASE_SEPOLIA_RPC = ["https://sepolia.base.org"]
15
+ _VERIFY_ANCHOR_SELECTOR = "0xf32bd282" # keccak256("verifyAnchor(bytes32)")[:4]
16
+
17
+
18
+ class AuditAttestation:
19
+ """An agent's 'VERIFIABLY-AUDITED' attestation (lives in its passport)."""
20
+ def __init__(self, data: dict):
21
+ self.audit_id = data.get("audit_id")
22
+ self.risk_score = data.get("risk_score")
23
+ self.risk_level = data.get("risk_level")
24
+ self.findings_count = data.get("findings_count")
25
+ self.audit_hash = data.get("audit_hash")
26
+ self.proof_of_audit_id = data.get("proof_of_audit_id") # kind 30105
27
+ self.state_anchor_proof_id = data.get("state_anchor_proof_id") # kind 30106
28
+ self.merkle_root = data.get("merkle_root")
29
+ self.root_commitment = data.get("root_commitment")
30
+ self.witnesses = data.get("witnesses", {})
31
+ self.badge = data.get("badge")
32
+ self.raw = data
33
+
34
+ def __repr__(self):
35
+ return (f"<AuditAttestation {self.audit_id!r} risk={self.risk_score} "
36
+ f"badge={self.badge!r} anchored={bool(self.state_anchor_proof_id)}>")
37
+
38
+
39
+ def _sorted_pair(a_hex: str, b_hex: str) -> str:
40
+ a, b = bytes.fromhex(a_hex), bytes.fromhex(b_hex)
41
+ lo, hi = (a, b) if a <= b else (b, a)
42
+ return hashlib.sha256(lo + hi).hexdigest()
43
+
44
+
45
+ def verify_inclusion(leaf_hex: str, proof_path: list, merkle_root_hex: str) -> bool:
46
+ """Client-side inclusion check (sorted-pair Merkle). No network, no trust in the server.
47
+
48
+ Fold the leaf with each sibling in ``proof_path`` and compare to ``merkle_root_hex``.
49
+ """
50
+ acc = leaf_hex
51
+ for sib in proof_path:
52
+ acc = _sorted_pair(acc, sib)
53
+ return acc == merkle_root_hex
54
+
55
+
56
+ def _rpc(urls, method, params, timeout=15):
57
+ body = json.dumps({"jsonrpc": "2.0", "method": method, "params": params, "id": 1}).encode()
58
+ last = None
59
+ for url in urls:
60
+ try:
61
+ req = urllib.request.Request(url, data=body, headers={
62
+ "content-type": "application/json", "User-Agent": "blindoracle-sdk/0.2"})
63
+ with urllib.request.urlopen(req, timeout=timeout) as r:
64
+ return json.loads(r.read().decode()).get("result")
65
+ except Exception as e: # noqa: BLE001
66
+ last = e
67
+ raise RuntimeError(f"all RPCs failed: {last}")
68
+
69
+
70
+ def verify_anchor(root_commitment_hex: str, contract: str, network: str = "base-mainnet") -> dict:
71
+ """Independently confirm a state-anchor root via ProofAnchor.verifyAnchor on a public RPC.
72
+
73
+ Returns {"exists": bool, "network", "contract"}. No keys, no spend.
74
+ """
75
+ urls = _BASE_MAINNET_RPC if network == "base-mainnet" else _BASE_SEPOLIA_RPC
76
+ data = _VERIFY_ANCHOR_SELECTOR + root_commitment_hex.removeprefix("0x").rjust(64, "0")
77
+ out = _rpc(urls, "eth_call", [{"to": contract, "data": data}, "latest"])
78
+ exists = bool(out) and out != "0x" and int(out[2:66], 16) == 1
79
+ return {"exists": exists, "network": network, "contract": contract}
80
+
81
+
82
+ class AuditAPI:
83
+ """Retrieve + independently verify agent audits.
84
+
85
+ Example:
86
+ att = client.audit.get_attestation("agent-x")
87
+ # don't trust — verify:
88
+ ok = client.audit.verify_anchor_receipt(att)
89
+ incl = client.audit.verify_inclusion_proof(leaf, proof_path, att.merkle_root)
90
+ """
91
+
92
+ def __init__(self, client):
93
+ self._client = client
94
+
95
+ def get_report(self, agent_id: str) -> dict:
96
+ """Full audit report JSON for an agent (findings, risk, audit_hash, proof ids)."""
97
+ return self._client.gw_get(f"/a2a/agents/{agent_id}/audit-report")
98
+
99
+ def get_attestation(self, agent_id: str) -> AuditAttestation:
100
+ """The passport-level 'VERIFIABLY-AUDITED' attestation (lighter than the full report)."""
101
+ data = self._client.gw_get(f"/a2a/agents/{agent_id}/audit-attestation")
102
+ return AuditAttestation(data)
103
+
104
+ def list_anchor_receipts(self, limit: int = 20) -> list:
105
+ """Recent state-anchor receipts (root_commitment + witness tx/event ids)."""
106
+ return self._client.gw_get("/a2a/anchor-receipts", params={"limit": limit}).get("entries", [])
107
+
108
+ # ---- independent verification (client-side / keyless) ----
109
+ @staticmethod
110
+ def verify_inclusion_proof(leaf_hex: str, proof_path: list, merkle_root_hex: str) -> bool:
111
+ """Verify a single record belongs to the committed set — locally, no server trust."""
112
+ return verify_inclusion(leaf_hex, proof_path, merkle_root_hex)
113
+
114
+ @staticmethod
115
+ def verify_anchor_receipt(attestation, network: str = "base-mainnet") -> dict:
116
+ """Confirm an attestation's root is anchored on-chain via a public RPC.
117
+
118
+ Accepts an AuditAttestation or a dict with root_commitment + witness contract.
119
+ """
120
+ att = attestation.raw if isinstance(attestation, AuditAttestation) else attestation
121
+ root = att.get("root_commitment")
122
+ witnesses = att.get("witnesses", {})
123
+ contract = (witnesses.get("base_mainnet") or {}).get("contract") if isinstance(
124
+ witnesses.get("base_mainnet"), dict) else att.get("mainnet_contract")
125
+ if not (root and contract):
126
+ return {"exists": False, "error": "no root_commitment / mainnet contract in attestation"}
127
+ return verify_anchor(root, contract, network)
blindoracle_sdk/cli.py ADDED
@@ -0,0 +1,105 @@
1
+ """``blindoracle`` command-line interface — try the marketplace before you code.
2
+
3
+ blindoracle version
4
+ blindoracle register my-agent --cap verified-introduction --cap research
5
+ blindoracle markets list --status active --limit 5
6
+ blindoracle agent me # uses BLINDORACLE_API_KEY from env
7
+
8
+ Thin wrapper over the SDK; prints JSON so output pipes into ``jq``.
9
+ """
10
+
11
+ import argparse
12
+ import json
13
+ import sys
14
+
15
+ from blindoracle_sdk import BlindOracleClient, __version__
16
+ from blindoracle_sdk.exceptions import BlindOracleError
17
+
18
+
19
+ def _print(obj) -> None:
20
+ print(json.dumps(obj, indent=2, default=str))
21
+
22
+
23
+ def _cmd_version(args) -> int:
24
+ _print({"blindoracle_sdk": __version__})
25
+ return 0
26
+
27
+
28
+ def _cmd_register(args) -> int:
29
+ bo = BlindOracleClient.register(args.name, args.cap, evm_address=args.evm or "")
30
+ _print(
31
+ {
32
+ "agent_id": bo.agent_id,
33
+ "api_key": bo.api_key,
34
+ "tier": (bo.registration or {}).get("tier"),
35
+ "hint": "export BLINDORACLE_API_KEY=<api_key> to reuse this identity",
36
+ }
37
+ )
38
+ return 0
39
+
40
+
41
+ def _cmd_markets_list(args) -> int:
42
+ bo = BlindOracleClient(api_key=args.api_key)
43
+ ms = bo.markets.list(status=args.status, category=args.category, limit=args.limit)
44
+ _print(
45
+ [
46
+ {"id": m.id, "title": m.title, "yes_probability": m.yes_probability, "status": m.status}
47
+ for m in ms
48
+ ]
49
+ )
50
+ return 0
51
+
52
+
53
+ def _cmd_agent_me(args) -> int:
54
+ bo = BlindOracleClient(api_key=args.api_key)
55
+ me = bo.agents.me()
56
+ _print(me.raw if hasattr(me, "raw") else me)
57
+ return 0
58
+
59
+
60
+ def build_parser() -> argparse.ArgumentParser:
61
+ p = argparse.ArgumentParser(prog="blindoracle", description="BlindOracle agent-marketplace CLI")
62
+ p.add_argument(
63
+ "--api-key", dest="api_key", default=None, help="API key (else BLINDORACLE_API_KEY env)"
64
+ )
65
+ sub = p.add_subparsers(dest="command")
66
+
67
+ sub.add_parser("version", help="print SDK version").set_defaults(func=_cmd_version)
68
+
69
+ pr = sub.add_parser("register", help="self-serve onboard -> ERC-8004 passport + key")
70
+ pr.add_argument("name")
71
+ pr.add_argument(
72
+ "--cap", action="append", default=[], required=True, help="capability (repeatable)"
73
+ )
74
+ pr.add_argument("--evm", default=None, help="optional EVM address")
75
+ pr.set_defaults(func=_cmd_register)
76
+
77
+ pm = sub.add_parser("markets", help="market operations")
78
+ msub = pm.add_subparsers(dest="markets_command")
79
+ ml = msub.add_parser("list", help="list markets")
80
+ ml.add_argument("--status", default="active")
81
+ ml.add_argument("--category", default=None)
82
+ ml.add_argument("--limit", type=int, default=20)
83
+ ml.set_defaults(func=_cmd_markets_list)
84
+
85
+ pa = sub.add_parser("agent", help="agent operations")
86
+ asub = pa.add_subparsers(dest="agent_command")
87
+ asub.add_parser("me", help="your passport + reputation").set_defaults(func=_cmd_agent_me)
88
+
89
+ return p
90
+
91
+
92
+ def main(argv=None) -> int:
93
+ args = build_parser().parse_args(argv)
94
+ if not getattr(args, "func", None):
95
+ build_parser().print_help()
96
+ return 1
97
+ try:
98
+ return args.func(args)
99
+ except BlindOracleError as e:
100
+ print(f"error: {e}", file=sys.stderr)
101
+ return 2
102
+
103
+
104
+ if __name__ == "__main__":
105
+ sys.exit(main())