Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
bcd3507
fix(shield-swap): resolve the DEX API host per network
iamalwaysuncomfortable Aug 5, 2026
89e5a63
fix(shield-swap): walk the tick list on increase_liquidity; OHLCV tak…
iamalwaysuncomfortable Aug 5, 2026
18d9b0c
chore(shield-swap): regen OpenAPI per-network; pick up UsdcUsdQuote +…
iamalwaysuncomfortable Aug 5, 2026
5808b9d
feat(shield-swap): owned-position views with the contract's view math
iamalwaysuncomfortable Aug 5, 2026
1260939
feat(shield-swap): swap reserves its blinding counter and journals th…
iamalwaysuncomfortable Aug 5, 2026
cafda1f
fix(shield-swap): keep pyright clean on the reserved-counter narrowing
iamalwaysuncomfortable Aug 5, 2026
cf0017e
fix(shield-swap): the airdrop stage is testnet-only, say so on mainnet
iamalwaysuncomfortable Aug 5, 2026
c63ee5a
feat(shield-swap): from_profile takes network and endpoint
iamalwaysuncomfortable Aug 5, 2026
013f5d4
fix(shield-swap): the write tier never passed its own credentials
iamalwaysuncomfortable Aug 5, 2026
7e0831c
fix(shield-swap): authenticate the write-tier clients before auth-gat…
iamalwaysuncomfortable Aug 5, 2026
2887aa2
fix(shield-swap): quote failures stay errors; expanduser; pyright to …
iamalwaysuncomfortable Aug 5, 2026
12d5dcf
fix(shield-swap): quote the route in canonical amounts, not base units
iamalwaysuncomfortable Aug 5, 2026
7a60a52
fix(shield-swap): address self-review — async parity, build-time cost…
iamalwaysuncomfortable Aug 5, 2026
ccf0e49
fix(shield-swap): await the async record scan; both Copilot findings
iamalwaysuncomfortable Aug 6, 2026
3439226
fix(shield-swap): swap_many takes expected_out; share the position de…
iamalwaysuncomfortable Aug 6, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sdk-abi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

[package]
name = "aleo-abi"
version = "0.3.1"
version = "0.4.0"
edition = "2024"
license = "GPL-3.0-or-later"
description = "Python bindings for ABI generation from Aleo bytecode (via Leo's leo-abi crate)"
Expand Down
2 changes: 1 addition & 1 deletion sdk-abi/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "maturin"

[project]
name = "aleo-contract-abi-generator"
version = "0.3.1"
version = "0.4.0"
description = "Python bindings for ABI generation from Aleo bytecode"
readme = "README.md"
license = {text = "GPL-3.0-or-later"}
Expand Down
9 changes: 8 additions & 1 deletion sdk-abi/python/aleo_abi/_aleo_abi.pyi
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
def generate_abi(program_name: str, bytecode: str, network: str) -> str: ...
from typing import Optional, Sequence, Tuple

