Skip to content

shield-swap-sdk: typed Python client for the shield_swap AMM - #49

Merged
iamalwaysuncomfortable merged 22 commits into
masterfrom
feat/shield-swap-sdk
Jul 14, 2026
Merged

shield-swap-sdk: typed Python client for the shield_swap AMM#49
iamalwaysuncomfortable merged 22 commits into
masterfrom
feat/shield-swap-sdk

Conversation

@iamalwaysuncomfortable

@iamalwaysuncomfortable iamalwaysuncomfortable commented Jul 13, 2026

Copy link
Copy Markdown
Member

What

shield-swap-sdk (new sibling package, shield-swap-sdk/): a typed Python client for shield_swap_v3.aleo, bound to the facade — the Python analog of aleo-viem's @provablehq/shield-swap-sdk. Built on aleo.codegen (#47). Also extends the ABI package (dist now aleo-contract-abi-generator) with an optional imports= parameter (snarkVM validation is contextual; the deployed contract imports test_shield_swap_multisig_core.aleo), and adds deployment + proofless-devnode APIs to the main SDK's bindings.

from aleo import Aleo
from aleo_shield_swap import ShieldSwap

aleo = Aleo(Aleo.HTTPProvider("https://api.provable.com"))
aleo.default_account = account
dex = ShieldSwap(aleo)                       # signer/scanner/proving from the facade

quote = dex.api.get_route(token_in=t0, token_out=t1, amount_in=10**9)
handle = dex.swap(pool_key=key, token_in_id=t0, amount_in=10**9,
                  expected_out=expected).delegate()   # SwapHandle — persist it
out = dex.claim_swap_output(handle).delegate()        # ClaimResult

Surface

  • Reads (typed, from committed aleo.codegen bindings): get_pool, get_slot (semantic SlotView: Q64 price(), tick_range()), get_swap_output, is_pool_initialized; pure derive_pool_key/derive_tick_key (BHP256, vector-verified against the TS SDK)
  • Writes preserve the facade verb ladder — every verb returns a prepared call (simulate/transact/delegate) with a typed result: swapclaim_swap_output (two-transaction private flow, serializable SwapHandle), create_pool, mint, increase/decrease_liquidity, collect, burn
  • Blinded identity: Poseidon8 derivation incl. the 252-bit LE repacking, byte-for-byte against the reference vectors; used_blinded_addresses counter scan
  • dex.api: typed REST client for amm-api (generated OpenAPI models; pools/tokens/route/swaps/ohlcv/balances; challenge/verify JWT auth for the gated endpoints), sync + async
  • Balances: private (record scan) / public (API) / composed per-token view
  • Agent tools + MCP server ([mcp] extra): all 12 verbs with exact JSON schemas
  • AsyncShieldSwap: reads, balances, swap lifecycle (liquidity verbs are sync-only for now, documented)
  • README in the repo's documentation voice (verb surface, privacy tradeoffs, test tiers)

Renames + versions

Distribution names changed (imports unchanged): main SDK aleoaleo-sdk (imports as aleo), aleo-abialeo-contract-abi-generator (imports as aleo_abi), package dir sdk-shield-swap/shield-swap-sdk/ (dist shield-swap-sdk, imports as aleo_shield_swap). All three SDKs are 0.2.0; package_version() falls back to the old aleo dist so existing installs report real versions. READMEs added for shield-swap-sdk and the ABI package.

Main-SDK bindings (sdk/src)

  • Process.deploy / Process.deployment_cost — synthesize and price a real deployment (V17: checksum + owner)
  • Transaction.from_deployment — deployer signs program ownership over the deployment ID
  • Deployment.from_program_unproven — dummy VK + certificate per function/record, mirroring the wasm SDK's buildDevnodeDeploymentTransaction; devnode-only (skips ~14 min of key synthesis for the AMM)
  • Execution/Fee.from_authorization_unproven — proofless transactions for devnodes, which skip proof verification

