@splitmarkets/sdk
v0.3.0
Published
Oracle-free, non-liquidatable ETH options — viem client + optional React widget for Split (split.markets). Integrate leverage your users can't get liquidated out of.
Maintainers
Readme
@splitmarkets/sdk — leverage your users can't get liquidated out of
Oracle-free, non-liquidatable ETH options on Base & Arbitrum, as a drop-in SDK. Every option is physically backed 1:1, so a buyer's max loss is the premium they paid — no liquidation, no margin calls, no oracle to game. Long = bet ETH up. Short = bet ETH down. Add it to your app in ~5 lines.
┌──────────────┐ getMarkets / getQuote / buy / close ┌─────────────────────┐
│ YOUR APP │ ─────────────────────────────────────▶ │ Split on Base/Arb │
│ + user wallet│ @splitmarkets/sdk (this package) │ no-liquidation opts│
└──────────────┘ ◀───────────────────────────────────── └─────────────────────┘
users click "Buy" you earn referral fees on the volume you routeInstall
npm install @splitmarkets/sdk viem
# react is only needed if you use the optional <SplitTradeWidget/>Quickstart — the widget (the whole flow in one component)
import { SplitTradeWidget } from "@splitmarkets/sdk/widget";
// give it a connected viem wallet (wagmi: useWalletClient() + useAccount())
<SplitTradeWidget walletClient={walletClient} account={address} chain="base" />That's pick-side → pick-leverage → buy → position → close, fully working. Restyle to taste.
Gasless (recommended)
By default the widget trades gasless: the user signs once (a single typed-data signature to authorize
the USDC spend), there is no separate approve transaction, and no ETH for gas — the trade is
submitted for them. Their contracts (N) and any close proceeds settle in their Split trading account, a
smart account deterministically derived from their wallet, so the same wallet always maps to the same trading
account. One signature, no approval, gas-free.
import { SplitTradeWidget } from "@splitmarkets/sdk/widget";
// gasless is the default — same props as the connected flow
<SplitTradeWidget walletClient={walletClient} account={address} chain="base" mode="gasless" />Why one signature: gasless buys pull USDC with a single EIP-3009 TransferWithAuthorization (the one popup
the user sees) and batch the approve + buyN into one sponsored call — so there is no on-chain approve
step and the user spends no gas. Closes work the same way (sell N back, USDC settles in the trading account).
Per-chain availability mirrors trading: gasless settlement runs on Base (8453) and Arbitrum (42161).
Connected fallback
mode="connected" keeps the original model: the user trades from their own EOA, signing each step and
paying their own gas (the first trade per token is two txs — approve, then buyN/sellN; later trades
skip the approve once allowance is set). N lands directly in their EOA. Use this when the host app prefers
the user's wallet to hold positions, or where gasless isn't available.
<SplitTradeWidget walletClient={walletClient} account={address} chain="base" mode="connected" />Appearance
Theme the widget with the appearance prop. Anything you omit falls back to the built-in dark theme.
<SplitTradeWidget
walletClient={walletClient}
account={address}
chain="base"
appearance={{
accent: "#7c5cff", // buttons + selected tile
background: "#0e0e12",
text: "#e8e8ee",
radius: 16, // corner radius (px)
}}
/>The widget ships with zero-CSS inline styles, so it drops in unstyled-but-polished and you can override only the tokens you care about.
Or the data layer (5 functions, headless, any framework)
import { getMarkets, getQuote, buy, getPosition, close } from "@splitmarkets/sdk";
const [m] = await getMarkets("base", "long"); // live leverage tiles
const quote = await getQuote("base", "long", m.seriesId); // pricer-signed quote
await buy({ chain: "base", side: "long", seriesId: m.seriesId, qN: 0.1, walletClient, account });
const pos = await getPosition({ chain: "base", side: "long", seriesId: m.seriesId, account });
await close({ chain: "base", side: "long", seriesId: m.seriesId, qN: pos.qN, walletClient, account });How it works (read this once)
- Discover —
getMarketsreads the on-chain pools for the live series (strike, expiry, leverage). - Price —
getQuotefetches a pricer-signed quote from Split's hosted API (ask/bid + a signature). You can't forge it; only Split can sign it. - Trade —
buysendsapprove(USDC) → pool.buyN(seriesId, qN, maxCost, quote, signature)from the user's own wallet. The contract verifies the signature on-chain — a stale or tampered quote reverts. - Settle — physical settlement reads no price feed. The buyer's downside is capped at the premium;
there is no liquidation.
closesells back at the signed bid.
In gasless mode the same buyN/sellN run inside a single sponsored call from the user's Split trading account, gated by the same on-chain signature check — so the trust model is identical, just with one signature and no gas. In the connected fallback the user signs each step and pays their own gas. Either way Split never holds their keys.
Advanced architecture (click to expand) — what's public vs internal
┌────────────────────── YOUR APP ──────────────────────┐
│ <SplitTradeWidget/> OR getMarkets/getQuote/buy/... │
│ user's connected wallet (their keys, their gas) │
└───────────────┬───────────────────────────┬──────────┘
│ read pools (public RPC) │ GET /pool-quote
▼ ▼
┌── @splitmarkets/sdk (this package) ──┐ ┌── Split quote-api (Split-hosted) ──┐
│ viem client • ABIs • addresses │ │ prices the option (vol model) and │
│ NO secrets, NO keys — all public │ │ SIGNS the quote with Split's key │
└───────────────┬──────────────────────┘ └──────────────┬─────────────────────┘
│ approve + buyN/sellN(quote, signature) │ ▲ signer key NEVER
▼ │ │ leaves Split's server
┌──────────────── Split contracts (Base / Arbitrum) ───────┴───┴────────────────┐
│ buyN/sellN VERIFY the signature on-chain → mint/burn N to the user's wallet │
│ physically-settled, oracle-free, 1:1 collateralized → no liquidation possible │
└───────────────────────────────────────────────────────────────────────────────┘
PUBLIC (in this SDK): pool addresses, ABIs, the quote-api URL, public RPCs.
INTERNAL (never shipped): the quote-signer private key, the pricing model, the keeper,
Split's gasless facilitator/paymaster, deploy keys.Security: the only secret is the quote-signer key, and it stays on Split's server. Every trade you route is gated by a Split-signed quote (you can't forge prices), verified on-chain, and funded by the user's own wallet — so an integrator can't mint free options or move prices. Nothing in this package is sensitive.
You earn referral fees
Your integration earns a share of protocol fees on the volume your app routes. Email [email protected] with your integration's address to set your referral.
Verify it works (don't take our word for it)
npm test # unit: chain config, pool resolution, quote encoding (offline, deterministic)
npm run test:integration # live, read-only: getMarkets + getQuote hit the real pools & quote-apiUnit tests are hermetic (no network). Integration tests prove discovery + signed pricing work against production with zero funds and zero writes. End-to-end buy/close (needs a funded wallet) is covered by the runnable example in the repo.
Requirements
viem^2 (peer dep).react≥18 only for the optional widget.- A connected EVM wallet on Base (8453) or Arbitrum (42161).
For coding agents
Point your agent at **SKILL.md** in this package — it's a one-pass integration brief.
Support
[email protected] — referral setup, questions, or anything that doesn't work first try.
