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.
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.
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.pos.liquidity, plan.is_safe, quote.estimated_amount_out — dataclasses with autocomplete, all JSON-able. No tuple[7].One package, light deps
Runtime dependencies are just the web3 stack — web3, eth-account, eth-abi, eth-utils, pydantic. Ships py.typed.
pip install sally-defi-py-sdk # or, with uv: uv add sally-defi-py-sdk
sally-defi-py-sdk, import as sally_defi — like pip install scikit-learn → import sklearn.≥ 3.12 and web3 ≥ 7.6. Reads need only an RPC; writes need a signer.Read in three lines
Point the client at any Base RPC and read prices, rates and quotes — no key required for reads.
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 USDCprices.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.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).
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)minOut from the simulation → send → assert received ≥ minOut. See docs/safety.md.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,
),
)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.pricesreadusd (display), usd_impact (execution), spot, weth, usd_liquidity, token_info — pricing at 1e18 precision.
client.swapsafequote (+ _v2/v3/v4/deep), candidates, plan, simulate_route, execute (+ build_only), token_info.
client.walletreadbalances, balances_raw, total_usd — formatted or raw, with USD value.
client.liquiditywriteadd / increase / decrease / remove / lock / claim / harvest across V2·V3·V4·Slipstream, positions, pool state, dex registry.
client.feeswriteReferral + 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).
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.
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_opEarn & 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.
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 tokensAsync · permit · private mempool
The hard stuff is a keyword argument. Concurrency, EIP-2612 permit instead of approve, and MEV-protected private relays.
# 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).
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.
# 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()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.
0x7777D0e2…87777LiquidityController (proxy)0x7777d7fF…e7777Treasury0x7777D7dC…887777Full addresses + ABIs in the contract reference.
Runnable scripts in examples/. Questions, integrations, partnerships → support@sally.tools.
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.
MIT-licensed and open source on GitHub ↗. See the contract reference, the security overview, or build a no-code embed widget.