Pinned upstream + drift safety

codegen/regen-abi.sh regenerates the pinned ABI from the deployed program (fetches the import closure; that's the ABI-package change) and re-emits _generated.py; regen-openapi.sh does the API models. A live-tier drift test (check_compatibility(deployed, pinned)) fails our CI on redeploys instead of consumers. The pinned ABI comes from the live deployment — two structs newer than the TS package's snapshot.

Verification

  • 79 offline tests (golden vectors for blinding/keys/tick math; verb input orders asserted position-exact against the TS reference; stubs mirror real facade shapes — property outputs, child-first transitions, process registration)
  • Live read tier (-m live): 10 passed, 3 skipped (invite-gated route/ohlcv/balances skip with a clear message) — pools/slots against testnet, local pool-key derivation matches the indexer's keys, ABI drift test green against production
  • Devnode tier (-m devnode): 9/9 green in ~5 min (ALEO_DEVNODE_UNPROVEN=1) — deploys the vendored stack proofless, then drives pools, liquidity, swaps + claims, collect, and burn end-to-end with mappings asserted at every step; Python analog of the TS devnodeLifecycle.actions.e2e suite
  • pyright: 0 errors
  • Adversarial review applied; notable catches now regression-tested: snarkVM process registration for dynamic-dispatch token programs, root-transition output scoping, disabled-fee-tier rejection
  • Write-tier e2e (-m "live and slow", spends testnet funds) is credential-gated and not yet run — needs ALEO_E2E_PRIVATE_KEY/ALEO_E2E_API_KEY/ALEO_E2E_CONSUMER_ID

iamalwaysuncomfortable and others added 21 commits July 13, 2026 16:53
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
snarkVM validation is contextual: a program whose imports are not loaded
into the Process first is rejected (e.g. shield_swap_v3.aleo imports
test_shield_swap_multisig_core.aleo). generate_abi now takes an optional
(program_id, bytecode) list in topological order, loads each into the
Process, then disassembles the target — same pattern leo-disassembler
documents for multi-program validation. Python hook passes it through.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Pinned from the live testnet deployment (12 structs — two more than the
TS package's snapshot: SwapIterCfg/SwapIterState; Slot is still Q64).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live /pools responses carry undocumented token0_info/token1_info; the
client builds models tolerantly (filter to declared fields) and reads
token info via TokenDoc, which does document wrapper_program.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Poseidon8 blinding factor + blinded address with the 252-bit LE repacking
(Plaintext::Array::to_fields_raw emulation), matching amm-v3-tests
feat/q128 and the TS SDK's pinned vectors exactly. next_blinded_identity
scans used_blinded_addresses for the first free counter.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nges)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rd selection

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… ladder, swap verb

Reads decode mapping values via the generated classes (quoted/null node
variants handled); swap resolves params against live pool state, derives
the counter-scanned blinded identity, auto-selects the token record, and
returns a DexCall whose transact/delegate build a complete SwapHandle
(outputs pre-broadcast on transact; recovered from the confirmed
transaction on delegate).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…check)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
create_pool (fee-tier validated pre-submit), mint (spacing-aligned range,
generated MintPositionRequest encoder, slot-neighbor insert hints with the
lower-inserted-first upper-hint rule), increase/decrease/collect/burn with
PositionNFT auto-selection by pool.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Async covers reads, balances, and the two-transaction swap flow the
agent/MCP layer needs; async liquidity verbs are a documented follow-up —
the sync client has the full surface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…xports

- framework-neutral tool defs + dispatcher (writes run delegate)
- FastMCP server behind [mcp] extra (sync client in worker threads)
- live read tier: pools/slots + local-pool-key-vs-indexer invariant;
  drift test (pinned ABI vs deployed, via aleo.abi imports=) — both PASS
  against production; write tier gated on funded creds; devnode tier is an
  explicit module-level skip (record-version skew)
- pyright caught mint's missing generate_field_nonce import (default-nonce
  path); test added

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… shapes, root-transition scoping

