Skip to main content

shield-swap-sdk

Typed Python client for the shield_swap AMM on Aleo. Sits on top of the Aleo Python SDK's facade (aleo.Aleo): signer, record provider, proving configuration, and network all come from the client you bind — this package adds the DEX methods, the typed results, and the off-chain DEX API, nothing else.

from aleo import Aleo
from aleo_shield_swap import ShieldSwap

aleo = Aleo(Aleo.HTTPProvider("https://edge.provable.com/api"))
aleo.default_account = account
dex = ShieldSwap(aleo)

pools = dex.api.get_pools()                      # requires a live DEX API
handle = dex.swap(pool_key=pools[0].key,
                  token_in_id=pools[0].token0,
                  amount_in=10**9).delegate()    # broadcasts; spends funds
out = dex.claim_swap_output(handle).delegate()   # broadcasts; spends funds

Targets the deployed shield_swap.aleo stack on testnet. Integer amounts are raw native token units (the AMM does no decimal scaling); prices are Q128.128. swap() also accepts strings or Decimal values for amount_in and expected_out in token units, converting them internally with registry metadata. For example, amount_in="1.5" means 1.5 input tokens; amount_in=1500000 means 1,500,000 base units. Returned amounts remain in base units. Wrapped assets (ALEO/USAD/USDCx) route automatically through the swap/LP routers — fund them with underlying records (credits.aleo / stablecoin); you never handle wrapper records. mint stores an immutable withdrawal address on the position NFT (defaults to the recipient) and collect always pays it — decide the payout wallet at mint time. The off-chain API is selected per network — https://api.testnet.swap.shield.fi for testnet, https://api.swap.shield.fi for mainnet (override with SHIELD_SWAP_API_URL).

Install

pip install -e shield-swap-sdk                 # from the repo root
pip install -e "shield-swap-sdk[async]"        # + AsyncShieldSwap (httpx)
pip install -e "shield-swap-sdk[mcp]"          # + the MCP server

