Python SDK for Aleo: PyO3 bindings over snarkvm v4.9.1 plus pure-Python client / record-scanner / delegated-proving layers and a Web3.py-style facade. Two shipped packages:
| Path | Package | What |
|---|---|---|
sdk/ |
aleo |
the PyO3 crate (_aleolib_mainnet) + the aleo Python package (accounts, algebra, programs, records, algorithms, network client, record scanner, facade) |
sdk-abi/ |
aleo-abi |
a separate package that generates contract ABIs from Aleo bytecode via Leo's leo-abi crate; kept isolated so Leo's snarkvm feature flags never reach the main crate |
cd sdk && python3 -m venv .env && . .env/bin/activate && pip install maturin
maturin develop --features mainnet # build + install the extension
python -m pytest python/tests -m "not slow" # fast suite (what CI runs)
python -m pytest python/tests -m slow # network/proving tests (run locally)
npx -y pyright@latest # strict type check (must be 0)
cargo fmt --check && cargo clippy --no-default-features --features mainnet -- -D warnings
cargo check --no-default-features --features testnet # guard the testnet cfg seamRun pytest from the package dir so it uses that package's pytest.ini
(sdk/pytest.ini, sdk-abi/pytest.ini) — a legacy root setup.cfg injects
addopts if you invoke pytest from the repo root.
- Two-build, no macro. One flat codebase;
CurrentNetwork/CurrentAleoare cfg-selected (--features mainnet|testnet) and compiled once per network into_aleolib_mainnet/_aleolib_testnet, exposed asaleo.mainnet.*/aleo.testnet.*. The Web3.py facade sits on top; network comes from the provider. - Sync + async pairs. Every HTTP client ships both (
AleoNetworkClient/AsyncAleoNetworkClient, scanner likewise,Aleo/AsyncAleo): sync onrequests, async onhttpx, shared pure logic in a common module — never duplicate orchestration. - Pythonic (anti-viem) surface. snake_case; pure getters are
@property; Python dunders (__str__/__bytes__/__eq__/__hash__) overto_string()/to_bytes()/equals(); explicitPrivateKey.random()(no implicit-RNG constructor). Do NOT mirror the TS/viem method names where a Python idiom exists. - Algebraic types expose BOTH a dunder and a named method (
__add__+add,__mul__+multiply, Group__mul__+scalar_multiply,__neg__+negate). snarkvm'sinverse()is negation → expose asnegate, neverinverse. - Both stubs. Update
python/aleo/_aleolib_mainnet.pyi(list[...]style) ANDpython/aleo/__init__.pyi(List[...]style) for every binding change. Pure-Python modules need no stub — annotate inline; pyright strict must pass. - aleo-abi isolation. Never add a Leo crate to
sdk/. The CItest-abilane assertsdev_skip_checksnever appears insdk/Cargo.lock.
- Known-answer vectors are vendored from
ProvableHQ/sdk(and itswasm/crate) with a_sourcenote (path + commit). Copy constants character-exact. - Never weaken an assertion to make a test pass. A KAT mismatch is a real signal — report it, don't paper over it.
- Proving / live-network tests are
@pytest.mark.slow(SRS downloads, live REST); CI runs-m "not slow".
Proving and scanning are Provable services hung off the hosted API's
SERVICE ROOT — NOT under the read node's /v2/{network} base. Two hosts:
https://edge.provable.com/api(the default,DEFAULT_HOST) — open: no API key, consumer id, or JWT for reads,/prove, or/scanner. Note the/apipath prefix: reads are{root}/v2/{network}/…, services{root}/prove,{root}/scanner. Derive the root withservice_root(url)(keeps the prefix; strips a legacy/v2[/{network}]suffix) — NOTjwt_origin, which drops it.https://api.provable.com(LEGACY_HOST) — credentialed: the same layout at the bare origin, with the prover and scanner behindapi_key+consumer_idJWTs.requires_credentials(url)is True only here; everything below about JWTs applies only to this host.
Each is confirmed working against live testnet (see tests/e2e/):
- JWT auth (legacy host only) —
POST {root}/jwts/{consumerId}. - Delegated proving —
{origin}/prove/{network}prefix:GET {origin}/prove/{network}/pubkey— ephemeral X25519 key + key id + aSet-Cookieaffinity session. The ephemeral private key lives only on the backend that served this call, so the follow-up/proveMUST stick to it: rely on the client cookie jar (requests.Session / httpx.AsyncClient) to re-send the cookie — do NOT hand-build aCookieheader fromset-cookie(comma-joins multiple cookies, carries attributes, and bypasses the jar).POST {origin}/prove/{network}/prove/authorization(or/prove/request) — sealed-box{key_id, ciphertext}; JWT + the affinity cookie; SDK retries 500/503.
- Record scanner —
{root}/scanner/{network}prefix (envRECORD_SCANNER_URL=https://edge.provable.com/api/scanner).
Documented endpoints (docs describe paths relative to the service base; the base is the origin + service prefix above):
- Register → API key:
POST /consumers— https://docs.provable.com/docs/api/services/get-auth-register - Issue/refresh JWT:
POST /jwts/:consumerId(sendX-Provable-API-Key; JWT returns in theAuthorization: Bearer …header) — https://docs.provable.com/docs/api/services/issue-jwt - Ephemeral prover pubkey:
GET /pubkey— https://docs.provable.com/docs/api/services/get-prove-pubkey - Submit proving:
POST /prove/authorization(the authorization endpoint; the docs also describe a/prove/encryptedvariant). https://docs.provable.com/docs/api/services/post-prove-encrypted
Tests read ALEO_CONSUMER_ID / ALEO_DPS_API_KEY from the env; the client mints
and refreshes JWTs automatically. The prover base defaults to
{jwt_origin(base_url)}/prove/{network} — override via HTTPProvider(prover_uri=…)
only to point at a non-default prover host.
Multi-step work is tracked in .superpowers/sdd/progress.md (git-ignored) with
a review gate per task. Specs/plans live under docs/superpowers/ (git-ignored,
kept local). Documentation voice: see .agents/voice.md.