@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/dist/widget.js ADDED
@@ -0,0 +1,272 @@
1
+ import { getMarkets, getPositionGasless, getPosition, deriveTradingAccount, buyGasless, buy, closeGasless, close } from './chunk-MZEPXYHI.js';
2
+ import { useState, useId, useMemo, useEffect, useCallback } from 'react';
3
+ import { jsxs, jsx } from 'react/jsx-runtime';
4
+
5
+ var THEMES = {
6
+ dark: {
7
+ accent: "#5b7cfa",
8
+ background: "#0e0e12",
9
+ surface: "#15151b",
10
+ text: "#e8e8ee",
11
+ muted: "#9a9aa6",
12
+ radius: "16px",
13
+ fontFamily: "system-ui, sans-serif"
14
+ },
15
+ light: {
16
+ accent: "#3b5bf5",
17
+ background: "#ffffff",
18
+ surface: "#f4f4f7",
19
+ text: "#16161c",
20
+ muted: "#6a6a76",
21
+ radius: "16px",
22
+ fontFamily: "system-ui, sans-serif"
23
+ }
24
+ };
25
+ var toLen = (v) => v === void 0 ? void 0 : typeof v === "number" ? `${v}px` : v;
26
+ function SplitTradeWidget({
27
+ walletClient,
28
+ account,
29
+ chain = "base",
30
+ theme = "dark",
31
+ appearance,
32
+ className,
33
+ mode = "gasless"
34
+ }) {
35
+ const gasless = mode === "gasless";
36
+ const [side, setSide] = useState("long");
37
+ const [markets, setMarkets] = useState([]);
38
+ const [picked, setPicked] = useState(null);
39
+ const [amount, setAmount] = useState("0.1");
40
+ const [position, setPosition] = useState(null);
41
+ const [status, setStatus] = useState("");
42
+ const [busy, setBusy] = useState(false);
43
+ const [trading, setTrading] = useState(null);
44
+ const scope = useId().replace(/[^a-zA-Z0-9_-]/g, "");
45
+ const rootClass = `swt-${scope}`;
46
+ const vars = useMemo(() => {
47
+ const base = THEMES[theme] ?? THEMES.dark;
48
+ const p = {
49
+ accent: appearance?.accent ?? base.accent,
50
+ background: appearance?.background ?? base.background,
51
+ surface: appearance?.surface ?? base.surface,
52
+ text: appearance?.text ?? base.text,
53
+ muted: appearance?.muted ?? base.muted,
54
+ radius: toLen(appearance?.radius) ?? base.radius,
55
+ fontFamily: appearance?.fontFamily ?? base.fontFamily
56
+ };
57
+ return {
58
+ "--swt-accent": p.accent,
59
+ "--swt-bg": p.background,
60
+ "--swt-surface": p.surface,
61
+ "--swt-text": p.text,
62
+ "--swt-muted": p.muted,
63
+ "--swt-radius": p.radius,
64
+ "--swt-font": p.fontFamily
65
+ };
66
+ }, [theme, appearance]);
67
+ useEffect(() => {
68
+ let alive = true;
69
+ setMarkets([]);
70
+ setPicked(null);
71
+ getMarkets(chain, side).then((m) => {
72
+ if (!alive) return;
73
+ setMarkets(m);
74
+ if (m.length) setPicked(m[0].seriesId);
75
+ }).catch((e) => alive && setStatus(`Couldn't load markets: ${e.message}`));
76
+ return () => {
77
+ alive = false;
78
+ };
79
+ }, [chain, side]);
80
+ const refreshPosition = useCallback(async () => {
81
+ if (!account || picked === null) {
82
+ setPosition(null);
83
+ return;
84
+ }
85
+ try {
86
+ if (gasless) {
87
+ if (!trading) {
88
+ setPosition(null);
89
+ return;
90
+ }
91
+ const p = await getPositionGasless({ chain, side, seriesId: picked, account, walletClient, trading });
92
+ setPosition({ qN: p.qN, markUsd: p.markUsd, intrinsicUsd: p.intrinsicUsd });
93
+ } else {
94
+ setPosition(await getPosition({ chain, side, seriesId: picked, account }));
95
+ }
96
+ } catch {
97
+ setPosition(null);
98
+ }
99
+ }, [account, chain, side, picked, gasless, trading, walletClient]);
100
+ useEffect(() => {
101
+ refreshPosition();
102
+ }, [refreshPosition]);
103
+ const onBuy = useCallback(async () => {
104
+ if (!walletClient || !account || picked === null) {
105
+ setStatus("Connect a wallet first.");
106
+ return;
107
+ }
108
+ const qN = Number(amount);
109
+ if (!(qN > 0)) {
110
+ setStatus("Enter an amount > 0.");
111
+ return;
112
+ }
113
+ setBusy(true);
114
+ try {
115
+ if (gasless) {
116
+ setStatus("Confirm one signature\u2026");
117
+ const t = trading ?? await deriveTradingAccount(walletClient, account, chain);
118
+ if (!trading) setTrading(t);
119
+ const r = await buyGasless({ chain, side, seriesId: picked, qN, walletClient, account, trading: t });
120
+ setStatus(`Bought ${qN} contracts. tx ${r.txHash.slice(0, 10)}\u2026`);
121
+ } else {
122
+ setStatus("Confirm in your wallet (approve, then buy)\u2026");
123
+ const r = await buy({ chain, side, seriesId: picked, qN, walletClient, account });
124
+ setStatus(`Bought ${r.qN} contracts. tx ${r.txHash.slice(0, 10)}\u2026`);
125
+ }
126
+ await refreshPosition();
127
+ } catch (e) {
128
+ setStatus(`Buy failed: ${errMsg(e)}`);
129
+ } finally {
130
+ setBusy(false);
131
+ }
132
+ }, [walletClient, account, chain, side, picked, amount, refreshPosition, gasless, trading]);
133
+ const onClose = useCallback(async () => {
134
+ if (!walletClient || !account || picked === null) return;
135
+ setBusy(true);
136
+ try {
137
+ if (gasless) {
138
+ setStatus("Closing\u2026");
139
+ const t = trading ?? await deriveTradingAccount(walletClient, account, chain);
140
+ if (!trading) setTrading(t);
141
+ const r = await closeGasless({ chain, side, seriesId: picked, walletClient, account, trading: t });
142
+ setStatus(`Closed. tx ${r.txHash.slice(0, 10)}\u2026`);
143
+ } else {
144
+ setStatus("Confirm in your wallet (approve, then close)\u2026");
145
+ const r = await close({ chain, side, seriesId: picked, walletClient, account });
146
+ setStatus(`Closed ${r.qN} contracts. tx ${r.txHash.slice(0, 10)}\u2026`);
147
+ }
148
+ await refreshPosition();
149
+ } catch (e) {
150
+ setStatus(`Close failed: ${errMsg(e)}`);
151
+ } finally {
152
+ setBusy(false);
153
+ }
154
+ }, [walletClient, account, chain, side, picked, refreshPosition, gasless, trading]);
155
+ const hasPosition = !!position && position.qN > 0;
156
+ return /* @__PURE__ */ jsxs("div", { className: [rootClass, className].filter(Boolean).join(" "), style: vars, children: [
157
+ /* @__PURE__ */ jsx("style", { children: css(rootClass) }),
158
+ /* @__PURE__ */ jsxs("div", { className: "swt-card", children: [
159
+ /* @__PURE__ */ jsxs("div", { className: "swt-head", children: [
160
+ /* @__PURE__ */ jsx("strong", { children: "Trade leverage on Split" }),
161
+ /* @__PURE__ */ jsx("span", { className: "swt-tag", children: "can't be liquidated \xB7 max loss = premium" })
162
+ ] }),
163
+ /* @__PURE__ */ jsx("div", { className: "swt-row", children: ["long", "short"].map((s) => /* @__PURE__ */ jsx(
164
+ "button",
165
+ {
166
+ type: "button",
167
+ onClick: () => setSide(s),
168
+ className: `swt-toggle${side === s ? " is-on" : ""}`,
169
+ children: s === "long" ? "Long (ETH up)" : "Short (ETH down)"
170
+ },
171
+ s
172
+ )) }),
173
+ /* @__PURE__ */ jsxs("div", { className: "swt-tiles", children: [
174
+ markets.length === 0 && /* @__PURE__ */ jsx("div", { className: "swt-muted", children: "No live markets right now." }),
175
+ markets.map((m) => /* @__PURE__ */ jsxs(
176
+ "button",
177
+ {
178
+ type: "button",
179
+ onClick: () => setPicked(m.seriesId),
180
+ className: `swt-tile${picked === m.seriesId ? " is-on" : ""}`,
181
+ children: [
182
+ /* @__PURE__ */ jsx("div", { className: "swt-lev", children: m.leverage > 0 ? `${m.leverage.toFixed(0)}x` : "\u2014" }),
183
+ /* @__PURE__ */ jsxs("div", { className: "swt-muted", children: [
184
+ "$",
185
+ m.strike.toLocaleString()
186
+ ] }),
187
+ /* @__PURE__ */ jsxs("div", { className: "swt-muted-sm", children: [
188
+ "$",
189
+ m.premium.toFixed(2),
190
+ "/contract"
191
+ ] })
192
+ ]
193
+ },
194
+ m.seriesId
195
+ ))
196
+ ] }),
197
+ /* @__PURE__ */ jsxs("div", { className: "swt-row", children: [
198
+ /* @__PURE__ */ jsx(
199
+ "input",
200
+ {
201
+ value: amount,
202
+ onChange: (e) => setAmount(e.target.value),
203
+ inputMode: "decimal",
204
+ placeholder: "0.1",
205
+ className: "swt-input"
206
+ }
207
+ ),
208
+ /* @__PURE__ */ jsx("span", { className: "swt-muted", children: "contracts" }),
209
+ /* @__PURE__ */ jsx(
210
+ "button",
211
+ {
212
+ type: "button",
213
+ onClick: onBuy,
214
+ disabled: busy || picked === null,
215
+ className: "swt-buy",
216
+ children: busy ? "\u2026" : "Buy"
217
+ }
218
+ )
219
+ ] }),
220
+ hasPosition && /* @__PURE__ */ jsxs("div", { className: "swt-pos", children: [
221
+ /* @__PURE__ */ jsxs("div", { children: [
222
+ /* @__PURE__ */ jsx("div", { className: "swt-muted", children: "Your position" }),
223
+ /* @__PURE__ */ jsxs("div", { children: [
224
+ /* @__PURE__ */ jsx("strong", { children: position.qN.toFixed(4) }),
225
+ " contracts \xB7 mark $",
226
+ position.markUsd.toFixed(2)
227
+ ] }),
228
+ /* @__PURE__ */ jsxs("div", { className: "swt-muted-sm", children: [
229
+ "intrinsic $",
230
+ position.intrinsicUsd.toFixed(2)
231
+ ] })
232
+ ] }),
233
+ /* @__PURE__ */ jsx("button", { type: "button", onClick: onClose, disabled: busy, className: "swt-close", children: busy ? "\u2026" : "Close" })
234
+ ] }),
235
+ status && /* @__PURE__ */ jsx("div", { className: "swt-status", children: status })
236
+ ] })
237
+ ] });
238
+ }
239
+ function errMsg(e) {
240
+ if (e && typeof e === "object") {
241
+ const o = e;
242
+ if (typeof o.shortMessage === "string") return o.shortMessage;
243
+ if (typeof o.message === "string") return o.message;
244
+ }
245
+ return String(e);
246
+ }
247
+ function css(root) {
248
+ const r = `.${root}`;
249
+ return `
250
+ ${r} .swt-card{max-width:380px;border:1px solid color-mix(in srgb, var(--swt-text) 12%, transparent);border-radius:var(--swt-radius);padding:16px;background:var(--swt-bg);color:var(--swt-text);font-family:var(--swt-font);display:flex;flex-direction:column;gap:12px}
251
+ ${r} .swt-head{display:flex;flex-direction:column;gap:4px}
252
+ ${r} .swt-tag{font-size:12px;color:var(--swt-accent)}
253
+ ${r} .swt-row{display:flex;gap:8px;align-items:center}
254
+ ${r} .swt-toggle{flex:1;padding:8px 10px;border-radius:calc(var(--swt-radius) * 0.6);border:1px solid color-mix(in srgb, var(--swt-text) 16%, transparent);background:transparent;color:var(--swt-muted);cursor:pointer;font-family:inherit}
255
+ ${r} .swt-toggle.is-on{background:color-mix(in srgb, var(--swt-accent) 18%, transparent);color:var(--swt-accent);border-color:color-mix(in srgb, var(--swt-accent) 50%, transparent)}
256
+ ${r} .swt-tiles{display:grid;grid-template-columns:repeat(auto-fill, minmax(96px, 1fr));gap:8px}
257
+ ${r} .swt-tile{padding:10px 8px;border-radius:calc(var(--swt-radius) * 0.6);border:1px solid color-mix(in srgb, var(--swt-text) 16%, transparent);background:transparent;color:var(--swt-text);cursor:pointer;text-align:center;font-family:inherit}
258
+ ${r} .swt-tile.is-on{border-color:var(--swt-accent);background:color-mix(in srgb, var(--swt-accent) 14%, transparent)}
259
+ ${r} .swt-lev{font-size:18px;font-weight:700}
260
+ ${r} .swt-muted{color:var(--swt-muted);font-size:13px}
261
+ ${r} .swt-muted-sm{color:color-mix(in srgb, var(--swt-muted) 80%, transparent);font-size:11px}
262
+ ${r} .swt-input{flex:1;min-width:0;padding:8px 10px;border-radius:calc(var(--swt-radius) * 0.6);border:1px solid color-mix(in srgb, var(--swt-text) 16%, transparent);background:var(--swt-surface);color:var(--swt-text);font-family:inherit}
263
+ ${r} .swt-buy{padding:8px 18px;border-radius:calc(var(--swt-radius) * 0.6);border:none;background:var(--swt-accent);color:#fff;font-weight:600;cursor:pointer;font-family:inherit}
264
+ ${r} .swt-buy:disabled,${r} .swt-close:disabled{opacity:.6;cursor:default}
265
+ ${r} .swt-pos{display:flex;justify-content:space-between;align-items:center;padding:12px;border-radius:calc(var(--swt-radius) * 0.75);background:var(--swt-surface)}
266
+ ${r} .swt-close{padding:8px 14px;border-radius:calc(var(--swt-radius) * 0.6);border:1px solid color-mix(in srgb, var(--swt-text) 16%, transparent);background:transparent;color:var(--swt-text);cursor:pointer;font-family:inherit}
267
+ ${r} .swt-status{font-size:12px;color:var(--swt-muted)}
268
+ `;
269
+ }
270
+ var example_default = SplitTradeWidget;
271
+
272
+ export { SplitTradeWidget, example_default as default };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@splitmarkets/sdk",
3
+ "version": "0.2.0",
4
+ "description": "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.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "files": ["dist", "README.md", "SKILL.md"],
8
+ "main": "./dist/index.js",
9
+ "module": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": { "types": "./dist/index.d.ts", "import": "./dist/index.js" },
13
+ "./widget": { "types": "./dist/widget.d.ts", "import": "./dist/widget.js" }
14
+ },
15
+ "scripts": {
16
+ "build": "tsup",
17
+ "test": "vitest run test/unit.test.ts",
18
+ "test:integration": "vitest run test/integration.test.ts",
19
+ "prepublishOnly": "npm run build && npm test"
20
+ },
21
+ "keywords": ["ethereum", "base", "arbitrum", "options", "defi", "viem", "split", "no-liquidation"],
22
+ "homepage": "https://split.markets",
23
+ "peerDependencies": {
24
+ "viem": "^2",
25
+ "react": ">=18"
26
+ },
27
+ "peerDependenciesMeta": {
28
+ "react": { "optional": true }
29
+ },
30
+ "devDependencies": {
31
+ "@types/react": "^19",
32
+ "react": "^19",
33
+ "tsup": "^8",
34
+ "typescript": "^5",
35
+ "viem": "^2",
36
+ "vitest": "^2"
37
+ }
38
+ }