Requires aleo-sdk>=0.5.1 (this repo's SDK; imports as aleo) and Python 3.10+.

First swap example

The first-swap example creates and funds a testnet account, swaps USDCx for ETH, and claims the output using SDK calls directly. It uses an in-memory account and swap handle by default; ENABLE_JOURNAL optionally attaches the SDK journal. Releases containing the example support python -m aleo_shield_swap.examples.first_swap.swap.

Agents

AGENTS.md (generated from the SDK's docstrings — always current) is the one page an agent needs: the five-method lifecycle, the conversation pattern, and the building-block reference. It ships in the wheel:

pip install shield-swap-sdk
python -m aleo_shield_swap          # prints the agent guide

Claude Code users get it via the shield-swap skill (packaged under aleo_shield_swap/skills/, copy into .claude/skills/); any MCP client can run the same lifecycle through python -m aleo_shield_swap.mcp.

How calls work

Every read returns a value immediately. Every write returns a prepared DexCall — nothing touches the network until you invoke a terminal method:

call = dex.swap(pool_key=key, token_in_id=token, amount_in=10**9)

call.simulate()             # runs locally; no broadcast, no fee
call.transact(account)      # proves locally, broadcasts, pays the fee
call.delegate(account)      # proves via the delegated proving service

transact and delegate return the method's typed result (a SwapHandle, MintResult, ClaimResult, …) built from the transaction's root-transition outputs — not a bare transaction id. Local proving downloads SNARK parameters on first use and takes minutes for the larger entrypoints; delegate is the practical path and requires DPS credentials on the provider (api_key=, network_client.consumer_id).

Chain reads and writes live directly on ShieldSwap; the off-chain DEX API is namespaced under .api, so a call site always shows whether a value came from the chain or the service.

The method surface

Chain reads (node REST API):

Verb Returns
get_pool(pool_key) The pool struct from the pools mapping.
get_slot(pool_key) SlotView — current tick, sqrt price, liquidity, spacing.
get_swap_output(swap_id) The finalized swap outcome; raises SwapOutputNotFinalizedError until the finalize lands (and again after the claim consumes it).
get_swap_execution(swap_id) The fill receipt — executed height and per-hop amounts, gross/protocol/LP fee, post-trade price. Survives the claim; None until finalized.
get_pool_creator(pool_key) Who created the pool (None for pools that predate creator tracking).
is_pool_initialized(pool_key) Whether the pool exists on chain.
get_public_balances(programs) Each token program's on-chain balances mapping entry for an address (raw base units; absent reads as 0).
get_private_balances(programs) Summed unspent record amounts per token program (needs a registered record provider).
get_balances() Public + private balances in one shape, joined through the API's token registry.
derive_pool_key(token0, token1, fee) / derive_tick_key(pool_key, tick) Mapping keys derived locally — no network.

Writes (each returns a DexCall):

Verb What it does
swap(...) Phase one of the two-transaction private swap: locks the input record against a blinded identity. Returns a SwapHandle — persist it if the process might die before the claim.
claim_swap_output(handle) Phase two: claims the finalized output as a private record.
create_pool(...) Initializes a pool for a token pair + fee tier.
mint(...) Opens a position in a tick range; returns a MintResult with the position's token id.
increase_liquidity(...) / decrease_liquidity(...) Resizes a position (spends the position NFT record and returns a fresh one).
collect(...) Pays out tokens_owed as private records.
burn(...) Closes an emptied position and removes it from the positions mapping.
plan_rebalance(...) / rebalance_position(...) Close a position and mint its successor range in ONE transaction via shield_swap_rebalance_router.aleo (testnet). The plan quotes what comes back, what the new range needs, and funding vs refund per token; every amount is asserted at the execution price, so re-plan and resubmit if the pool moved.

A swap whose input was fully consumed (amount_remaining == 0) claims through the no-refund entrypoints (claim_swap_output_no_refund and the router's claim_to_*_no_refund), which never mint a zero-value refund record — claim_swap_output picks them automatically.

Quote before you swap: pass expected_out from dex.api.get_route(...) — without it a spot estimate is used, which ignores fees and price impact. On busy pools leave slippage headroom: prices move between quote and finalize, and a too-tight amount_out_min rejects safely at finalize. On-chain amounts are u128 base units; fees are microcredits. swap() converts string and Decimal token amounts internally; integer inputs remain base units.

Two liquidity behaviors worth knowing (both verified live): mint walks the pool's on-chain tick list to compute its insertion hints (find_tick_predecessor) — pass tick_*_hint= only if you know better. And when a pool side is wrapped, routed mint/increase amounts for that side must be exactly what the range consumes (the router burns the wrapper change record) — single-sided ranges make this deterministic; in-range wrapped amounts depend on the live price.

DEX API (dex.api, standalone as ApiClient): get_pools, get_tokens, get_route, get_ohlcv, get_positions, get_unclaimed, get_pool_stats_batch, get_liquidity_distribution, the compliance reads (get_compliance, get_token_compliance, get_pair_compliance — check before spending on a write), cookie-session management (get_session, refresh_session, list_sessions, revoke_session, logout, logout_all, get_ws_ticket), and referral issuance (referral_settings, list_referral_codes, generate_referral_codes). Route quoting, OHLCV, and the account views are auth-gated — call api.authenticate(address, sign) once (challenge/verify by signature, no funds required; the session rides as httpOnly cookies + a CSRF header and is short-lived — mint a durable ss_… token via create_api_token for anything long-running). Authentication is the whole gate — no invite is needed. A referral code is optional attribution: redeem_code credits the friend who shared it (once per account), and my_referral_code returns the code this account shares with others.

Privacy

The swap flow never puts your address on chain next to the output. swap derives a single-use blinded identity from the signer's view key (derive_blinding_factor / derive_blinded_address are exported for verification against the TS SDK's vectors); the claim proves knowledge of the blinding factor instead of revealing the owner.

Two conveniences trade secret material for service:

  • delegate sends the transaction authorization to the proving service — it can see the transaction's contents (not your private key). Prove locally with transact if that is unacceptable.
  • The hosted record scanner behind get_private_balances / aleo.records.register shares the account's view key with the scanning service, which can then see everything the account owns.

Async

AsyncShieldSwap / AsyncApiClient mirror the sync read, swap and claim surface method-for-method on aleo.AsyncAleo (install the [async] extra). The liquidity verbs (create_pool, mint, increase_liquidity, decrease_liquidity, collect, burn, rebalance_position) and the journal-bound helpers (from_profile, onboard, status, swap_many, collect_all, get_positions) are sync-only for now — the exact list is pinned by tests/test_async_parity.py:

from aleo_shield_swap import AsyncShieldSwap

dex = AsyncShieldSwap(async_aleo)
handle = await (await dex.swap(pool_key=key, token_in_id=token,
                               amount_in=10**9)).delegate()

Agent tools and MCP

shield_swap_tools() returns JSON-schema tool definitions for the whole method surface; dispatch_tool(dex, name, args) executes one. For MCP hosts, the [mcp] extra ships a stdio server over the same definitions:

ALEO_PRIVATE_KEY=APrivateKey1... python -m aleo_shield_swap.mcp

Omit ALEO_PRIVATE_KEY for a read-only server. See the module docstring for the full environment (ALEO_ENDPOINT, ALEO_NETWORK, DPS credentials).

Generated bindings

The contract surface is pinned, not hand-written: codegen/ holds the deployed program source, its ABI, and the DEX API's OpenAPI document. _generated.py (program structs + entrypoints via aleo.codegen) and _api_models.py (API response models) are built from those pins. When the deployed contract or API drifts, rerun codegen/regen-abi.sh / codegen/regen-openapi.sh and reconcile.

Tests

python -m pytest                        # hermetic tier — no network
python -m pytest -m live               # read-only against the REAL testnet + DEX API
python -m pytest -m "live and slow"    # spends real testnet funds (DPS proving)
ALEO_DEVNODE_UNPROVEN=1 \
python -m pytest -m devnode            # full AMM lifecycle on a local aleo-devnode

The devnode tier deploys the vendored shield_swap.aleo stack and drives pool creation, liquidity, swaps, and burn end-to-end, hermetically. It needs the aleo-devnode binary (ALEO_DEVNODE_BIN or on PATH) and skips otherwise. Deployments are proofless (dummy verifying keys — the devnode skips certificate verification); ALEO_DEVNODE_UNPROVEN=1 extends that to executions and is the fast path (~5 minutes). Without it, executions are fully proven locally: expect SNARK parameter downloads and key synthesis on first use.

Compliance

Swaps and liquidity actions prove in zero knowledge that the addresses taking part are not on a freezelist. Two kinds of list apply. Shield Swap's own list (shield_swap_freezelist.aleo) screens the signer when a swap output is claimed, and the signer, the position's recipient, and its withdrawal address when a position is minted, collected, or rebalanced. Each ARC-22 stablecoin that trades through a wrapper (USDCx, USAD) screens the signer when its records are wrapped and the receiving address when they are unwrapped, against the stablecoin's own list.

The SDK builds these Merkle exclusion proofs locally and passes them as private inputs of the transaction. The programs check the Merkle paths inside the transaction proof, and their finalize compares each reconstructed root with the root stored on chain. The network verifies that an address is absent from a list without seeing the address or the paths.

Proof generation is on by default. Leave compliance_proofs unset and call the methods as usual:

dex.mint(pool_key=key, tick_lower=lo, tick_upper=hi,
         amount0_desired=a0, amount1_desired=a1)
dex.claim_swap_output(handle)

Functions that take compliance proofs

Each bundle is one [MerkleProof; 2] input: two Merkle paths proving that one address is absent from one list. The counts are input slots on the entrypoint the call resolves to, including the placeholder slot of a wrapper that ignores proofs (credits).

Method Type Bundles Subjects, in input order
swap() SwapProofs 0 to 1 The signer, for a wrapped input token
claim_swap_output() ClaimProofs 1 to 3 Shield Swap signer, then the wrapped output receiver and the wrapped refund receiver when there is a refund
mint() MintProofs 3 to 5 Wrapped token senders, then Shield Swap signer, recipient, and withdrawal address
increase_liquidity() IncreaseProofs 0 to 2 Wrapped token senders
collect() CollectProofs 2 to 4 Shield Swap owner and withdrawal address, then wrapped token receivers
rebalance_position() RebalanceProofs 2 to 6 Funded wrapped token senders, wrapped token receivers, then Shield Swap owner and withdrawal address

Shield Swap slots use the AMM list. Wrapped-token slots use the underlying stablecoin's list, found through that program's imports (test_usdcx_stablecoin.aleo imports test_usdcx_freezelist.aleo). Senders are the signer. Claim receivers are the signer too; collect and rebalance receivers are the PositionNFT's withdrawal address. Within each group side 0 comes before side 1, a plain side has no slot, and a rebalance keeps its receiver slots even when a side has no refund. A multi-hop swap adds no slot for its intermediate hops. decrease_liquidity() and burn() take no proofs; swap_many() and collect_all() always let the SDK build them.

Automatic generation

Before each call the SDK works out the entrypoint, the slots, and the address each slot screens, then reads each list's current root (freeze_list_root[1u8]) once, so a mint's three Shield Swap bundles share one root. When a root changed since the last call it downloads the published tree from GET /programs/{id}/compliance/freeze-list, checks that the tree ends in that root, builds each witness, and checks that the witness reconstructs the root. Trees and witnesses stay in memory per client, list, and root, so while a root is unchanged nothing beyond the root is read. An empty list uses the empty-tree witness without a download.

A frozen address raises FrozenAddressError, and a missing root or a published tree that does not match the root raises FreezeListError, in each case before anything is proved or a fee is paid. A wrapped token whose underlying program the registry cannot name raises ValueError; passing token0_program= (or the matching argument) names it. The cache holds at most 16 lists and 256 addresses per list and does not survive a restart.

dex.freezelist is the resolver, an aleo.FreezeListProofs: dex.freezelist.exclusion_proof(address, program) returns one bundle for any address and list, dex.freezelist.resolve(contexts) fills a list of ComplianceProofContext values, and dex.freezelist.clear() drops the cached trees. dex.amm_freezelist_program names the AMM list for a non-standard deployment.

Optional proof inputs

compliance_proofs takes a ComplianceProofs value: the method's tuple of bundles in input order, or None. Each bundle is an encoded [MerkleProof; 2] string, a MerkleProofPair, or a (MerkleProofInput, MerkleProofInput) tuple; MerkleProofInput takes siblings as ints or "...field" literals. The count is checked against the route the call resolves to before anything runs, and a mismatch raises ValueError naming the slots the route expects. The same bundle may fill several slots when the list and the address match.

wrapper_proofs=, the earlier single-string argument, still fills every wrapped-token slot while the Shield Swap slots are built by the SDK. It cannot be combined with compliance_proofs. A supplied bundle bypasses the root read and the witness check, so its freshness is the caller's responsibility.

from aleo_shield_swap import MerkleProofInput, MerkleProofPair

dex.swap(pool_key=key, token_in_id=usdcx, amount_in=10**6,
         compliance_proofs=(sender_bundle,))                      # one wrapped input
dex.mint(pool_key=key, tick_lower=lo, tick_upper=hi,
         amount0_desired=a0, amount1_desired=a1,
         compliance_proofs=(signer_bundle, recipient_bundle, withdrawal_bundle))   # plain pool
pair = MerkleProofPair(MerkleProofInput(siblings=(0,) * 16, leaf_index=1),
                       MerkleProofInput(siblings=("0field",) * 16, leaf_index="1u32"))
dex.collect(pool_key=key, amount0_requested=x, amount1_requested=y,
            compliance_proofs=(pair, pair, usdcx_receiver_bundle))  # token0 wrapped

Metadata

Release files for shield-swap-sdk 0.7.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for shield-swap-sdk 0.7.0
File Size Uploaded Provenance
shield_swap_sdk-0.7.0.tar.gz 634.0 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for shield-swap-sdk 0.7.0
File Interpreter ABI Platform
shield_swap_sdk-0.7.0-py3-none-any.whl Python 3 none any Details

Total release size: 777.9 kB

Release files / shield_swap_sdk-0.7.0.tar.gz

Download URL shield_swap_sdk-0.7.0.tar.gz
Size 634.0 kB
Tags Source
SHA-256 checksum
How to use checksums
d0eb8e5360250dad919269efd98792f91ac7b5965f38c537ebfe7b5256ef6786
BLAKE2b-256 checksum
How to use checksums
e13a1b0082310840d0b8361d69d6b44dccfa669d9d539ba5c51cdacc5b2bd86f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Release files / shield_swap_sdk-0.7.0-py3-none-any.whl

Download URL shield_swap_sdk-0.7.0-py3-none-any.whl
Size 143.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
968b40ebd49f8ed2766a235d7212d2a1ccf933313af7cb6e4b5eafece2c93f67
BLAKE2b-256 checksum
How to use checksums
a8d7f7dc5d5e1957edd3f8d9bf98ad1119279cdb332dd145c76dfcd31093f9ca
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Security for shield-swap-sdk 0.7.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for shield_swap_sdk-0.7.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page