Official Python SDK for Blockchain0x - the non-custodial AI-agent wallet platform on Base.
Pre-release:
0.0.1a0ships the operational essentials - HTTP transport,apiKeysresource, andwebhooks.verify. The full surface (every resource + x402 client + async client) lands in the 21.3 Phase C follow-up rows.
pip install blockchain0xRequires Python 3.9 or newer.
from blockchain0x import Client
client = Client(api_key="sk_test_...")
for key in client.api_keys.list()["data"]:
print(key["id"], key["prefix"])The client pins the network from the api-key prefix
(sk_test_* → testnet, sk_live_* → mainnet). Override with
Client(api_key=..., network="mainnet") when you need both modes in
one process (mixed-mode tests).
This is the single most important utility this SDK ships - drop it into the top of your webhook handler BEFORE touching the body.
from blockchain0x import webhooks
# In your Flask / FastAPI / Django handler:
def receive_webhook(req, raw_body, secret):
result = webhooks.verify(headers=req.headers, raw_body=raw_body, secret=secret)
if not result.ok:
return {"code": result.code}, 400
# result.event_type / result.event_id / result.delivery_id populated
process_event(raw_body, result.event_type)The verifier:
- Reads
X-Blockchain0x-Signaturein eithert=<ts>,v1=<hex>or bare-hex form (some load balancers strip commas). - Falls back to
X-Blockchain0x-Timestampwhen the signature is bare. - Rejects with
webhook.timestamp_outside_windowwhen drift exceeds 300s (5-minute replay window; matches the worker). - Constant-time compares via
hmac.compare_digest.
For exception-based flows pass raise_on_fail=True:
try:
webhooks.verify(headers=req.headers, raw_body=raw_body, secret=secret, raise_on_fail=True)
except WebhookSignatureError as e:
return {"code": e.code}, 400Two classes:
Blockchain0xError- base class; every SDK error inherits.ApiKeyError- subclass for HTTP 401 / 403 envelopes whoseerror.codestarts withapikey.(e.g.apikey.scope_insufficient,apikey.wallet_not_assigned).
Always branch on .code, never regex-match .message:
from blockchain0x import ApiKeyError
try:
client.api_keys.list()
except ApiKeyError as e:
if e.code == "apikey.scope_insufficient":
# mint a fresh key with more scope
...Two key shapes exist (see docs/concept-api-key-types.md for the full decision tree):
- Wallet-only: bound to ONE agent via
agent_id. The right shape for an autonomous AI agent that IS one wallet. - Workspace: for human operators that can carry workspace-level scopes AND assignments to N specific wallets.
The Python SDK forwards both shapes through the same client.api_keys resource; the body shape decides the flavor on the server. Once the create surface lands beyond the C-2 scaffold:
key = client.api_keys.create(
label="Treasury daily reconciliation",
workspace_scopes=["read_workspace"],
wallet_assignments=[
{"agentId": "agt_trading", "scopes": ["read_wallet_metadata"]},
{"agentId": "agt_settlement", "scopes": ["read_wallet_metadata"]},
],
expires_in_days=30,
)
print(key["secret"]) # shown ONCEServer-side RBAC: the minter cannot grant a scope they do not have themselves. Over-grants reject with apikey.role_insufficient_for_grants - catch ApiKeyError and branch on e.code.
Cascade behaviour: when an assigned wallet is soft-deleted, the assignment row is removed. If the key has zero workspace scopes AND no remaining assignments, it auto-revokes with apikey.no_grants_remaining. Mint a fresh key.
The sibling package blockchain0x-x402 (Python port of @blockchain0x/x402) will ship the x402 client + FastAPI / Starlette / Flask adapters in sub-plan 21.3 row C-7. The wire format is identical across languages so a Python service can accept payments from a Node client and vice-versa.
The transport retries on 429 and 5xx with exponential backoff
(0.5s → 1s → 2s → ... → 30s cap). Retry-After is honoured when the
server sends it. Network errors (timeout, connection drop) get the
same retry budget.
POST / PATCH / DELETE requests carry an Idempotency-Key header
- the SDK mints a uuid4 if you do not supply one. Pass
idempotency_key="..."to thread a stable key across SDK retries OR across processes (e.g. cron jobs that hash their input deterministically).
Type-only DTOs are generated from apps/backend/openapi/openapi.yaml
via datamodel-code-generator.
Transport, retry, error mapping, and the webhook verifier stay
handwritten - see codegen/README.md for the
decision rationale.
Source-of-truth: this directory in
Tosh-Labs/blockchain0x-app
under packages/sdk-python/.
Public mirror: Tosh-Labs/blockchain0x-python
(receives merges from this directory on each push to dev).
Distribution: pypi via Trusted Publisher OIDC.
Apache-2.0.