Critical (all verbs were broken against a real facade; stubs had been
shaped to the bugs):
- register the DEX program, its static imports, and token wrapper programs
  with the snarkVM process before authorization (ensure_programs /
  register_program_sources; sync + async; imports= override now real)
- TransactionResult.outputs is a property — transact() crashed with
  TypeError after proving; now uses decoded()
- scope output harvesting to the ROOT transition (executions are
  child-first; first-field-output picked token-transfer outputs, stranding
  swap_id/position ids); delegate reads the confirmed tx's transitions
High:
- MCP: low-level Server with exact per-tool JSON schemas (FastMCP inference
  collapsed every tool to one opaque args object)
- agent: expected_out=0 no longer treated as absent; full liquidity tool
  set (increase/decrease/collect/burn/create_pool)
- get_balances no longer conflates the caller's private records into a
  third party's balance report
- get_swap_output accepts the SwapHandle (spec surface); uninitialized
  pools raise PoolNotInitializedError, not PoolNotFoundError
- create_pool rejects disabled fee tiers (value false, not just presence)
Cleanup:
- stubs now mirror real facade shapes (property outputs, decoded(),
  child-first transitions, process registration recorded)
- async client: dead imports dropped, shared _core helpers (no duplicated
  selection/normalization logic), honest surface docstring
- typed get_ohlcv (OhlcvDoc); untrack committed .pyc files; slow marker on
  the funded write-tier test

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Every read action against the live API + testnet chain: pools/tokens/
route/ohlcv/balances/swap-404 on the service side; pool/slot invariants,
initialization, absent-entry errors on the chain side; pool-key and
tick-key derivation parity against live state.

ApiClient/AsyncApiClient gain the signature challenge/verify handshake
(authenticate/set_token, bearer attachment). Route/OHLCV/balances are
additionally invite-gated per account upstream — those tests authenticate
with ALEO_E2E_PRIVATE_KEY when present and skip with the documented
reason otherwise.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Directory and PyPI distribution are now both shield-swap-sdk; the import
module stays aleo_shield_swap.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Process.deploy / Process.deployment_cost — synthesize and price a real
  deployment (V17 semantics: checksum + owner set).
- Transaction.from_deployment — deployer signs program ownership over the
  deployment ID.
- Deployment.from_program_unproven — dummy verifying key + certificate per
  function/record, mirroring the wasm SDK's
  buildDevnodeDeploymentTransaction; devnode-only (no key synthesis).
- Execution/Fee.from_authorization_unproven — proofless transactions for
  devnodes, which skip proof verification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Python analog of the TS suite's devnodeLifecycle.actions.e2e test: deploys
the vendored shield_swap_v3 stack (proofless dummy-VK deployments), runs
admin setup, then drives pools, liquidity, swaps + claims, collect, and
burn through the ShieldSwap verbs with mappings asserted at every step.
Two execution ladders: fully proven, or ALEO_DEVNODE_UNPROVEN=1 (fast,
~5 min end to end). Marked devnode; excluded from the default run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@iamalwaysuncomfortable iamalwaysuncomfortable changed the title aleo-shield-swap: typed Python client for the shield_swap AMM shield-swap-sdk: typed Python client for the shield_swap AMM Jul 14, 2026
…generator), bump SDKs to 0.2.0

- Distribution names only; import modules stay aleo / aleo_abi /
  aleo_shield_swap.
- aleo-sdk 0.2.0, aleo-contract-abi-generator 0.2.0, shield-swap-sdk 0.2.0
  (shield-swap now depends on aleo-sdk>=0.2).
- package_version() resolves aleo-sdk with a fallback to the pre-0.2 aleo
  dist so older installs keep reporting their real version.
- Install hints, CI pip installs, and docs swept to the new names.
- New README for the ABI package.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@iamalwaysuncomfortable
iamalwaysuncomfortable merged commit 684a179 into master Jul 14, 2026
16 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants