@splitmarkets/sdk 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +171 -0
- package/SKILL.md +73 -0
- package/dist/chunk-MZEPXYHI.js +758 -0
- package/dist/index.d.ts +527 -0
- package/dist/index.js +1 -0
- package/dist/widget.d.ts +43 -0
- package/dist/widget.js +272 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# @splitmarkets/sdk — leverage your users can't get liquidated out of
|
|
2
|
+
|
|
3
|
+
Oracle-free, **non-liquidatable** ETH options on Base & Arbitrum, as a drop-in SDK. Every option is
|
|
4
|
+
physically backed 1:1, so a buyer's **max loss is the premium they paid** — no liquidation, no margin calls,
|
|
5
|
+
no oracle to game. Long = bet ETH up. Short = bet ETH down. Add it to your app in ~5 lines.
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
┌──────────────┐ getMarkets / getQuote / buy / close ┌─────────────────────┐
|
|
9
|
+
│ YOUR APP │ ─────────────────────────────────────▶ │ Split on Base/Arb │
|
|
10
|
+
│ + user wallet│ @splitmarkets/sdk (this package) │ no-liquidation opts│
|
|
11
|
+
└──────────────┘ ◀───────────────────────────────────── └─────────────────────┘
|
|
12
|
+
users click "Buy" you earn referral fees on the volume you route
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install @splitmarkets/sdk viem
|
|
19
|
+
# react is only needed if you use the optional <SplitTradeWidget/>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Quickstart — the widget (the whole flow in one component)
|
|
23
|
+
|
|
24
|
+
```tsx
|
|
25
|
+
import { SplitTradeWidget } from "@splitmarkets/sdk/widget";
|
|
26
|
+
|
|
27
|
+
// give it a connected viem wallet (wagmi: useWalletClient() + useAccount())
|
|
28
|
+
<SplitTradeWidget walletClient={walletClient} account={address} chain="base" />
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
That's pick-side → pick-leverage → buy → position → close, fully working. Restyle to taste.
|
|
32
|
+
|
|
33
|
+
## Gasless (recommended)
|
|
34
|
+
|
|
35
|
+
By default the widget trades **gasless**: the user signs **once** (a single typed-data signature to authorize
|
|
36
|
+
the USDC spend), there is **no separate `approve` transaction**, and **no ETH for gas** — the trade is
|
|
37
|
+
submitted for them. Their contracts (N) and any close proceeds settle in their **Split trading account**, a
|
|
38
|
+
smart account deterministically derived from their wallet, so the same wallet always maps to the same trading
|
|
39
|
+
account. One signature, no approval, gas-free.
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { SplitTradeWidget } from "@splitmarkets/sdk/widget";
|
|
43
|
+
|
|
44
|
+
// gasless is the default — same props as the connected flow
|
|
45
|
+
<SplitTradeWidget walletClient={walletClient} account={address} chain="base" mode="gasless" />
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Why one signature: gasless buys pull USDC with a single EIP-3009 `TransferWithAuthorization` (the one popup
|
|
49
|
+
the user sees) and batch the approve + `buyN` into one sponsored call — so there is no on-chain `approve`
|
|
50
|
+
step and the user spends no gas. Closes work the same way (sell N back, USDC settles in the trading account).
|
|
51
|
+
|
|
52
|
+
> Per-chain availability mirrors trading: gasless settlement runs on Base (8453) and Arbitrum (42161).
|
|
53
|
+
|
|
54
|
+
### Connected fallback
|
|
55
|
+
|
|
56
|
+
`mode="connected"` keeps the original model: the user trades from their **own EOA**, signing each step and
|
|
57
|
+
paying their own gas (the first trade per token is two txs — `approve`, then `buyN`/`sellN`; later trades
|
|
58
|
+
skip the approve once allowance is set). N lands directly in their EOA. Use this when the host app prefers
|
|
59
|
+
the user's wallet to hold positions, or where gasless isn't available.
|
|
60
|
+
|
|
61
|
+
```tsx
|
|
62
|
+
<SplitTradeWidget walletClient={walletClient} account={address} chain="base" mode="connected" />
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Appearance
|
|
66
|
+
|
|
67
|
+
Theme the widget with the `appearance` prop. Anything you omit falls back to the built-in dark theme.
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
<SplitTradeWidget
|
|
71
|
+
walletClient={walletClient}
|
|
72
|
+
account={address}
|
|
73
|
+
chain="base"
|
|
74
|
+
appearance={{
|
|
75
|
+
accent: "#7c5cff", // buttons + selected tile
|
|
76
|
+
background: "#0e0e12",
|
|
77
|
+
text: "#e8e8ee",
|
|
78
|
+
radius: 16, // corner radius (px)
|
|
79
|
+
}}
|
|
80
|
+
/>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The widget ships with zero-CSS inline styles, so it drops in unstyled-but-polished and you can override only
|
|
84
|
+
the tokens you care about.
|
|
85
|
+
|
|
86
|
+
## Or the data layer (5 functions, headless, any framework)
|
|
87
|
+
|
|
88
|
+
```ts
|
|
89
|
+
import { getMarkets, getQuote, buy, getPosition, close } from "@splitmarkets/sdk";
|
|
90
|
+
|
|
91
|
+
const [m] = await getMarkets("base", "long"); // live leverage tiles
|
|
92
|
+
const quote = await getQuote("base", "long", m.seriesId); // pricer-signed quote
|
|
93
|
+
await buy({ chain: "base", side: "long", seriesId: m.seriesId, qN: 0.1, walletClient, account });
|
|
94
|
+
const pos = await getPosition({ chain: "base", side: "long", seriesId: m.seriesId, account });
|
|
95
|
+
await close({ chain: "base", side: "long", seriesId: m.seriesId, qN: pos.qN, walletClient, account });
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## How it works (read this once)
|
|
99
|
+
|
|
100
|
+
1. **Discover** — `getMarkets` reads the on-chain pools for the live series (strike, expiry, leverage).
|
|
101
|
+
2. **Price** — `getQuote` fetches a **pricer-signed** quote from Split's hosted API (ask/bid + a signature).
|
|
102
|
+
You can't forge it; only Split can sign it.
|
|
103
|
+
3. **Trade** — `buy` sends `approve(USDC) → pool.buyN(seriesId, qN, maxCost, quote, signature)` from the
|
|
104
|
+
user's **own wallet**. The contract **verifies the signature on-chain** — a stale or tampered quote reverts.
|
|
105
|
+
4. **Settle** — physical settlement reads **no price feed**. The buyer's downside is capped at the premium;
|
|
106
|
+
there is no liquidation. `close` sells back at the signed bid.
|
|
107
|
+
|
|
108
|
+
In **gasless** mode the same buyN/sellN run inside a single sponsored call from the user's Split trading
|
|
109
|
+
account, gated by the same on-chain signature check — so the trust model is identical, just with one
|
|
110
|
+
signature and no gas. In the **connected** fallback the user signs each step and pays their own gas. Either
|
|
111
|
+
way Split never holds their keys.
|
|
112
|
+
|
|
113
|
+
**Advanced architecture (click to expand) — what's public vs internal**
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
┌────────────────────── YOUR APP ──────────────────────┐
|
|
117
|
+
│ <SplitTradeWidget/> OR getMarkets/getQuote/buy/... │
|
|
118
|
+
│ user's connected wallet (their keys, their gas) │
|
|
119
|
+
└───────────────┬───────────────────────────┬──────────┘
|
|
120
|
+
│ read pools (public RPC) │ GET /pool-quote
|
|
121
|
+
▼ ▼
|
|
122
|
+
┌── @splitmarkets/sdk (this package) ──┐ ┌── Split quote-api (Split-hosted) ──┐
|
|
123
|
+
│ viem client • ABIs • addresses │ │ prices the option (vol model) and │
|
|
124
|
+
│ NO secrets, NO keys — all public │ │ SIGNS the quote with Split's key │
|
|
125
|
+
└───────────────┬──────────────────────┘ └──────────────┬─────────────────────┘
|
|
126
|
+
│ approve + buyN/sellN(quote, signature) │ ▲ signer key NEVER
|
|
127
|
+
▼ │ │ leaves Split's server
|
|
128
|
+
┌──────────────── Split contracts (Base / Arbitrum) ───────┴───┴────────────────┐
|
|
129
|
+
│ buyN/sellN VERIFY the signature on-chain → mint/burn N to the user's wallet │
|
|
130
|
+
│ physically-settled, oracle-free, 1:1 collateralized → no liquidation possible │
|
|
131
|
+
└───────────────────────────────────────────────────────────────────────────────┘
|
|
132
|
+
|
|
133
|
+
PUBLIC (in this SDK): pool addresses, ABIs, the quote-api URL, public RPCs.
|
|
134
|
+
INTERNAL (never shipped): the quote-signer private key, the pricing model, the keeper,
|
|
135
|
+
Split's gasless facilitator/paymaster, deploy keys.
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Security:** the only secret is the quote-signer key, and it stays on Split's server. Every trade you route
|
|
139
|
+
is gated by a Split-signed quote (you can't forge prices), verified on-chain, and funded by the user's own
|
|
140
|
+
wallet — so an integrator can't mint free options or move prices. Nothing in this package is sensitive.
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
## You earn referral fees
|
|
145
|
+
|
|
146
|
+
Your integration earns a share of protocol fees on the volume your app routes. Email
|
|
147
|
+
**[mayur@vistara.dev](mailto:mayur@vistara.dev)** with your integration's address to set your referral.
|
|
148
|
+
|
|
149
|
+
## Verify it works (don't take our word for it)
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npm test # unit: chain config, pool resolution, quote encoding (offline, deterministic)
|
|
153
|
+
npm run test:integration # live, read-only: getMarkets + getQuote hit the real pools & quote-api
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Unit tests are hermetic (no network). Integration tests prove discovery + signed pricing work against
|
|
157
|
+
production with zero funds and zero writes. End-to-end buy/close (needs a funded wallet) is covered by the
|
|
158
|
+
runnable example in the repo.
|
|
159
|
+
|
|
160
|
+
## Requirements
|
|
161
|
+
|
|
162
|
+
- `viem` ^2 (peer dep). `react` ≥18 only for the optional widget.
|
|
163
|
+
- A connected EVM wallet on Base (8453) or Arbitrum (42161).
|
|
164
|
+
|
|
165
|
+
## For coding agents
|
|
166
|
+
|
|
167
|
+
Point your agent at `**SKILL.md**` in this package — it's a one-pass integration brief.
|
|
168
|
+
|
|
169
|
+
## Support
|
|
170
|
+
|
|
171
|
+
[mayur@vistara.dev](mailto:mayur@vistara.dev) — referral setup, questions, or anything that doesn't work first try.
|
package/SKILL.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: split-options
|
|
3
|
+
description: Add "Trade leverage on Split" to a React + viem app — oracle-free, non-liquidatable ETH options (long calls / short puts) on Base and Arbitrum. Use when someone wants to let users trade leverage, options, or a perps-alternative that can't be liquidated, or asks to integrate Split / @splitmarkets/sdk.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Integrate Split leverage trading (one pass)
|
|
7
|
+
|
|
8
|
+
Split is oracle-free, non-liquidatable ETH options. Each option is physically backed 1:1 — the buyer's max
|
|
9
|
+
loss is the premium, and there is no liquidation. Long = call (ETH up), short = put (ETH down). Contracts are
|
|
10
|
+
deployed; pricing is a hosted, signed quote API. Everything you need is the npm package.
|
|
11
|
+
|
|
12
|
+
## Install
|
|
13
|
+
```bash
|
|
14
|
+
npm install @splitmarkets/sdk viem # react ≥18 too, only if you use the widget
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Fastest path — the widget
|
|
18
|
+
```tsx
|
|
19
|
+
import { SplitTradeWidget } from "@splitmarkets/sdk/widget";
|
|
20
|
+
// wagmi: const { data: walletClient } = useWalletClient(); const { address } = useAccount();
|
|
21
|
+
<SplitTradeWidget walletClient={walletClient} account={address} chain="base" />
|
|
22
|
+
```
|
|
23
|
+
That's pick-side → pick-leverage → amount → Buy → position → Close, end to end. Match the wallet's chain to
|
|
24
|
+
the `chain` prop (`"base"` | `"arbitrum"`).
|
|
25
|
+
|
|
26
|
+
## Gasless (recommended, default)
|
|
27
|
+
The widget defaults to `mode="gasless"`: **one signature**, **no `approve` tx**, **no gas**. The user signs a
|
|
28
|
+
single typed-data authorization for the USDC spend; the approve + buyN are batched into one sponsored call.
|
|
29
|
+
Contracts (N) and close proceeds settle in the user's **Split trading account** — a smart account
|
|
30
|
+
deterministically derived from their wallet (same wallet → same account). Available on Base + Arbitrum.
|
|
31
|
+
```tsx
|
|
32
|
+
<SplitTradeWidget walletClient={walletClient} account={address} chain="base" mode="gasless" />
|
|
33
|
+
```
|
|
34
|
+
**Connected fallback** (`mode="connected"`): trades from the user's own EOA — they sign each step and pay
|
|
35
|
+
their own gas (first trade per token is approve + buyN; later trades skip the approve). N lands in their EOA.
|
|
36
|
+
|
|
37
|
+
## Appearance / theming
|
|
38
|
+
Pass `appearance` to theme the widget; omitted tokens fall back to the dark default.
|
|
39
|
+
```tsx
|
|
40
|
+
<SplitTradeWidget walletClient={walletClient} account={address} chain="base"
|
|
41
|
+
appearance={{ accent: "#7c5cff", background: "#0e0e12", text: "#e8e8ee", radius: 16 }} />
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Headless — the five functions
|
|
45
|
+
```ts
|
|
46
|
+
import { getMarkets, getQuote, buy, getPosition, close } from "@splitmarkets/sdk";
|
|
47
|
+
```
|
|
48
|
+
1. **Markets.** `getMarkets(chain, side)` → leverage tiles `{ seriesId, vault, strike, leverage, spot, premium, label }`. Leverage = spot / premium.
|
|
49
|
+
2. **Quote.** `getQuote(chain, side, seriesId)` → pricer-signed `{ askPerN, bidPerN, validUntil, nonce, signature, ... }`. Quotes live ~3 min; the trade fns fetch fresh automatically.
|
|
50
|
+
3. **Buy.** `buy({ chain, side, seriesId, qN, walletClient, account })` → approves USDC, calls `buyN` with the signed quote; N mints to the wallet. Returns `{ txHash, qN }`.
|
|
51
|
+
4. **Position.** `getPosition({ chain, side, seriesId, account })` → `{ qN, markUsd, intrinsicUsd }` (mark at mid).
|
|
52
|
+
5. **Close.** `close({ chain, side, seriesId, walletClient, account })` → approves N, calls `sellN`; USDC to the wallet. Omit `qN` to close the full balance.
|
|
53
|
+
|
|
54
|
+
These five functions are the **connected** model: the user signs with their own wallet and pays their own gas
|
|
55
|
+
(N lands in their EOA). The widget's default **gasless** mode instead takes one signature, charges no gas, and
|
|
56
|
+
settles N in the user's Split trading account — see the Gasless section above. No backend, no keys either way.
|
|
57
|
+
|
|
58
|
+
## Units & gotchas
|
|
59
|
+
- `qN` is human contracts (e.g. `0.1`); the client scales to 18dp. USDC is 6dp; the client converts.
|
|
60
|
+
- First trade per token is two txs (approve, then buyN/sellN); the client skips the approve when allowance is set.
|
|
61
|
+
- The signed quote is verified on-chain in buyN/sellN — a stale/tampered quote reverts. You never sign prices.
|
|
62
|
+
- Honest copy to show users: **"can't be liquidated · max loss = premium."**
|
|
63
|
+
|
|
64
|
+
## Verify
|
|
65
|
+
`npm test` (offline unit) and `npm run test:integration` (live read-only) ship with the package.
|
|
66
|
+
|
|
67
|
+
## Deployed addresses (mainnet, constants in the package)
|
|
68
|
+
- Quote API: `https://quote-api-production-b5b9.up.railway.app`
|
|
69
|
+
- Base (8453): long/calls `0xe310f78e2721a2a10faabf8df830140756fcb579`, short/puts `0x8a176e1a7103cccbd6551eef65bc00da641a1ecb`, WETH `0x4200000000000000000000000000000000000006`, USDC `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`.
|
|
70
|
+
- Arbitrum (42161): long `0xa1b161ff5ddd2437ba9e359397e868ef4cb6df2f`, WETH `0x82aF49447D8a07e3bd95BD0d56f35241523fBab1`, USDC `0xaf88d065e77c8cC2239327C5EDb3A432268e5831`. (Calls only for now — `getMarkets("arbitrum","short")` throws.)
|
|
71
|
+
|
|
72
|
+
## Earn referral fees
|
|
73
|
+
Volume you route earns a share of protocol fees. Email mayur@vistara.dev with your integration's address.
|