@ostium/builder-sdk 0.1.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 +589 -0
- package/dist/cli.js +6835 -0
- package/dist/index.cjs +5013 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1449 -0
- package/dist/index.d.ts +1449 -0
- package/dist/index.js +4988 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,589 @@
|
|
|
1
|
+
# Ostium Builder SDK
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
TypeScript SDK for trading Stocks, Commodities, Forex, Indices, Crypto and ETF perps on [Ostium](https://ostium.com).
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @ostium/builder-sdk viem
|
|
9
|
+
# or
|
|
10
|
+
bun add @ostium/builder-sdk viem
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
**Guides:**
|
|
14
|
+
- [BUILDER.md](BUILDER.md) — Integrating Ostium into your app with builder fees
|
|
15
|
+
- [TRADER.md](TRADER.md) — Building trading strategies programmatically on Ostium
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Project Structure
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
src/
|
|
23
|
+
client.ts # OstiumClient — public API
|
|
24
|
+
config.ts # Named param interfaces for each mode
|
|
25
|
+
types.ts # OpenTradeParams, CloseTradeParams, etc.
|
|
26
|
+
errors.ts # OstiumError + OstiumErrorCode
|
|
27
|
+
encoder.ts # ABI encoding helpers (openTrade, closeTrade, ...)
|
|
28
|
+
cli.ts # Interactive CLI (bunx ostium)
|
|
29
|
+
signer/ # SignerStrategy — Self vs Delegated wrapping
|
|
30
|
+
submitter/ # SubmissionStrategy — EOA vs Pimlico UserOp
|
|
31
|
+
internal/
|
|
32
|
+
contracts.ts # On-chain addresses (mainnet + testnet)
|
|
33
|
+
decimal.ts # parseUsdc, parsePrice, parseLeverage
|
|
34
|
+
erc20.ts # Minimal ERC-20 ABI
|
|
35
|
+
contract.ts # Ostium Trading ABI
|
|
36
|
+
open-builder.ts # Trade struct construction
|
|
37
|
+
precision.ts # Precision constants
|
|
38
|
+
validation.ts # Input validation helpers
|
|
39
|
+
data/ # Read-only subgraph + price feed client
|
|
40
|
+
client.ts # OstiumSubgraphClient
|
|
41
|
+
types.ts # Pair, Position, Fill, OpenOrder, Order, …
|
|
42
|
+
queries.ts # GraphQL queries
|
|
43
|
+
internal/ # Formatters, calculations, pagination
|
|
44
|
+
dist/ # Compiled output (bun run build)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Development
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
bun install # install dependencies
|
|
53
|
+
bun run build # compile to dist/
|
|
54
|
+
bun run typecheck # tsc --noEmit
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Modes
|
|
60
|
+
|
|
61
|
+
The four client modes differ in **who signs** and **how the transaction is submitted**.
|
|
62
|
+
|
|
63
|
+
### Signing
|
|
64
|
+
|
|
65
|
+
| Mode | Who owns USDC + positions | Who signs transactions |
|
|
66
|
+
|:--|:--|:--|
|
|
67
|
+
| **Self** | Your EOA | Your EOA |
|
|
68
|
+
| **Delegated** | Trader address (separate EOA) | Delegate EOA, wrapped in `delegatedAction(traderAddress, data)` |
|
|
69
|
+
|
|
70
|
+
### Submission
|
|
71
|
+
|
|
72
|
+
| Mode | How the tx is sent | Gas |
|
|
73
|
+
|:--|:--|:--|
|
|
74
|
+
| **Self** (EOA) | Standard `eth_sendRawTransaction` | ETH required per tx |
|
|
75
|
+
| **Gasless** | Pimlico ERC-4337 UserOperation via Safe smart account | ETH-free after one-time setup |
|
|
76
|
+
|
|
77
|
+
### The Four Combinations
|
|
78
|
+
|
|
79
|
+
| Constructor | Signer | Submitter | Notes |
|
|
80
|
+
|:--|:--|:--|:--|
|
|
81
|
+
| `createSelfAndSelf` | EOA | EOA | Simplest — one key, pays gas |
|
|
82
|
+
| `createSelfAndGasless` | EOA (owner) + Safe (delegate) | Pimlico UserOp | One-time setup, then free |
|
|
83
|
+
| `createDelegatedAndSelf` | Delegate EOA | Delegate EOA | Delegate holds ETH, trader holds USDC |
|
|
84
|
+
| `createDelegatedAndGasless` | Delegate EOA (owner) + Safe (delegate) | Pimlico UserOp | No ETH after trader calls `setDelegate(safeAddress)` |
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { OstiumClient, OrderType } from '@ostium/builder-sdk';
|
|
88
|
+
|
|
89
|
+
// Self + Self
|
|
90
|
+
const client = await OstiumClient.createSelfAndSelf({
|
|
91
|
+
traderPrivateKey: '0x...',
|
|
92
|
+
rpcUrl: 'https://arb-mainnet.g.alchemy.com/v2/...',
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Self + Gasless
|
|
96
|
+
const client = await OstiumClient.createSelfAndGasless({
|
|
97
|
+
traderPrivateKey: '0x...',
|
|
98
|
+
pimlicoUrl: 'https://builder.ostium.io/v1/pimlico/sponsor?chainId=42161',
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Delegated + Self
|
|
102
|
+
const client = await OstiumClient.createDelegatedAndSelf({
|
|
103
|
+
delegatePrivateKey: '0xDelegateKey',
|
|
104
|
+
traderAddress: '0xTraderAddress',
|
|
105
|
+
rpcUrl: 'https://arb-mainnet.g.alchemy.com/v2/...',
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Delegated + Gasless
|
|
109
|
+
const client = await OstiumClient.createDelegatedAndGasless({
|
|
110
|
+
delegatePrivateKey: '0xDelegateKey',
|
|
111
|
+
traderAddress: '0xTraderAddress',
|
|
112
|
+
pimlicoUrl: 'https://builder.ostium.io/v1/pimlico/sponsor?chainId=42161',
|
|
113
|
+
});
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Read-only (no private key)
|
|
117
|
+
|
|
118
|
+
Use `createReadOnly` to access market data without a signer. All read methods are available; write methods throw `INVALID_CONFIG`.
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
const reader = await OstiumClient.createReadOnly();
|
|
122
|
+
const { pairs } = await reader.getPairs();
|
|
123
|
+
const positions = await reader.getOpenPositions({ user: '0xTrader...' });
|
|
124
|
+
const balances = await reader.getBalances('0xTrader...');
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Common Options
|
|
128
|
+
|
|
129
|
+
All constructors (including `createReadOnly`) accept these optional overrides:
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
{
|
|
133
|
+
testnet?: boolean; // Arbitrum Sepolia when true (default false)
|
|
134
|
+
subgraphUrl?: string; // override the default subgraph endpoint
|
|
135
|
+
builderApiUrl?: string; // override the builder API base URL (prices, OHLC, WebSocket)
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Example:
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
const client = await OstiumClient.createSelfAndSelf({
|
|
143
|
+
traderPrivateKey: '0x...',
|
|
144
|
+
rpcUrl: '...',
|
|
145
|
+
subgraphUrl: 'https://your-subgraph.example.com/graphql',
|
|
146
|
+
builderApiUrl: 'https://your-builder-api.example.com',
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
const reader = await OstiumClient.createReadOnly({
|
|
150
|
+
subgraphUrl: 'https://your-subgraph.example.com/graphql',
|
|
151
|
+
builderApiUrl: 'https://your-builder-api.example.com',
|
|
152
|
+
});
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Self + Gasless: One-Time Setup
|
|
156
|
+
|
|
157
|
+
A Safe smart account is derived deterministically from your private key. You need to register it as your on-chain delegate once:
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
const client = await OstiumClient.createSelfAndGasless({
|
|
161
|
+
traderPrivateKey: '0x...',
|
|
162
|
+
pimlicoUrl: 'https://builder.ostium.io/v1/pimlico/sponsor?chainId=42161',
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
console.log('EOA (trader):', client.getTraderAddress());
|
|
166
|
+
console.log('Safe (delegate):', client.getSmartAccountAddress());
|
|
167
|
+
|
|
168
|
+
await client.approveUsdc('max'); // EOA → TradingStorage approval (gas, once)
|
|
169
|
+
await client.setupGaslessDelegation(); // EOA → setDelegate(Safe) (gas, once)
|
|
170
|
+
|
|
171
|
+
// All trades from here are gasless UserOps
|
|
172
|
+
await client.openTrade({ ... });
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Delegated Modes: Setup Checklist
|
|
176
|
+
|
|
177
|
+
The trader account must perform two one-time operations before the delegate can trade:
|
|
178
|
+
|
|
179
|
+
```ts
|
|
180
|
+
// 1. Trader approves USDC
|
|
181
|
+
const traderClient = await OstiumClient.createSelfAndSelf({
|
|
182
|
+
traderPrivateKey: '0xTraderKey',
|
|
183
|
+
rpcUrl: '...',
|
|
184
|
+
});
|
|
185
|
+
await traderClient.approveUsdc('max');
|
|
186
|
+
|
|
187
|
+
// 2a. Delegated + Self: trader registers the delegate EOA
|
|
188
|
+
await traderClient.setDelegate('0xDelegateEOA');
|
|
189
|
+
|
|
190
|
+
// 2b. Delegated + Gasless: trader registers the delegate's Safe
|
|
191
|
+
const delegateClient = await OstiumClient.createDelegatedAndGasless({
|
|
192
|
+
delegatePrivateKey: '0xDelegateKey',
|
|
193
|
+
traderAddress: '0xTraderAddress',
|
|
194
|
+
pimlicoUrl: '...',
|
|
195
|
+
});
|
|
196
|
+
await traderClient.setDelegate(delegateClient.getSmartAccountAddress()!);
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
## CLI — Environment Variable Configuration
|
|
202
|
+
|
|
203
|
+
The `ostium` CLI can load all credentials from a `.env` file (via `dotenv`). Create a `.env` file in your working directory with the variables relevant to your mode:
|
|
204
|
+
|
|
205
|
+
```dotenv
|
|
206
|
+
# ── Universal ─────────────────────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
# Self / main EOA private key (required for Self modes; also used as the main key in Test + Delegated)
|
|
209
|
+
PRIVATE_KEY=0x...
|
|
210
|
+
|
|
211
|
+
# Arbitrum RPC URL — required for Self+Self and Delegated+Self; optional for gasless modes
|
|
212
|
+
ARB_RPC_URL=https://arb-mainnet.g.alchemy.com/v2/YOUR_KEY
|
|
213
|
+
|
|
214
|
+
# ── Delegated modes ───────────────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
# Delegate EOA private key (falls back to PRIVATE_KEY if not set)
|
|
217
|
+
DELEGATE_PRIVATE_KEY=0x...
|
|
218
|
+
|
|
219
|
+
# Trader address the delegate acts on behalf of (required for Trade + Delegated)
|
|
220
|
+
TRADER_ADDRESS=0x...
|
|
221
|
+
|
|
222
|
+
# ── Gasless modes ─────────────────────────────────────────────────────────────
|
|
223
|
+
|
|
224
|
+
# Pimlico sponsor URL (required for Self+Gasless and Delegated+Gasless)
|
|
225
|
+
PIMLICO_URL=https://builder.ostium.io/v1/pimlico/sponsor?chainId=42161
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
When the CLI detects a `.env` file it skips the interactive key/URL prompts and loads all values automatically. Fields not present in the env file are prompted interactively as normal.
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
## Networks
|
|
233
|
+
|
|
234
|
+
| Network | Chain ID | `testnet` |
|
|
235
|
+
|:--|:-:|:-:|
|
|
236
|
+
| Arbitrum One | 42161 | `false` (default) |
|
|
237
|
+
| Arbitrum Sepolia | 421614 | `true` |
|
|
238
|
+
|
|
239
|
+
```ts
|
|
240
|
+
const client = await OstiumClient.createSelfAndSelf({
|
|
241
|
+
traderPrivateKey: '0x...',
|
|
242
|
+
rpcUrl: 'https://sepolia-rollup.arbitrum.io/rpc',
|
|
243
|
+
testnet: true,
|
|
244
|
+
});
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## API Reference
|
|
250
|
+
|
|
251
|
+
### Enums
|
|
252
|
+
|
|
253
|
+
```ts
|
|
254
|
+
import { OrderType, CancelOrderType } from '@ostium/builder-sdk';
|
|
255
|
+
|
|
256
|
+
OrderType.Market // 'market'
|
|
257
|
+
OrderType.Limit // 'limit'
|
|
258
|
+
OrderType.Stop // 'stop'
|
|
259
|
+
|
|
260
|
+
CancelOrderType.Limit // 'limit'
|
|
261
|
+
CancelOrderType.PendingOpen // 'pendingOpen'
|
|
262
|
+
CancelOrderType.PendingClose // 'pendingClose'
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
### Trading Methods
|
|
266
|
+
|
|
267
|
+
All return `Promise<SubmissionResult>` as soon as the transaction is submitted (UserOp or EOA). The hash is available immediately; the SDK does not wait for receipt or parse oracle events.
|
|
268
|
+
|
|
269
|
+
```ts
|
|
270
|
+
interface SubmissionResult {
|
|
271
|
+
txHash: `0x${string}`;
|
|
272
|
+
smartAccountAddress?: `0x${string}`; // gasless modes only
|
|
273
|
+
}
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Use `getOrders({ initiatedTxHashes: [result.txHash] })` to poll by submission hash, or `orderIds` / recent orders for the trader.
|
|
277
|
+
|
|
278
|
+
#### `openTrade(params)`
|
|
279
|
+
|
|
280
|
+
```ts
|
|
281
|
+
const result = await client.openTrade({
|
|
282
|
+
pairIndex: 0, // trading pair (use getPairs() to list all)
|
|
283
|
+
buy: true, // true = long, false = short
|
|
284
|
+
price: '65000', // entry price
|
|
285
|
+
collateral: '100', // USD (min $5)
|
|
286
|
+
leverage: '10', // multiplier (max varies by pair)
|
|
287
|
+
type: OrderType.Market,
|
|
288
|
+
takeProfit?: '70000',
|
|
289
|
+
stopLoss?: '60000',
|
|
290
|
+
slippage?: 50, // bps, market orders only
|
|
291
|
+
isDayTrade?: false, // true when leverage > pair.overnightMaxLeverage
|
|
292
|
+
});
|
|
293
|
+
console.log(result.txHash);
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
> **Day trades** are auto-closed before market close. Set `isDayTrade: true` when the desired leverage exceeds `pair.overnightMaxLeverage` (and `overnightMaxLeverage > 0`). Use `getPairs()` to read both thresholds per pair.
|
|
297
|
+
|
|
298
|
+
#### `closeTrade(params)`
|
|
299
|
+
|
|
300
|
+
`pairId` and `idx` come from `getOpenPositions()`:
|
|
301
|
+
|
|
302
|
+
```ts
|
|
303
|
+
const { pairPositions } = await client.getOpenPositions();
|
|
304
|
+
const { pairId, idx } = pairPositions[0].position;
|
|
305
|
+
|
|
306
|
+
await client.closeTrade({
|
|
307
|
+
pairId, // from Position.pairId
|
|
308
|
+
idx, // from Position.idx
|
|
309
|
+
price: '66000',
|
|
310
|
+
closePercent: 100, // 1–100, partial closes supported
|
|
311
|
+
slippage?: 50,
|
|
312
|
+
});
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
#### `cancelOrder(params)`
|
|
316
|
+
|
|
317
|
+
TypeScript narrows the required fields by `type`:
|
|
318
|
+
|
|
319
|
+
```ts
|
|
320
|
+
// Cancel a pending limit order
|
|
321
|
+
const [order] = await client.getOpenOrders();
|
|
322
|
+
await client.cancelOrder({ type: CancelOrderType.Limit, pairId: order.pairId, idx: order.idx });
|
|
323
|
+
|
|
324
|
+
// Cancel a timed-out market open (orderId from chain / indexer)
|
|
325
|
+
await client.cancelOrder({ type: CancelOrderType.PendingOpen, orderId: 123 });
|
|
326
|
+
|
|
327
|
+
// Cancel a timed-out market close — retry: true re-submits the close
|
|
328
|
+
await client.cancelOrder({ type: CancelOrderType.PendingClose, orderId: 123, retry: true });
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
#### `modifyOrder(params)`
|
|
332
|
+
|
|
333
|
+
`pairId` and `idx` come from `getOpenPositions()` (for TP/SL) or `getOpenOrders()` (for limit order price):
|
|
334
|
+
|
|
335
|
+
```ts
|
|
336
|
+
// Update TP on an open trade
|
|
337
|
+
await client.modifyOrder({ pairId, idx, takeProfit: '72000' });
|
|
338
|
+
|
|
339
|
+
// Update SL on an open trade
|
|
340
|
+
await client.modifyOrder({ pairId, idx, stopLoss: '61000' });
|
|
341
|
+
|
|
342
|
+
// Reprice a limit order (with optional new TP/SL)
|
|
343
|
+
await client.modifyOrder({ pairId, idx, price: '64000', takeProfit: '70000' });
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Note: you cannot update both `takeProfit` and `stopLoss` in a single call without also providing `price`. Send two separate calls instead.
|
|
347
|
+
|
|
348
|
+
#### `updateCollateral(params)`
|
|
349
|
+
|
|
350
|
+
`pairId` and `idx` come from `getOpenPositions()`:
|
|
351
|
+
|
|
352
|
+
```ts
|
|
353
|
+
await client.updateCollateral({ pairId, idx, amount: '50' }); // add $50
|
|
354
|
+
await client.updateCollateral({ pairId, idx, amount: '-30' }); // remove $30
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### USDC + Balance Helpers
|
|
358
|
+
|
|
359
|
+
```ts
|
|
360
|
+
const { current, required, sufficient } = await client.checkUsdcAllowance('100');
|
|
361
|
+
await client.approveUsdc('1000'); // approve $1000
|
|
362
|
+
await client.approveUsdc('max'); // approve MaxUint256
|
|
363
|
+
|
|
364
|
+
// All three values are decimal strings produced by formatUnits (e.g. "1234.56")
|
|
365
|
+
// Parse with Number() / parseFloat() only when you need numeric arithmetic.
|
|
366
|
+
const { usdc, eth, allowance } = await client.getBalances();
|
|
367
|
+
// In read-only mode, pass the address explicitly:
|
|
368
|
+
const { usdc, eth, allowance } = await client.getBalances('0xTrader...');
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Read Methods
|
|
372
|
+
|
|
373
|
+
All read methods are available on both `OstiumClient` and `OstiumSubgraphClient`. On `OstiumClient` the connected trader address is used by default wherever `user` is optional.
|
|
374
|
+
|
|
375
|
+
#### `getPairs(params?)`
|
|
376
|
+
|
|
377
|
+
All trading pairs with live prices, market-status flags, computed size limits, and rollover rates.
|
|
378
|
+
|
|
379
|
+
```ts
|
|
380
|
+
const { pairs } = await client.getPairs();
|
|
381
|
+
// Optional — filter to specific pairs:
|
|
382
|
+
const { pairs } = await client.getPairs({ pairIds: [0, 1, 4] });
|
|
383
|
+
|
|
384
|
+
// Pair fields:
|
|
385
|
+
// pairId, pairTo, pairFrom, category
|
|
386
|
+
// maxLeverage, overnightMaxLeverage
|
|
387
|
+
// minSz, maxBSz, maxSSz, minNtl
|
|
388
|
+
// openInterest, buyOpenInterest, sellOpenInterest, maxOpenInterest
|
|
389
|
+
// rolloverRate: { long, short } — 8hr % by side
|
|
390
|
+
// rolloverFeePerBlock
|
|
391
|
+
// midPx, askPx, bidPx
|
|
392
|
+
// isMarketOpen, isDayTradingClosed, secondsToToggleIsDayTradingClosed
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
Pair symbol names from the subgraph are normalized automatically (e.g. CL → WTI, FTSE → UK100, SPX → US500).
|
|
396
|
+
|
|
397
|
+
#### `getAllPrices()`
|
|
398
|
+
|
|
399
|
+
Live mid/bid/ask prices for every pair, keyed by `pairId`.
|
|
400
|
+
|
|
401
|
+
```ts
|
|
402
|
+
const { prices } = await client.getAllPrices();
|
|
403
|
+
// prices['0'] → { mid: '65000', bid: '64990', ask: '65010' }
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
#### `getOpenPositions(params?)`
|
|
407
|
+
|
|
408
|
+
Open positions, margin summary, and per-position live PnL. Block number is fetched automatically on `OstiumClient`.
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
const { pairPositions, marginSummary, withdrawable, time } =
|
|
412
|
+
await client.getOpenPositions();
|
|
413
|
+
|
|
414
|
+
// Each position:
|
|
415
|
+
const { pairId, pid, idx, side, szi, entryPx, leverage, ntl,
|
|
416
|
+
unrealizedPnl, returnOnEquity, liquidationPx,
|
|
417
|
+
collateralUsed, cumRollover, tpPx, slPx,
|
|
418
|
+
openTimestamp, isDayTrade } = pairPositions[0].position;
|
|
419
|
+
|
|
420
|
+
// marginSummary: accountValue, totalCollateralUsed, totalNtlPos, totalRawPnlUsd
|
|
421
|
+
// withdrawable: max collateral removable across all positions
|
|
422
|
+
// time: server time (Unix ms)
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
#### `getFills(params?)`
|
|
426
|
+
|
|
427
|
+
Executed fills, newest first.
|
|
428
|
+
|
|
429
|
+
```ts
|
|
430
|
+
const fills = await client.getFills();
|
|
431
|
+
// Filter options:
|
|
432
|
+
const fills = await client.getFills({ pairId: 0, limit: 50 });
|
|
433
|
+
// All traders (no address filter):
|
|
434
|
+
const fills = await client.getFills({ user: 'ALL' });
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
#### `getFillsByTime(params)`
|
|
438
|
+
|
|
439
|
+
Same as `getFills` but with a time range filter (Unix milliseconds).
|
|
440
|
+
|
|
441
|
+
```ts
|
|
442
|
+
const fills = await client.getFillsByTime({
|
|
443
|
+
startTime: Date.now() - 7 * 86_400_000, // last 7 days
|
|
444
|
+
endTime: Date.now(), // optional, defaults to now
|
|
445
|
+
pairId: 0, // optional pair filter
|
|
446
|
+
});
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
#### `getOpenOrders(params?)`
|
|
450
|
+
|
|
451
|
+
Active limit orders for a trader.
|
|
452
|
+
|
|
453
|
+
```ts
|
|
454
|
+
const orders = await client.getOpenOrders();
|
|
455
|
+
// Each order: pairId, pairTo, pairFrom, idx, side, limitPx, szi, orderType, tpPx?, slPx?, timestamp
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
#### `getOrders(params?)`
|
|
459
|
+
|
|
460
|
+
Orders at any status — pending, executed, or cancelled. Use this to poll whether an `openTrade`/`closeTrade` was executed by the oracle.
|
|
461
|
+
|
|
462
|
+
```ts
|
|
463
|
+
const result = await client.openTrade({ /* … */ });
|
|
464
|
+
|
|
465
|
+
// Poll by the submission tx (matches subgraph `initiatedTx`):
|
|
466
|
+
const [order] = await client.getOrders({ initiatedTxHashes: [result.txHash] });
|
|
467
|
+
console.log(order.isPending, order.isCancelled);
|
|
468
|
+
|
|
469
|
+
// Or by on-chain order id:
|
|
470
|
+
const [byId] = await client.getOrders({ orderIds: [123] });
|
|
471
|
+
|
|
472
|
+
// Recent orders for the connected trader (no filter args):
|
|
473
|
+
const orders = await client.getOrders();
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
Each `Order` extends `Fill` with `initiatedTx`, `initiatedTime`, `isPending`, `isCancelled`, and optional `cancelReason`.
|
|
477
|
+
|
|
478
|
+
#### `getSimSlippage(params)`
|
|
479
|
+
|
|
480
|
+
Simulate price impact for a list of pairs and notionals.
|
|
481
|
+
|
|
482
|
+
```ts
|
|
483
|
+
const result = await client.getSimSlippage({
|
|
484
|
+
pairIds: [0, 1],
|
|
485
|
+
ntls: ['1000', '10000', '100000'],
|
|
486
|
+
});
|
|
487
|
+
// result['0'].long → [{ ntl, slippage }, ...]
|
|
488
|
+
// result['0'].short → [{ ntl, slippage }, ...]
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
#### `getSimOrderbook(params)`
|
|
492
|
+
|
|
493
|
+
Synthetic bid/ask orderbook for a pair, following Hyperliquid L2Book format. Levels are log-spaced from the minimum order size to the remaining OI capacity on each side.
|
|
494
|
+
|
|
495
|
+
```ts
|
|
496
|
+
const book = await client.getSimOrderbook({ pairId: 0, levels: 20 });
|
|
497
|
+
// book.levels[0] → bids (short entries), best bid first
|
|
498
|
+
// book.levels[1] → asks (long entries), best ask first
|
|
499
|
+
// Each level: { px: string, sz: string, n: 1 }
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
#### `getCandles(params)`
|
|
503
|
+
|
|
504
|
+
OHLC candles from the builder API. `from`/`to` are Unix milliseconds. `pairId` is the same value returned by `getPairs()`.
|
|
505
|
+
|
|
506
|
+
```ts
|
|
507
|
+
const candles = await client.getCandles({
|
|
508
|
+
pairId: 0,
|
|
509
|
+
from: Date.now() - 30 * 86_400_000, // last 30 days
|
|
510
|
+
to: Date.now(), // optional, defaults to now
|
|
511
|
+
resolution: '1D', // '1' | '5' | '15' | '60' | '240' | '1D'
|
|
512
|
+
});
|
|
513
|
+
// Each candle: { pairFrom, pairTo, time, open, high, low, close }
|
|
514
|
+
// pairFrom / pairTo use normalized display names (e.g. "WTI" not "CL")
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
#### `streamPrices(pairIds?)`
|
|
518
|
+
|
|
519
|
+
WebSocket connection to the live price feed. Fires a `snapshot` on connect, then a `tick` on every update. Accepts the same `pairId` values as everywhere else in the SDK — no pair name strings needed.
|
|
520
|
+
|
|
521
|
+
```ts
|
|
522
|
+
const stream = client.streamPrices([0, 1]); // BTC and ETH by pairId
|
|
523
|
+
|
|
524
|
+
stream.onOpen(() => console.log('connected'));
|
|
525
|
+
stream.onSnapshot(ticks => console.log('initial snapshot:', ticks.length));
|
|
526
|
+
stream.onTick(tick => console.log(tick.pair, tick.mid));
|
|
527
|
+
// tick.pair / tick.from / tick.to use normalized display names
|
|
528
|
+
|
|
529
|
+
// Dynamically add / remove by pairId:
|
|
530
|
+
stream.subscribe([2]);
|
|
531
|
+
stream.unsubscribe([0]);
|
|
532
|
+
|
|
533
|
+
// Clean up:
|
|
534
|
+
stream.close();
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
Requires Node.js 18+, Bun, or a browser environment.
|
|
538
|
+
|
|
539
|
+
### Getters
|
|
540
|
+
|
|
541
|
+
```ts
|
|
542
|
+
client.getTraderAddress(); // on-chain trader address (throws in read-only mode)
|
|
543
|
+
client.getSmartAccountAddress(); // Safe address — gasless modes only, else undefined
|
|
544
|
+
client.isReadOnly(); // true when created via createReadOnly()
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
### Error Codes
|
|
548
|
+
|
|
549
|
+
```ts
|
|
550
|
+
import { OstiumError, OstiumErrorCode } from '@ostium/builder-sdk';
|
|
551
|
+
```
|
|
552
|
+
|
|
553
|
+
| Code | When thrown |
|
|
554
|
+
|:--|:--|
|
|
555
|
+
| `INVALID_CONFIG` | Missing or malformed config (privateKey format, address format, fee range) |
|
|
556
|
+
| `VALIDATION_FAILED` | Invalid trade params (TP/SL direction, leverage range, close percent) |
|
|
557
|
+
| `ALLOWANCE_INSUFFICIENT` | USDC allowance too low for `openTrade` / `updateCollateral` |
|
|
558
|
+
| `DELEGATION_FAILED` | `approveUsdc()` called in delegated mode |
|
|
559
|
+
| `CONTRACT_ERROR` | Contract revert (e.g. `WrongLeverage`, `NoDelegate`) |
|
|
560
|
+
| `NETWORK_ERROR` | RPC / Pimlico connectivity issues or rate limits |
|
|
561
|
+
| `SUBMISSION_FAILED` | Transaction rejected for other reasons |
|
|
562
|
+
|
|
563
|
+
### Decimal Utilities
|
|
564
|
+
|
|
565
|
+
```ts
|
|
566
|
+
import { parseUsdc, parsePrice, parseLeverage, MIN_COLLATERAL_USD, MAX_COLLATERAL_USD } from '@ostium/builder-sdk';
|
|
567
|
+
|
|
568
|
+
parsePrice('65000.50'); // 65000500000000000000000n (18 decimals)
|
|
569
|
+
parseUsdc('100.50'); // 100500000n (6 decimals)
|
|
570
|
+
MIN_COLLATERAL_USD; // 5
|
|
571
|
+
MAX_COLLATERAL_USD; // 2_000_000
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
### Standalone Subgraph Client
|
|
575
|
+
|
|
576
|
+
For read-only market data without a trading key, use `OstiumSubgraphClient` directly:
|
|
577
|
+
|
|
578
|
+
```ts
|
|
579
|
+
import { OstiumSubgraphClient } from '@ostium/builder-sdk';
|
|
580
|
+
|
|
581
|
+
const subgraph = await OstiumSubgraphClient.create({ testnet: false });
|
|
582
|
+
|
|
583
|
+
const { pairs } = await subgraph.getPairs();
|
|
584
|
+
const { prices } = await subgraph.getAllPrices();
|
|
585
|
+
const positions = await subgraph.getOpenPositions({ user: '0xTrader...' });
|
|
586
|
+
const fills = await subgraph.getFills({ user: '0xTrader...', limit: 50 });
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
`OstiumSubgraphClient.getOpenPositions` accepts an optional `blockNumber` (required for accurate PnL). On `OstiumClient` this is fetched automatically.
|