Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

aquarius-sdk

The Python SDK for Aquarius — swaps, liquidity, and rewards on Stellar.

Status: 0.6.x — swaps and the liquidity lifecycle are complete and verified with real testnet transactions. Concentrated liquidity position management is available as a beta surface (the contracts are under an ongoing external audit). The API may still change before 1.0.

Swap through a specific pool

client.quote()/swap() route through the path-finding API. To pin a swap to one pool you trust — your own routing, API independence — quote and swap on the pool itself; the estimate comes from the router's on-chain estimate_swap:

pool = next(p for p in client.pools_for_pair(XLM, AQUA) if p.fee_bps == 10)

quote = pool.quote(XLM, AQUA, amount_in=100_0000000)   # no signer needed
receipt = quote.execute()                              # swaps in this pool only
# or: pool.swap(XLM, AQUA, amount_in=100_0000000, retries=3)

Exact input only — the router has no single-pool strict-receive; for exact output use client.quote(..., amount_out=...).

Concentrated liquidity positions (beta)

A position is the key (owner, tick_lower, tick_upper) on the pool contract — no NFTs, merged on re-deposit, at most 20 ranges per account. Quotes come from the contract's own estimators; execute/withdraw_position derive real slippage guards from them:

pool = next(p for p in aqua.pools_for_pair(XLM, AQUA) if p.type == "concentrated")

est = pool.estimate_position_deposit(
    {XLM: 10_0000000, AQUA: 100_0000000},
    price_range=("0.9", "1.1"),   # snaps to tick spacing; or tick_range=(lower, upper)
)
opened = est.execute(slippage=0.01)          # min-liquidity guard from the estimate

pool.position_ranges()                        # all of the signer's ranges
pool.position_range_status(est.tick_lower, est.tick_upper)  # in_range / below / above
pool.position_value(est.tick_lower, est.tick_upper)  # principal / fees / total, split
pool.claim_position_fees(est.tick_lower, est.tick_upper)
pool.withdraw_position(est.tick_lower, est.tick_upper)  # full close, auto-claims fees

tick_from_price, price_at_tick, and snap_tick are exported for range math — integer-exact and identical across both language packages.

pip install aquarius-sdk
from stellar_sdk import Keypair
from aquarius import AquariusClient, Asset, XLM, SlippageError

AQUA = Asset.classic("AQUA", "GBNZ...AQUA")

aqua = AquariusClient(network="mainnet", signer=Keypair.from_secret(secret))

# exact input: quote, inspect, execute
quote = aqua.quote(XLM, AQUA, amount_in=100_0000000, slippage=0.01)
receipt = quote.execute()

# exact output: pass amount_out instead — strict-receive throughout
quote = aqua.quote(XLM, AQUA, amount_out=500_0000000)

Provider fees

Pass the collector configuration with the quote. The SDK keeps the fee math exact, routes through the collector, and sends output to recipient without requiring that address to sign:

from aquarius import ProviderFeeConfig

provider_fee = ProviderFeeConfig(
    contract_id="C...",
    fee_fraction=30,   # 30 / fee_denominator=10_000 -> 0.3% of the output
    fee_denominator=10_000,
    recipient="G...",
)

quote = aqua.quote(
    XLM,
    AQUA,
    amount_in=100_0000000,
    slippage=0.005,
    provider_fee=provider_fee,
)
receipt = quote.execute()

Omit recipient to send output back to the signer through the collector's legacy swap methods. Use amount_out instead of amount_in for an exact-output provider swap.

Liquidity

pools = aqua.pools_for_pair(XLM, AQUA)          # discovered on-chain, sorted by type and fee
pool = pools[0]                                  # Pool(type="volatile", fee_bps=10, ...)

result = pool.deposit({XLM: 50_0000000, AQUA: 2500_0000000}, slippage=0.01)
print(result.shares)                             # pool share tokens minted

pool.pending_rewards()                           # accrued AQUA, in stroops
pool.claim_rewards()
pool.withdraw(result.shares, slippage=0.01)

aqua.positions()                                 # every pool where the signer holds shares

Deposit and withdrawal guards come from a simulation of the exact call, reduced by slippage — quoted-versus-executed drift is bounded the same way as for swaps. Reads (reserves(), pending_rewards(), pools_for_pair()) need no signer.

A withdraw or claim right after a deposit into a pool with rewards should first wait for the next ledger:

result = pool.deposit({XLM: 50_0000000, AQUA: 2500_0000000})
aqua.wait_for_next_ledger()                      # past the ledger that applied the deposit
pool.withdraw(result.shares)

Simulation runs on the latest ledger's state, but a transaction applies in a later one. The rewards contract rewrites the user's reward entry only when rewards accrued since the user's last checkpoint, so a withdraw simulated on the deposit's own ledger declares that entry read-only and fails on-chain once time has advanced. wait_for_next_ledger(ledger=None, timeout=30.0) waits until the RPC's latest ledger is past ledger — by default the ledger of the client's last on-chain transaction (confirmed or failed), or, if there is none, the current one — polling getHealth about once a second, and returns the latest ledger observed; it raises AquariusError on timeout. The SDK never waits on its own.

What the SDK handles for you

  • Routing — quotes come from the find-path API; the swap chain XDR is passed through untouched.
  • Transaction lifecycle — simulation, assembly, submission with congestion retries (same-hash resubmission with backoff), and confirmation polling.
  • Fixed authorization — every contract write and unsigned transaction uses client-built authorization trees. Preparation simulates in enforce mode, uses the response for resources, and preserves the exact operation and auth through restore and sequence retries. RPC-returned auth is never added, including when the supplied list is empty.
  • Archived state — if simulation reports expired ledger entries, the SDK restores them (one extra signed transaction) and retries automatically.
  • Typed errors — SlippageError (with requote()), PausedError (kill switches — not your bug), NoRouteError, UserRejectedError, TxTimeoutError.

