Sally
SALLY
Python SDK · v2.1.0

sally-defi-py-sdk

One typed SallyClient for the Sally CrossHybrid protocol on Base and BSC — best-route hybrid swaps, V2·V3·V4·Slipstream liquidity, 1e18 USD pricing, honeypot screening, locks and referral fees behind one beginner-friendly, AI-agent-ready API. Safe by default: it simulates every route on-chain, sets minOut from the simulation, then asserts the funds actually arrived. 100% on-chain — no Sally API, no subgraph, no backend in the data path.

pip install sally-defi-py-sdkimport sally_defiPython ≥ 3.12web3 ≥ 7.6MIT
Why

Raw web3.py, minus the foot-guns

Most Python DeFi code leans on untyped tuples, manual approvals, quotes that don't match execution, and no honeypot or sandwich protection. The SDK folds all of that into one safe call — the diligence is the default, not an opt-in.

Fully on-chain & decentralized. Every read — prices, routes, positions, balances — is a direct eth_call to the contracts. No proprietary API, no Graph Node / subgraph, no indexer. Point it at any RPC and the only thing you trust is the chain.
Safety in the hot path. Simulate-before-execute plus a post-trade balance-delta assertion — shipped in the call path, not just the test suite. Even mature SDKs skip the balance-delta check.
Typed end-to-end. pos.liquidity, plan.is_safe, quote.estimated_amount_out — dataclasses with autocomplete, all JSON-able. No tuple[7].
AI-agent-ready by construction. Every return serializes cleanly, so wiring Sally into LLM tools is mechanical. Async, MEV-protection and EIP-2612 permit are constructor/keyword args — not projects.
Honest about scope. Sally routes within its own protocol on Base + BSC — it is not a 100-source aggregator. The full, cited comparison (advantages and trade-offs) vs raw web3.py / uniswap-python / eth_defi / Ape / aggregator SDKs lives in docs/comparison.md.
Install

One package, light deps

Runtime dependencies are just the web3 stack — web3, eth-account, eth-abi, eth-utils, pydantic. Ships py.typed.

pip
pip install sally-defi-py-sdk
# or, with uv:
uv add sally-defi-py-sdk
Install vs import. Install as sally-defi-py-sdk, import as sally_defi — like pip install scikit-learnimport sklearn.
Requirements. Python ≥ 3.12 and web3 ≥ 7.6. Reads need only an RPC; writes need a signer.
Quickstart

Read in three lines

Point the client at any Base RPC and read prices, rates and quotes — no key required for reads.

read-only.py
from sally_defi import SallyClient
from sally_defi.constants import Base

sally = SallyClient("base", "https://mainnet.base.org")

sally.prices.usd(Base.USDC).as_float            # 1.0      (display / spot-mid)
sally.prices.usd_impact(Base.WETH).as_float     # 1703.19  (execution-aware)

quote = sally.swap.quote(Base.WETH, Base.USDC, 10**18)
quote.estimated_amount_out / 1e6                # 1703.45 USDC
Two prices, on purpose. prices.usd is the spot-mid display price; prices.usd_impact is the execution-aware price after slippage for a reference size. Same split as the on-chain getUsdPrice / getUsdPriceImpact pair.
Swapping

Safe by default

Inspect a plan first, then execute with the same vetting plus a received-balance assertion. Pass a private key, a mnemonic, the SALLY_PRIVATE_KEY env var — or no key at all (build-only).

swap.py
sally = SallyClient("base", RPC, private_key="0x…")   # or SALLY_PRIVATE_KEY env

# Inspect first: compares every route, simulates each, screens the output token.
plan = sally.swap.plan(Base.WETH, Base.USDC, 10**18)
plan.summary()      # 'SwapPlan(… out~1702924727 sim=… min=… impact=13bps steps=2 SAFE)'
plan.is_safe        # False if honeypot / excessive tax / >15% impact / bad route
plan.candidates     # every route considered, with simulated output

# Execute: same vetting, then send + balance-delta assertion.
receipt = sally.swap.execute(Base.WETH, Base.USDC, 10**18, slippage_bps=50)

# Native ETH in — pass the NATIVE sentinel; value is attached for you.
from sally_defi.token import NATIVE
sally.swap.execute(NATIVE, Base.USDC, 10**17, slippage_bps=50)
What execute() does, in order. approve (exact amount) → enumerate routes → integrity-check each so funds can never enter a wrong pool → simulate every route on-chain → pick best realized output → honeypot/tax preflight on the output token → set minOut from the simulation → send → assert received ≥ minOut. See docs/safety.md.
tune the guard rails
from sally_defi import SafetyConfig

sally = SallyClient(
  "base", RPC, private_key="0x…",
  safety=SafetyConfig(
    slippage_bps=30,
    tax_block_bps=3000,
    price_impact_block_bps=1000,
  ),
)
API surface

One object, every function

SallyClient exposes the whole protocol through feature namespaces. Raw contracts stay reachable via client.swap_contract / liquidity_contract / lens_contract / sidecar_contract.

client.pricesread

