sally-defi-ts-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 ethers / viem, minus the foot-guns
Most TypeScript DeFi code leans on untyped tuples, hand-rolled 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.isSafe, quote.estimatedAmountOut — classes with autocomplete, ships .d.ts. No tuple[7].One package, one peer dep
The only runtime dependency is the ethers v6 stack. Ships ESM and bundled .d.ts types — works in Node and modern bundlers/browsers.
npm install sally-defi-ts-sdk ethers # or pnpm add sally-defi-ts-sdk ethers yarn add sally-defi-ts-sdk ethers
type: module) and ships .d.ts — import { SallyClient } from "sally-defi-ts-sdk". The async client lives at the sally-defi-ts-sdk/aio subpath.≥ 18 and ethers ≥ 6 (a peer dep you install alongside). 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.
import { SallyClient, Base } from "sally-defi-ts-sdk";
const sally = new SallyClient("base", "https://mainnet.base.org");
(await sally.prices.usd(Base.USDC)).asFloat; // 1.0 (display / spot-mid)
(await sally.prices.usdImpact(Base.WETH)).asFloat; // 1703.19 (execution-aware)
const quote = await sally.swap.quote(Base.WETH, Base.USDC, 10n ** 18n);
Number(quote.estimatedAmountOut) / 1e6; // 1703.45 USDCprices.usd is the spot-mid display price; prices.usdImpact 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).
const sally = new SallyClient("base", RPC, { privateKey: "0x…" }); // or SALLY_PRIVATE_KEY env
// Inspect first: compares every route, simulates each, screens the output token.
const plan = await sally.swap.plan(Base.WETH, Base.USDC, 10n ** 18n);
plan.summary(); // 'SwapPlan(… out~1702924727 sim=… min=… impact=13bps steps=2 SAFE)'
plan.isSafe; // 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.
const receipt = await sally.swap.execute(Base.WETH, Base.USDC, 10n ** 18n, { slippageBps: 50 });
// Native ETH in — pass the NATIVE sentinel; value is attached for you.
import { NATIVE } from "sally-defi-ts-sdk";
await sally.swap.execute(NATIVE, Base.USDC, 10n ** 17n, { slippageBps: 50 });minOut from the simulation → send → assert received ≥ minOut. See docs/safety.md.import { SafetyConfig } from "sally-defi-ts-sdk";
const sally = new SallyClient("base", RPC, {
privateKey: "0x…",
safety: new SafetyConfig({
slippageBps: 30,
taxBlockBps: 3000,
priceImpactBlockBps: 1000,
}),
});One object, every function
SallyClient exposes the whole protocol through feature namespaces. Raw contracts stay reachable via client.swapContract / liquidityContract / lensContract / sidecarContract.
client.pricesreadusd (display), usdImpact (execution), spot, weth, usdLiquidity, tokenInfo — pricing at 1e18 precision.
client.swapsafequote (+ V2/V3/V4/Deep), candidates, plan, simulateRoute, execute (+ buildOnly), tokenInfo.
client.walletreadbalances, balancesRaw, totalUsd — 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, swapFee. 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.
await sally.liquidity.addV2(4, Base.WETH, Base.USDC, 10n ** 15n, 2_000_000n, { lockDays: 0 });
await sally.liquidity.positionsV3(wallet, 0, 10); // V3Position[]
await sally.liquidity.locks(wallet); // Lock[]
await sally.liquidity.claimableFees(npm, tokenId); // ClaimableFees
// addV3 / addV4 / addSlipstream, increase/decrease, removeV2,
// lock/unlock, claimFeesV3/V4(+Locked), massClaimFees, harvestOplockDays: 0) or time-locked. V3/V4/Slipstream positions are minted into the controller's lock vault, so they require lockDays ≥ 7 (the on-chain MIN_LOCK_DAYS).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.
await sally.swap.execute(Base.WETH, Base.USDC, 10n ** 18n, { referral: "0xYourRef" });
await sally.fees.totalReferralOwed("0xYourRef"); // total owed across tokens
await sally.fees.referralTokens("0xYourRef"); // per-token breakdown (paged)
await sally.fees.claimReferral([Base.USDC, Base.WETH]); // claim selected tokensAsync · permit · private mempool
The hard stuff is an option. Concurrency via the async client, EIP-2612 permit instead of approve, and MEV-protected private relays.
// Async (concurrent quoting + simulation)
import { AsyncSallyClient } from "sally-defi-ts-sdk/aio";
import { Base } from "sally-defi-ts-sdk";
const sally = new AsyncSallyClient("base", RPC, { privateKey: "0x…" });
await sally.swap.execute(Base.WETH, Base.USDC, 10n ** 18n, { slippageBps: 50 });
// EIP-2612 permit instead of approve
await sally.swap.execute(Base.USDC, Base.WETH, 10n ** 8n, { approval: "permit" });
// Private mempool (MEV protection)
import { PrivateRelays } from "sally-defi-ts-sdk";
new SallyClient("base", RPC, { privateKey: "0x…", privateRpcUrl: 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 ethers wallet.
// mnemonic seed phrase
const sally = new SallyClient("base", RPC, { mnemonic: "word1 … word12", accountIndex: 0 });
// external signing — the SDK never holds your key
const ext = new SallyClient("base", RPC, { address: "0xYourWallet" }); // knows sender, no key
const build = await ext.swap.execute(Base.WETH, Base.USDC, 10n ** 18n, { buildOnly: true });
build.plan.summary(); // what happens to your wallet: out, min, impact, safe?
build.needsApproval; // sign build.approveTx first if true
build.swapTx; // unsigned tx → sign with your own account, broadcast
// preview ANY call: decoded fn + args + simulated result + revert reason
(await ext.preview(fn, args)).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/ (run with npx tsx). Questions, integrations, partnerships → support@sally.tools.
The same safe-by-default client ships for Python — pip install sally-defi-py-sdk, one typed SallyClient, identical surface.
MIT-licensed and open source on GitHub ↗ · published on npm ↗. See the contract reference, the security overview, or build a no-code embed widget.