Signers

A stellar-sdk Keypair works as-is. Custom signers provide public_key plus sign(tx_xdr) -> str returning the signed envelope XDR. Reads — quote() — need no signer at all.

Escape hatches

quote.build_transaction() returns the simulated, unsigned envelope XDR for external signing flows, with the same fixed authorization as execute(). client.api is the typed REST client.

client.contract_call(fn, *scvals) invokes the router raw and authorizes only that exact root call with source-account credentials. Raw calls that previously relied on simulation to discover nested permissions must now provide those permissions explicitly with auth=[...], or use the corresponding high-level method such as pool.deposit(). An incomplete tree fails during enforced simulation before the contract transaction is signed.

For example, an explicit raw reward claim mirrors the router and pool calls:

from stellar_sdk import scval
from aquarius.authorization import authorized_invocation, source_account_entry

user = scval.to_address(public_key)  # the transaction source account
args = [
    user,
    scval.to_vec([scval.to_address(token) for token in pool.tokens]),
    scval.to_bytes(pool.pool_hash),
]
entry = source_account_entry(authorized_invocation(
    aqua.network.router, "claim", args,
    [authorized_invocation(pool.address, "claim", [user])],
))
aqua.contract_call("claim", *args, auth=[entry])

Pass auth=[] when the call requires no authorization; the empty list remains empty after assembly. Reads and estimates can still use recording simulation, but their recorded auth never enters a transaction for signing. Archived-state restoration remains a separate footprint operation when a signer is available.

Infrastructure

Defaults point at the protocol's own endpoints: the mainnet RPC is https://soroban-rpc.aqua.network — the same node the Aquarius web app and backend use. Running your own infrastructure? Every endpoint is overridable:

aqua = AquariusClient(
    network="mainnet",
    rpc_url="https://your-rpc.example.com",
    horizon_url="https://your-horizon.example.com",
)

Fees

base_fee (default 100_000 stroops) is the inclusion fee bid. On top of it, every Soroban transaction declares a resource fee taken from simulation, raised by resource_fee_headroom (default 0.5, i.e. +50%; 0 disables):

aqua = AquariusClient(network="mainnet", signer=signer, resource_fee_headroom=0.3)

The declared resource fee is a cap: the network charges the non-refundable part plus the refundable part actually consumed (rent, events, return value) and refunds the rest, so headroom costs nothing unless it is used. The refundable part depends on ledger state at apply time, so the bare simulated fee can fall short and fail the transaction on-chain with INVOKE_HOST_FUNCTION_INSUFFICIENT_REFUNDABLE_FEE. The headroom is applied before signing (also to automatic restore transactions and to quote.build_transaction()), so wallets see and sign the final fee.

The source account must hold the full declared fee — inclusion fee plus resource fee plus headroom: it is debited before execution, and the unused part is refunded after apply. When spending the whole XLM balance, leave that margin. Re-preparing the XDR from quote.build_transaction() with stellar-sdk's own prepare_transaction re-simulates and replaces the resource fee, which drops the headroom.

Declared CPU instructions are a hard limit too: a transaction that needs more than it declares fails on-chain with INVOKE_HOST_FUNCTION_RESOURCE_LIMIT_EXCEEDED, and actual usage can exceed the simulated count by several percent. So the simulated instructions are raised by instruction_headroom (default 0.25, i.e. +25%; 0 disables), capped at the network's per-transaction limit (read from the network config and cached), and the resource fee by the compute fee of the added instructions — non-refundable, under 0.0001 XLM for a typical swap at current mainnet rates — before the fee headroom is applied on top. Restore transactions run no contract code and keep the simulated instructions.

aqua = AquariusClient(network="mainnet", signer=signer, instruction_headroom=0.4)

Amounts

All amounts are integers in token base units (stroops for classic assets: 1 token = 10^7).

Questions and integration help: Discord.

Release files for aquarius-sdk 0.6.0a2

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

Source distribution (sdist)

Source distribution for aquarius-sdk 0.6.0a2
File Size Uploaded
aquarius_sdk-0.6.0a2.tar.gz 59.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for aquarius-sdk 0.6.0a2
File Interpreter ABI Platform
aquarius_sdk-0.6.0a2-py3-none-any.whl Python 3 none any Details

Total release size: 103.0 kB

Release files / aquarius_sdk-0.6.0a2.tar.gz

Download URL aquarius_sdk-0.6.0a2.tar.gz
Size 59.7 kB
Tags Source
SHA-256 checksum
How to use checksums
64ed517bc3feaba92af24acd755dbfb4613110c6d561d8098fb26075805007d5
BLAKE2b-256 checksum
How to use checksums
fb51f087c6c07cd237b5327d30470c122e68668669535f65d96c5a63dd028033
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 25, 2026.

Transparency log

Release files / aquarius_sdk-0.6.0a2-py3-none-any.whl

Download URL aquarius_sdk-0.6.0a2-py3-none-any.whl
Size 43.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1065a48579a22804b6d479930855a2cfcc5a9fcc782ac0b440154740a64ec038
BLAKE2b-256 checksum
How to use checksums
a6ad76eb4c9ce8e4408aa017020d9ba58750241ae5f46693346c76ba48417df1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

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 Sep 25, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.6.0a2 This release

2 release files

0.5.1

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

0.0.1

2 release files

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