usd (display), usd_impact (execution), spot, weth, usd_liquidity, token_info — pricing at 1e18 precision.

client.swapsafe

quote (+ _v2/v3/v4/deep), candidates, plan, simulate_route, execute (+ build_only), token_info.

client.walletread

balances, balances_raw, total_usd — formatted or raw, with USD value.

client.liquiditywrite

add / increase / decrease / remove / lock / claim / harvest across V2·V3·V4·Slipstream, positions, pool state, dex registry.

client.feeswrite

Referral + batch fee reads & claims, swap_fee. See Referrals.

Single source of truth: all addresses + ABIs come from the bundled deployment.json — nothing hard-coded. Proxy-fallback aware: Lens views resolve through the swap proxy, Sidecar views through the liq proxy (see the contract architecture).

Liquidity

Add, lock, claim, harvest

Full LP lifecycle across V2·V3·V4 and Aerodrome Slipstream, plus time-locks and a unified fee claim — typed positions, no positional tuples.

liquidity.py
sally.liquidity.add_v2(dex_id=4, token_a=Base.WETH, token_b=Base.USDC,
                       amount_a=10**15, amount_b=2_000_000, lock_days=0)

sally.liquidity.positions_v3(wallet, 0, 10)     # list[V3Position]
sally.liquidity.locks(wallet)                   # list[Lock]
sally.liquidity.claimable_fees(npm, token_id)   # ClaimableFees

# add_v3 / add_v4 / add_slipstream, increase/decrease, remove_v2,
# lock/unlock, claim_fees_v3/v4(+_locked), mass_claim_fees, harvest_op
Referrals

Earn & claim referral fees

Every swap and liquidity action takes a referral address. Pass yours and the protocol accrues referral fees to it; read and claim them anytime.

referrals.py
sally.swap.execute(Base.WETH, Base.USDC, 10**18, referral="0xYourRef")

sally.fees.total_referral_owed("0xYourRef")     # total owed across tokens
sally.fees.referral_tokens("0xYourRef")         # per-token breakdown (paged)
sally.fees.claim_referral([Base.USDC, Base.WETH])   # claim selected tokens
Same fee share as the app. This is the same referral system that powers the referral page and the embed widget — your wallet earns a share of the protocol fee on every routed swap. Full guide: docs/referrals.md.
Advanced

Async · permit · private mempool

The hard stuff is a keyword argument. Concurrency, EIP-2612 permit instead of approve, and MEV-protected private relays.

advanced.py
# Async (concurrent quoting + simulation)
from sally_defi.aio import AsyncSallyClient
sally = AsyncSallyClient("base", RPC, private_key="0x…")
await sally.swap.execute(Base.WETH, Base.USDC, 10**18, slippage_bps=50)

# EIP-2612 permit instead of approve
sally.swap.execute(Base.USDC, Base.WETH, 10**8, approval="permit")

# Private mempool (MEV protection)
from sally_defi.constants import PrivateRelays
SallyClient("base", RPC, private_key="0x…",
            private_rpc_url=PrivateRelays.FLASHBOTS_FAST)

Full guide: docs/advanced.md (async client, EIP-2612 permit, Permit2, private mempool, BSC).

Bring your own wallet

The SDK never has to hold your key

Give a private key, a mnemonic, or no key at all — build a vetted, simulated, ABI-decoded unsigned transaction and sign it with your own web3.

external-signing.py
# mnemonic seed phrase
sally = SallyClient("base", RPC, mnemonic="word1 … word12", account_index=0)

# external signing — the SDK never holds your key
sally = SallyClient("base", RPC, address="0xYourWallet")   # knows sender, no key
build = sally.swap.execute(Base.WETH, Base.USDC, 10**18, build_only=True)
build.plan.summary()       # what happens to your wallet: out, min, impact, safe?
build.needs_approval       # sign build.approve_tx first if True
build.swap_tx              # unsigned tx → sign with your own account, broadcast

# preview ANY call: decoded fn + args + simulated result + revert reason
sally.preview(some_contract_fn).summary()
AI agents. Every call returns a JSON-able dataclass with a clear docstring, so wrapping Sally as LLM tools is trivial. See docs/ai-agents.md and examples/10_ai_agent_tools.py.
Reference

Docs, examples & deployment

The SDK targets the live v2.1.0 deployment — the same 0x7777 proxies documented in the contract reference. Same address on Base and BSC.

Live deployment (v2.1.0, Base = BSC)
SwapController (proxy)0x7777D0e2…87777LiquidityController (proxy)0x7777d7fF…e7777Treasury0x7777D7dC…887777

Full addresses + ABIs in the contract reference.

Examples & support

Runnable scripts in examples/. Questions, integrations, partnerships → support@sally.tools.

JavaScript / TypeScript SDK

The same safe-by-default client ships for TypeScript — typed returns, simulate-before-execute, one SallyClient for Node and the browser. npm install sally-defi-ts-sdk, built on ethers v6.

Read the JS/TS docs →

MIT-licensed and open source on GitHub ↗. See the contract reference, the security overview, or build a no-code embed widget.

Command palette

Jump to a page or run an action