def generate_abi(
program_name: str,
bytecode: str,
network: str,
imports: Optional[Sequence[Tuple[str, str]]] = None,
) -> str: ...
def check_compatibility(candidate_abi_json: str, standard_abi_json: str) -> list[str]: ...
2 changes: 1 addition & 1 deletion sdk/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "aleo"
authors = ["Konstantin Pandl", "Mike Turner", "Roman Proskuryakov"]
version = "0.3.1"
version = "0.4.0"
description = "A Python sdk for zero-knowledge cryptography based on Aleo"
edition = "2021"
license = "GPL-3.0-or-later"
Expand Down
2 changes: 1 addition & 1 deletion sdk/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[project]
name = "aleo-sdk"
description = "Python SDK for building zero-knowledge apps and DeFi on the Aleo network"
version = "0.3.1"
version = "0.4.0"
readme = "Readme.md"
license = {file = "LICENSE.md"}
authors = [
Expand Down
35 changes: 30 additions & 5 deletions shield-swap-sdk/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,26 @@ report = dex.swap_many(pool_key=pools[0].key, token_in_id=pools[0].token0,
dex.collect_all() # any session, any time
```

### `from_profile(home: 'Any' = None) -> "'ShieldSwap'"`
### `from_profile(home: 'Any' = None, *, network: 'Optional[str]' = None, endpoint: 'Optional[str]' = None) -> "'ShieldSwap'"`

The client for the local participant profile (created on first use).

Wires endpoint, network, signer, and (when present) delegated-proving
credentials from ``$SHIELD_SWAP_HOME``/``~/.shield-swap``. Run
``onboard()`` next on a fresh profile.

*network* and *endpoint* apply only when the profile is being created —
an existing one keeps what it was created with, because its derived pool
keys and blinded identities are network-scoped and would not transfer.
Give each network its own home directory.

Args:
home: Profile directory; defaults to ``$SHIELD_SWAP_HOME`` or
``~/.shield-swap``.
network: ``"mainnet"`` or ``"testnet"`` for a NEW profile; defaults
to testnet.
endpoint: Node API origin for a NEW profile.

### `onboard(self, invite_code: 'Optional[str]' = None) -> 'OnboardReport'`

Register this profile end to end — safe to re-run any time.
Expand All @@ -54,7 +66,7 @@ The scan catches positions the journal never saw (account used from
another machine, journal lost); it needs a registered record
provider and is skipped silently without one.

### `swap_many(self, *, pool_key: 'str', token_in_id: 'str', amount_in: 'int', count: 'int', slippage_bps: 'int' = 50, record_wait_seconds: 'float' = 120.0, account: 'Any' = None) -> 'SwapBatchReport'`
### `swap_many(self, *, pool_key: 'str', token_in_id: 'str', amount_in: 'int', count: 'int', slippage_bps: 'int' = 50, expected_out: 'Optional[int]' = None, record_wait_seconds: 'float' = 120.0, account: 'Any' = None) -> 'SwapBatchReport'`

*count* private swaps of *amount_in* each, with reserved counters.

Expand All @@ -66,6 +78,11 @@ becomes claimable (it stays in ``still_pending``). A failed
broadcast burns its counter and the batch continues; failures are
reported, not raised. Requires ``from_profile()``.

*expected_out* (base units) skips the route quote. Without it the batch
quotes once and refuses rather than falling back to a spot estimate,
which ignores the pool fee and would revert every swap after paying for
its proof.

### `collect_all(self, account: 'Any' = None) -> 'CollectReport'`

Claim every finalized swap and collect owed fees on open positions.
Expand Down Expand Up @@ -323,7 +340,7 @@ fast when something is systematically wrong (e.g. wrong program).

### Chain methods

### `swap(self, *, pool_key: 'str', token_in_id: 'str', amount_in: 'int', slippage_bps: 'int' = 50, expected_out: 'Optional[int]' = None, sqrt_price_limit: 'Optional[int]' = None, deadline_offset_blocks: 'int' = 10000, nonce: 'Optional[int]' = None, identity: 'Optional[BlindedIdentity]' = None, token_in_program: 'Optional[str]' = None, token_record: 'Optional[str]' = None, wrapper_proofs: 'Optional[str]' = None, imports: 'Optional[dict[str, str]]' = None, account: 'Any' = None) -> 'DexCall[SwapHandle]'`
### `swap(self, *, pool_key: 'str', token_in_id: 'str', amount_in: 'int', slippage_bps: 'int' = 50, expected_out: 'Optional[int]' = None, sqrt_price_limit: 'Optional[int]' = None, deadline_offset_blocks: 'int' = 10000, nonce: 'Optional[int]' = None, identity: 'Optional[BlindedIdentity]' = None, token_in_program: 'Optional[str]' = None, token_record: 'Optional[str]' = None, wrapper_proofs: 'Optional[str]' = None, track: 'bool' = True, imports: 'Optional[dict[str, str]]' = None, account: 'Any' = None) -> 'DexCall[SwapHandle]'`

Request a private swap — phase one of the two-transaction flow.

Expand All @@ -339,8 +356,16 @@ process might die before the claim.

Quote first (``dex.api.get_route``) and pass *expected_out*: without
it a spot estimate is used, which ignores fees and price impact.
Pass *identity* (from journal-reserved counters) to skip the
on-chain probe — required for concurrent swaps. The default
**Building is not free with a journal.** The blinded address is a
transition input, so a counter is reserved *here*, not at the terminal
method — discarding the call, or only simulating, still spends it. That
reservation is what makes concurrent swaps safe: it serializes under a
file lock where the probe it replaces could hand two callers the same
counter. The handle is journaled once the broadcast is accepted, so a
crash before the claim keeps the blinding factor. ``track=False`` builds
on the racing probe instead; *identity* supplies your own.

The default
*deadline_offset_blocks* (~8h at ~3s blocks) absorbs delegated-
proving latency; a tight deadline aborts at finalize when proving
outlives it.
Expand Down
72 changes: 70 additions & 2 deletions shield-swap-sdk/codegen/amm_api.openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -461,7 +461,7 @@
"airdrop"
],
"summary": "POST /airdrop",
"description": "Delivers ~$10 worth each of ALEO, USDCx, and ETH to the given address as\nprivate records via `transfer_public_to_private`, spent from the\ntreasury's public balance in each target's underlying program. One claim\nper recipient address per 15 minutes (429 after that).\n\nProving each token takes ~15-30s, so this returns immediately with\n`status: \"running\"` + a `job_id`. The transfers run in a detached worker\n(capped concurrency, per-token timeout); poll `GET /airdrop/{job_id}` until\n`status == \"complete\"` to read per-token results.\n\nRequired env: `DEPLOYER_PRIVATE_KEY` (the treasury key whose public balances\nfund every airdrop; `AIRDROP_PRIVATE_KEY` honored as a fallback),\n`RPC_URLS` (first entry used).",
"description": "Delivers configured amounts of ALEO, USDCx, and ETH to the given address as\nprivate records via `transfer_public_to_private`, spent from the\ntreasury's public balance in each target's underlying program. One claim\nper recipient address per 15 minutes (429 after that).\n\nProving each token takes ~15-30s, so this returns immediately with\n`status: \"running\"` + a `job_id`. The transfers run in a detached worker\n(capped concurrency, per-token timeout); poll `GET /airdrop/{job_id}` until\n`status == \"complete\"` to read per-token results.\n\nRequired env: `DEPLOYER_PRIVATE_KEY` (the treasury key whose public balances\nfund every airdrop; `AIRDROP_PRIVATE_KEY` honored as a fallback),\n`RPC_URLS` (first entry used).",
"operationId": "airdrop",
"requestBody": {
"content": {
Expand Down Expand Up @@ -1848,6 +1848,15 @@
"type": "integer",
"format": "int64"
}
},
{
"name": "include_valuation",
"in": "query",
"description": "Include the server USDC/USD valuation",
"required": false,
"schema": {
"type": "boolean"
}
}
],
"responses": {
Expand Down Expand Up @@ -3170,6 +3179,15 @@
"schema": {
"type": "string"
}
},
{
"name": "pool_key",
"in": "query",
"description": "Optional pool key. The route uses only this pool, as a single hop, with no fallback.",
"required": false,
"schema": {
"type": "string"
}
}
],
"responses": {
Expand Down Expand Up @@ -5266,6 +5284,16 @@
},
"pagination": {
"$ref": "#/components/schemas/PaginationMeta"
},
"valuation": {
"oneOf": [
{
"type": "null"
},
{
"$ref": "#/components/schemas/UsdcUsdQuote"
}
]
}
}
},
Expand Down Expand Up @@ -7237,6 +7265,46 @@
}
}
},
"UsdcUsdQuote": {
"type": "object",
"required": [
"price",
"decimals",
"publishTime",
"validUntil",
"serverTime",
"source",
"corroborated"
],
"properties": {
"corroborated": {
"type": "boolean"
},
"decimals": {
"type": "integer",
"format": "int32",
"minimum": 0
},
"price": {
"type": "string"
},
"publishTime": {
"type": "integer",
"format": "int64"
},
"serverTime": {
"type": "integer",
"format": "int64"
},
"source": {
"type": "string"
},
"validUntil": {
"type": "integer",
"format": "int64"
}
}
},
"VerifyRequestDoc": {
"type": "object",
"required": [
Expand Down Expand Up @@ -7325,7 +7393,7 @@
},
{
"name": "airdrop",
"description": "Faucet \u2014 sends ~$10 each of ALEO, USDCx, and ETH to a user address as private records, once per address per 15 min"
"description": "Faucet \u2014 sends ALEO, USDCx, and ETH to a user address as private records, once per address per 15 min"
},
{
"name": "unclaimed",
Expand Down
13 changes: 10 additions & 3 deletions shield-swap-sdk/codegen/regen-openapi.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@
# Refetch the DEX API's OpenAPI spec and regenerate the response models.
set -euo pipefail
cd "$(dirname "$0")"
# Staging serves the migrated shield_swap.aleo stack; the old dev host still
# serves the pre-migration deployment — never mix the two.
BASE="${1:-https://amm-api-staging.dev.provable.com}"
# The API is deployed per network on separate hosts; pass the network (or a
# full base URL) as $1. Never point this at amm-api.dev.provable.com — that
# host indexes the pre-migration shield_swap_v3.aleo.
TARGET="${1:-testnet}"
case "$TARGET" in
mainnet) BASE="https://api.swap.shield.fi" ;;
testnet) BASE="https://api.testnet.swap.shield.fi" ;;
*) BASE="$TARGET" ;;
esac
echo "fetching spec from $BASE"
PYTHON="${PYTHON:-python3}"
curl -sf "${BASE}/openapi.json" | "$PYTHON" -m json.tool > amm_api.openapi.json
"$PYTHON" -m datamodel_code_generator \
Expand Down
2 changes: 1 addition & 1 deletion shield-swap-sdk/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "shield-swap-sdk"
version = "0.3.1"
version = "0.4.0"
description = "Python SDK for the shield swap AMM Dex on Aleo"
readme = "README.md"
requires-python = ">=3.10"
Expand Down
35 changes: 30 additions & 5 deletions shield-swap-sdk/python/aleo_shield_swap/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,26 @@ report = dex.swap_many(pool_key=pools[0].key, token_in_id=pools[0].token0,
dex.collect_all() # any session, any time
```

### `from_profile(home: 'Any' = None) -> "'ShieldSwap'"`
### `from_profile(home: 'Any' = None, *, network: 'Optional[str]' = None, endpoint: 'Optional[str]' = None) -> "'ShieldSwap'"`

The client for the local participant profile (created on first use).

Wires endpoint, network, signer, and (when present) delegated-proving
credentials from ``$SHIELD_SWAP_HOME``/``~/.shield-swap``. Run
``onboard()`` next on a fresh profile.

*network* and *endpoint* apply only when the profile is being created —
an existing one keeps what it was created with, because its derived pool
keys and blinded identities are network-scoped and would not transfer.
Give each network its own home directory.

Args:
home: Profile directory; defaults to ``$SHIELD_SWAP_HOME`` or
``~/.shield-swap``.
network: ``"mainnet"`` or ``"testnet"`` for a NEW profile; defaults
to testnet.
endpoint: Node API origin for a NEW profile.

### `onboard(self, invite_code: 'Optional[str]' = None) -> 'OnboardReport'`

Register this profile end to end — safe to re-run any time.
Expand All @@ -54,7 +66,7 @@ The scan catches positions the journal never saw (account used from
another machine, journal lost); it needs a registered record
provider and is skipped silently without one.

### `swap_many(self, *, pool_key: 'str', token_in_id: 'str', amount_in: 'int', count: 'int', slippage_bps: 'int' = 50, record_wait_seconds: 'float' = 120.0, account: 'Any' = None) -> 'SwapBatchReport'`
### `swap_many(self, *, pool_key: 'str', token_in_id: 'str', amount_in: 'int', count: 'int', slippage_bps: 'int' = 50, expected_out: 'Optional[int]' = None, record_wait_seconds: 'float' = 120.0, account: 'Any' = None) -> 'SwapBatchReport'`

*count* private swaps of *amount_in* each, with reserved counters.

Expand All @@ -66,6 +78,11 @@ becomes claimable (it stays in ``still_pending``). A failed
broadcast burns its counter and the batch continues; failures are
reported, not raised. Requires ``from_profile()``.

*expected_out* (base units) skips the route quote. Without it the batch
quotes once and refuses rather than falling back to a spot estimate,
which ignores the pool fee and would revert every swap after paying for
its proof.

### `collect_all(self, account: 'Any' = None) -> 'CollectReport'`

Claim every finalized swap and collect owed fees on open positions.
Expand Down Expand Up @@ -323,7 +340,7 @@ fast when something is systematically wrong (e.g. wrong program).

### Chain methods

### `swap(self, *, pool_key: 'str', token_in_id: 'str', amount_in: 'int', slippage_bps: 'int' = 50, expected_out: 'Optional[int]' = None, sqrt_price_limit: 'Optional[int]' = None, deadline_offset_blocks: 'int' = 10000, nonce: 'Optional[int]' = None, identity: 'Optional[BlindedIdentity]' = None, token_in_program: 'Optional[str]' = None, token_record: 'Optional[str]' = None, wrapper_proofs: 'Optional[str]' = None, imports: 'Optional[dict[str, str]]' = None, account: 'Any' = None) -> 'DexCall[SwapHandle]'`
### `swap(self, *, pool_key: 'str', token_in_id: 'str', amount_in: 'int', slippage_bps: 'int' = 50, expected_out: 'Optional[int]' = None, sqrt_price_limit: 'Optional[int]' = None, deadline_offset_blocks: 'int' = 10000, nonce: 'Optional[int]' = None, identity: 'Optional[BlindedIdentity]' = None, token_in_program: 'Optional[str]' = None, token_record: 'Optional[str]' = None, wrapper_proofs: 'Optional[str]' = None, track: 'bool' = True, imports: 'Optional[dict[str, str]]' = None, account: 'Any' = None) -> 'DexCall[SwapHandle]'`

Request a private swap — phase one of the two-transaction flow.

Expand All @@ -339,8 +356,16 @@ process might die before the claim.

Quote first (``dex.api.get_route``) and pass *expected_out*: without
it a spot estimate is used, which ignores fees and price impact.
Pass *identity* (from journal-reserved counters) to skip the
on-chain probe — required for concurrent swaps. The default
**Building is not free with a journal.** The blinded address is a
transition input, so a counter is reserved *here*, not at the terminal
method — discarding the call, or only simulating, still spends it. That
reservation is what makes concurrent swaps safe: it serializes under a
file lock where the probe it replaces could hand two callers the same
counter. The handle is journaled once the broadcast is accepted, so a
crash before the claim keeps the blinding factor. ``track=False`` builds
on the racing probe instead; *identity* supplies your own.

The default
*deadline_offset_blocks* (~8h at ~3s blocks) absorbs delegated-
proving latency; a tight deadline aborts at finalize when proving
outlives it.
Expand Down
5 changes: 4 additions & 1 deletion shield-swap-sdk/python/aleo_shield_swap/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
from .async_client import AsyncShieldSwap as AsyncShieldSwap
from .api import ApiClient as ApiClient, AsyncApiClient as AsyncApiClient
from .types import (
OwnedPosition as OwnedPosition,
OwnedPositionState as OwnedPositionState,
ClaimResult as ClaimResult,
CollectReport as CollectReport,
MintResult as MintResult,
Expand Down Expand Up @@ -70,7 +72,7 @@ def agent_guide() -> str:
return files(__name__).joinpath("AGENTS.md").read_text()


__version__ = "0.3.1"
__version__ = "0.4.0"

__all__ = [
"ShieldSwap", "AsyncShieldSwap", "ApiClient", "AsyncApiClient",
Expand All @@ -83,6 +85,7 @@ def agent_guide() -> str:
"NotAuthenticatedError", "NotRedeemedError", "NotFundedError",
"AirdropPendingError", "AirdropRateLimitedError",
"CredentialsMissingError",
"OwnedPosition", "OwnedPositionState",
"Profile", "Journal", "REGISTRATION_STAGES",
"OnboardReport", "StageOutcome", "SessionStatus", "PositionView",
"SwapBatchReport", "CollectReport", "blinded_identity_at",
Expand Down
12 changes: 12 additions & 0 deletions shield-swap-sdk/python/aleo_shield_swap/_api_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -765,6 +765,17 @@ class TokenResponseDoc:
data: TokenDoc


@dataclass
class UsdcUsdQuote:
corroborated: bool
decimals: int
price: str
publishTime: int
serverTime: int
source: str
validUntil: int


@dataclass
class VerifyRequestDoc:
address: str
Expand Down Expand Up @@ -1039,6 +1050,7 @@ class LiveCompatibility:
class PoolListResponseDoc:
data: list[PoolResponseDoc]
pagination: PaginationMeta
valuation: UsdcUsdQuote | None = None


@dataclass
Expand Down
Loading
Loading