@lynx-crypto/kraken-api 0.1.2 → 1.0.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 CHANGED
@@ -8,326 +8,369 @@
8
8
  [![last commit](https://img.shields.io/github/last-commit/lynx-laboratory/kraken-api?branch=master)](https://github.com/lynx-laboratory/kraken-api/commits/master)
9
9
  [![license](https://img.shields.io/github/license/lynx-laboratory/kraken-api)](./LICENSE.md)
10
10
 
11
- TypeScript client for **Kraken SPOT**:
12
-
11
+ A modern, strongly-typed TypeScript client for the **Kraken Spot API**, providing both **REST** and **WebSocket v2** access in a single package.
13
12
  - **REST API** (public + private endpoints)
14
13
  - **WebSocket v2** (public market-data + authenticated user-data/trading)
15
14
 
16
15
  See [Kraken Official Documentation](https://docs.kraken.com/api/docs/category/guides)
17
16
 
18
- IMPORTANT
19
-
17
+ ## Important
20
18
  - This package is currently **SPOT only**. It does **not** implement Kraken Futures.
21
19
  - Unofficial project. Not affiliated with Kraken.
22
20
 
23
21
  ---
24
22
 
25
- ## Install
23
+ ## Features
24
+
25
+ ### Spot REST API
26
+ - Public market data (time, assets, pairs, ticker, OHLC, order book, trades, spreads)
27
+ - Private account data (balances, orders, trades, ledgers, export reports)
28
+ - Trading endpoints (add/amend/edit/cancel orders, batch operations)
29
+ - Funding endpoints (deposits, withdrawals, transfers)
30
+ - Earn endpoints (strategies, allocations, status)
31
+ - Subaccounts (master account operations)
32
+ - Transparency endpoints (pre-trade / post-trade data)
33
+
34
+ ### Spot WebSocket v2 API
35
+ - Public streams (ticker, trades, book, OHLC, instruments)
36
+ - Private user streams (balances, executions)
37
+ - Private trading RPC methods
38
+ - Automatic request/response correlation (`req_id`)
39
+ - Optional auto-reconnect with backoff
40
+ - Works in Node.js (via `ws`) or browser environments
26
41
 
27
- NPM:
42
+ ---
28
43
 
29
- ```
30
- npm i @lynx-crypto/kraken-api
31
- ```
44
+ ## Installation
32
45
 
33
- Yarn:
46
+ Using Yarn:
34
47
 
35
- ```
48
+ ```sh
36
49
  yarn add @lynx-crypto/kraken-api
37
50
  ```
38
51
 
39
- pnpm:
52
+ Using npm:
40
53
 
54
+ ```sh
55
+ npm install @lynx-crypto/kraken-api
41
56
  ```
42
- pnpm add @lynx-crypto/kraken-api
43
- ```
44
-
45
- Node support
46
-
47
- - Node >= 18 recommended (uses built-in fetch / AbortController)
48
57
 
49
58
  ---
50
59
 
51
- ## Quick start
60
+ ## Requirements
52
61
 
53
- ESM:
62
+ - Node.js 18+ recommended
63
+ - TypeScript 4.9+ recommended
64
+ - For private endpoints:
65
+ - Kraken API key and secret
66
+ - Appropriate permissions enabled in Kraken
67
+ - “WebSocket interface” enabled for private WS usage
54
68
 
55
- ```
56
- import { KrakenSpotRestClient, KrakenSpotWebsocketV2Client } from "@lynx-crypto/kraken-api";
57
- ```
69
+ ---
58
70
 
59
- CJS:
71
+ ## Environment Variables (for private usage)
60
72
 
61
- ```
62
- const { KrakenSpotRestClient, KrakenSpotWebsocketV2Client } = require("@lynx-crypto/kraken-api");
73
+ ```bash
74
+ KRAKEN_API_KEY="your_api_key"
75
+ KRAKEN_API_SECRET="your_api_secret"
63
76
  ```
64
77
 
65
78
  ---
66
79
 
67
- ## REST (Spot)
80
+ ## Package Exports
68
81
 
69
- ### Create a REST client
82
+ ### REST
83
+ - `KrakenSpotRestClient`
70
84
 
71
- ```ts
72
- const kraken = new KrakenSpotRestClient({
73
- // Optional:
74
- // baseUrl: "https://api.kraken.com",
75
- // timeoutMs: 10_000,
76
- // userAgent: "my-app/1.0.0",
85
+ Sub-APIs available on the client:
86
+ - `marketData`
87
+ - `accountData`
88
+ - `trading`
89
+ - `funding`
90
+ - `subaccounts`
91
+ - `earn`
92
+ - `transparency`
77
93
 
78
- // Required for private endpoints:
79
- apiKey: process.env.KRAKEN_API_KEY,
80
- apiSecret: process.env.KRAKEN_API_SECRET,
94
+ ### WebSocket
95
+ - `KrakenSpotWebsocketV2Client`
96
+ - `KrakenWebsocketBase` (advanced / custom integrations)
81
97
 
82
- // Optional logger:
83
- // logger: console,
84
- });
85
- ```
98
+ Typed channel namespaces:
99
+ - `admin`
100
+ - `marketData`
101
+ - `userData`
102
+ - `userTrading`
86
103
 
87
- ### Public endpoint example
104
+ All public request/response and stream payloads are exported as TypeScript types.
88
105
 
89
- (Your exact public endpoints depend on what you’ve implemented in src/spot/rest.)
106
+ ---
107
+ ## REST Usage
90
108
 
91
- Example shape:
109
+ ### Public REST example (no authentication)
92
110
 
93
111
  ```ts
94
- const serverTime = await kraken.public.getServerTime();
95
- ```
112
+ import { KrakenSpotRestClient } from '@lynx-crypto/kraken-api';
96
113
 
97
- ### Private endpoint example
114
+ async function main() {
115
+ const kraken = new KrakenSpotRestClient({
116
+ userAgent: 'example-app/1.0.0',
117
+ });
98
118
 
99
- (Your exact private endpoints depend on what you’ve implemented in src/spot/rest.)
119
+ const time = await kraken.marketData.getServerTime();
120
+ console.log('Kraken time:', time.rfc1123);
121
+ }
100
122
 
101
- Example shape:
102
- const balances = await kraken.accountData.getAccountBalance();
103
- console.log("USD:", balances["ZUSD"]);
123
+ main().catch((err) => {
124
+ console.error(err);
125
+ process.exitCode = 1;
126
+ });
127
+ ```
104
128
 
105
- ---
129
+ ### Private REST example (authenticated)
106
130
 
107
- ## WebSocket v2 (Spot)
131
+ ```ts
132
+ import 'dotenv/config';
133
+ import { KrakenSpotRestClient } from '@lynx-crypto/kraken-api';
134
+
135
+ function requireEnv(name: string): string {
136
+ const v = process.env[name];
137
+ if (!v) throw new Error(`Missing env var: ${name}`);
138
+ return v;
139
+ }
140
+
141
+ async function main() {
142
+ const kraken = new KrakenSpotRestClient({
143
+ userAgent: 'example-app/1.0.0',
144
+ apiKey: requireEnv('KRAKEN_API_KEY'),
145
+ apiSecret: requireEnv('KRAKEN_API_SECRET'),
146
+ });
147
+
148
+ const balances = await kraken.accountData.getAccountBalance();
149
+ console.log('Asset count:', Object.keys(balances).length);
150
+ }
151
+
152
+ main().catch((err) => {
153
+ console.error(err);
154
+ process.exitCode = 1;
155
+ });
156
+ ```
108
157
 
109
- This package provides a top-level v2 WS client that creates:
158
+ ---
110
159
 
111
- - a **public connection** (market data + admin)
112
- - a **private/auth connection** (user-data + user-trading)
160
+ ## WebSocket v2 Usage
113
161
 
114
- ### Create a WS v2 client
162
+ ### Public WebSocket (market data)
115
163
 
116
164
  ```ts
117
- const ws = new KrakenSpotWebsocketV2Client({
118
- // Optional override URLs:
119
- // publicUrl: "wss://ws.kraken.com/v2",
120
- // privateUrl: "wss://ws-auth.kraken.com/v2",
121
-
122
- // IMPORTANT: private WS requires a session token
123
- authToken: process.env.KRAKEN_WS_AUTH_TOKEN,
124
-
125
- // Optional connection tuning:
126
- // autoReconnect: true,
127
- // reconnectDelayMs: 1_000,
128
- // requestTimeoutMs: 10_000,
129
-
130
- // Optional logger:
131
- // logger: console,
132
-
133
- // Optional WS implementation:
134
- // - In Node, ws is used by default.
135
- // - In browsers, pass the browser WebSocket if needed.
136
- // WebSocketImpl: WebSocket,
137
- });
138
- ```
165
+ import { KrakenSpotWebsocketV2Client } from '@lynx-crypto/kraken-api';
139
166
 
140
- Available sub-APIs:
167
+ async function main() {
168
+ const wsClient = new KrakenSpotWebsocketV2Client({
169
+ autoReconnect: false,
170
+ });
141
171
 
142
- - `ws.admin` (public connection)
143
- - `ws.marketData` (public connection)
144
- - `ws.userData` (private connection)
145
- - `ws.userTrading` (private connection)
172
+ await wsClient.publicConnection.connect();
146
173
 
147
- ### Connect
174
+ const ack = await wsClient.marketData.subscribeTrade(
175
+ { symbol: ['BTC/USD'], snapshot: true },
176
+ { reqId: 1 },
177
+ );
148
178
 
149
- You can connect explicitly:
179
+ if (!ack.success) {
180
+ throw new Error(`Subscribe failed: ${ack.error}`);
181
+ }
150
182
 
151
- ```ts
152
- await ws.publicConnection.connect();
153
- await ws.privateConnection.connect();
154
- ```
183
+ const unsubscribe = wsClient.publicConnection.addMessageHandler((msg) => {
184
+ console.log(JSON.stringify(msg));
185
+ });
155
186
 
156
- Or let calls auto-connect (methods like `request()/sendRaw()` will connect if needed).
187
+ setTimeout(() => {
188
+ unsubscribe();
189
+ wsClient.publicConnection.close(1000, 'example done');
190
+ }, 20_000);
191
+ }
157
192
 
158
- ---
193
+ main().catch((err) => {
194
+ console.error(err);
195
+ process.exitCode = 1;
196
+ });
197
+ ```
159
198
 
160
- ## WS routing: receiving streaming messages
199
+ ### Private WebSocket (balances / executions)
161
200
 
162
- The underlying `KrakenWebsocketBase` supports message fan-out:
201
+ Authenticated WebSocket usage requires a token obtained via REST.
163
202
 
164
203
  ```ts
165
- const unsubscribe = ws.publicConnection.addMessageHandler((msg) => {
166
- // msg is already JSON-parsed when possible
167
- // route based on msg.channel / msg.type, etc.
168
- // console.log(msg);
204
+ import 'dotenv/config';
205
+ import {
206
+ KrakenSpotRestClient,
207
+ KrakenSpotWebsocketV2Client,
208
+ } from '@lynx-crypto/kraken-api';
209
+
210
+ function requireEnv(name: string): string {
211
+ const v = process.env[name];
212
+ if (!v) throw new Error(`Missing env var: ${name}`);
213
+ return v;
214
+ }
215
+
216
+ async function main() {
217
+ const rest = new KrakenSpotRestClient({
218
+ userAgent: 'example-app/1.0.0',
219
+ apiKey: requireEnv('KRAKEN_API_KEY'),
220
+ apiSecret: requireEnv('KRAKEN_API_SECRET'),
221
+ });
222
+
223
+ const { token } = await rest.trading.getWebSocketsToken();
224
+
225
+ const wsClient = new KrakenSpotWebsocketV2Client({
226
+ authToken: token,
227
+ autoReconnect: false,
228
+ });
229
+
230
+ await wsClient.privateConnection.connect();
231
+
232
+ const ack = await wsClient.userData.subscribeBalances({}, { reqId: 10 });
233
+
234
+ if (!ack.success) {
235
+ throw new Error(`Subscribe failed: ${ack.error}`);
236
+ }
237
+
238
+ const off = wsClient.privateConnection.addMessageHandler((msg) => {
239
+ console.log(JSON.stringify(msg));
240
+ });
241
+
242
+ setTimeout(() => {
243
+ off();
244
+ wsClient.privateConnection.close(1000, 'example done');
245
+ }, 30_000);
246
+ }
247
+
248
+ main().catch((err) => {
249
+ console.error(err);
250
+ process.exitCode = 1;
169
251
  });
170
-
171
- // later:
172
- unsubscribe();
173
252
  ```
174
253
 
175
254
  ---
176
255
 
177
- ## WS v2: Admin
256
+ ## REST rate limiting & retries
257
+
258
+ This library supports Kraken-style rate limiting with optional automatic retries:
178
259
 
179
- Admin utilities exist on the public connection (ex: ping/status/heartbeat).
260
+ - Lightweight in-memory token bucket limiter by default
261
+ - Automatic retries are configurable
262
+ - Handles:
263
+ - EAPI:Rate limit exceeded
264
+ - EService: Throttled: <unix timestamp>
265
+ - HTTP 429 Too Many Requests
180
266
 
181
267
  Example:
182
268
 
183
269
  ```ts
184
- const pong = await ws.admin.ping({ reqId: 123 });
185
- if (!pong.success) console.error('ping failed:', pong.error);
270
+ const kraken = new KrakenSpotRestClient({
271
+ apiKey: process.env.KRAKEN_API_KEY,
272
+ apiSecret: process.env.KRAKEN_API_SECRET,
273
+ rateLimit: {
274
+ mode: 'auto',
275
+ tier: 'starter',
276
+ retryOnRateLimit: true,
277
+ maxRetries: 5,
278
+ // restCostFn: (path) => (path.includes("Ledgers") ? 2 : 1),
279
+ },
280
+ });
186
281
  ```
187
282
 
188
- ---
189
-
190
- ## WS v2: Market Data (public)
191
-
192
- Market data subscriptions live on ws.marketData (public connection).
193
- (Exact channel helpers depend on your implemented market-data modules.)
283
+ Disable built-in throttling:
194
284
 
195
- Typical pattern:
196
-
197
- 1. call subscribe helper (await ack)
198
- 2. listen via `addMessageHandler` and route messages by channel/type
199
-
200
- ---
285
+ ```ts
286
+ rateLimit: {
287
+ mode: 'off';
288
+ }
289
+ ```
201
290
 
202
- ## WS v2: User Data (authenticated)
291
+ ### Redis rate limiting (multi-process / multi-container)
203
292
 
204
- User-data streams live on `ws.userData` (private connection).
293
+ If you run multiple Node processes, Docker containers, or workers, they all share the same Kraken IP-level limits. In-memory rate limiting only protects a single process.
205
294
 
206
- Implemented channels:
295
+ For cross-process coordination, you can use the Redis-backed token bucket limiter.
207
296
 
208
- - executions (order lifecycle + fills)
209
- - balances (balance snapshots + ledger-derived updates)
297
+ Example (you provide the Redis client + EVAL wrapper):
210
298
 
211
- Example (executions):
299
+ ```ts
300
+ import { KrakenSpotRestClient } from '@lynx-crypto/kraken-api';
301
+ import { RedisTokenBucketLimiter } from '@lynx-crypto/kraken-api/base/redisRateLimit';
302
+
303
+ // Your Redis EVAL wrapper should return a number:
304
+ // - 0 means "proceed now"
305
+ // - >0 means "wait this many ms then retry"
306
+ const evalRedis = async (
307
+ key: string,
308
+ maxCounter: number,
309
+ decayPerSec: number,
310
+ cost: number,
311
+ ttlSeconds: number,
312
+ minWaitMs: number,
313
+ ): Promise<number> => {
314
+ // Example shape (pseudo-code):
315
+ // return await redis.eval(luaScript, { keys: [key], arguments: [maxCounter, decayPerSec, cost, ttlSeconds, minWaitMs] });
316
+ return 0;
317
+ };
212
318
 
213
- ```
214
- const ack = await ws.userData.subscribeExecutions({
215
- snap_trades: true,
216
- snap_orders: true,
217
- order_status: true,
319
+ const kraken = new KrakenSpotRestClient({
320
+ apiKey: process.env.KRAKEN_API_KEY,
321
+ apiSecret: process.env.KRAKEN_API_SECRET,
322
+ rateLimit: {
323
+ mode: 'auto',
324
+ tier: 'starter',
325
+ retryOnRateLimit: true,
326
+ maxRetries: 5,
327
+
328
+ // Cross-process limiter (Redis):
329
+ redis: {
330
+ limiter: new RedisTokenBucketLimiter({
331
+ key: 'kraken:rest:global',
332
+ maxCounter: 15,
333
+ decayPerSec: 0.33,
334
+ ttlSeconds: 30,
335
+ minWaitMs: 50,
336
+ evalRedis,
337
+ }),
338
+ },
339
+ },
218
340
  });
219
-
220
- if (!ack.success) console.error("executions subscribe error:", ack.error);
221
341
  ```
222
342
 
223
- Then route messages:
343
+ Notes:
224
344
 
225
- ```
226
- ws.privateConnection.addMessageHandler((msg: any) => {
227
- if (msg?.channel === "executions" && (msg.type === "snapshot" || msg.type === "update")) {
228
- for (const report of msg.data ?? []) {
229
- console.log("[exec]", report.exec_type, report.order_id, report.order_status);
230
- }
231
- }
232
- });
233
- ```
234
-
235
- Example (balances):
236
-
237
- ````ts
238
- const ack2 = await ws.userData.subscribeBalances({ snapshot: true });
239
- if (!ack2.success) console.error("balances subscribe error:", ack2.error);
240
-
241
- ws.privateConnection.addMessageHandler((msg: any) => {
242
- if (msg?.channel === "balances" && msg.type === "snapshot") {
243
- for (const asset of msg.data ?? []) {
244
- console.log("[balances snapshot]", asset.asset, "total:", asset.balance);
245
- }
246
- }
247
- if (msg?.channel === "balances" && msg.type === "update") {
248
- for (const tx of msg.data ?? []) {
249
- console.log("[balances update]", tx.asset, tx.type, "delta:", tx.amount, "new:", tx.balance);
250
- }
251
- }
252
- });
345
+ - Redis is optional. Only use it when you need cross-process coordination.
346
+ - If Redis is down / eval fails, the request fails (no silent bypass).
253
347
 
254
348
  ---
255
349
 
256
- ## WS v2: User Trading (authenticated RPC)
350
+ ## Examples
257
351
 
258
- User-trading methods live on `ws.userTrading` (private connection).
352
+ The repository includes a comprehensive [examples](./examples) directory:
259
353
 
260
- Implemented RPCs:
354
+ - REST (public + safe private endpoints)
355
+ - WebSocket v2 public streams
356
+ - WebSocket v2 private streams (balances, executions)
357
+ - Clean lifecycle examples (connect → subscribe → receive → close)
261
358
 
262
- - `add_order`
263
- - `amend_order`
264
- - `edit_order` (legacy)
265
- - `cancel_order`
266
- - `cancel_all`
267
- - `cancel_all_orders_after` (Dead Man’s Switch)
268
- - `batch_add`
269
- - `batch_cancel`
270
-
271
- Add order:
272
- ```ts
273
- const res = await ws.userTrading.addOrder({
274
- order_type: "limit",
275
- side: "buy",
276
- symbol: "BTC/USD",
277
- order_qty: 0.01,
278
- limit_price: 30000,
279
- time_in_force: "gtc",
280
- cl_ord_id: "demo-0001",
281
- });
282
-
283
-
284
- if (res.success) console.log("order_id:", res.result?.order_id);
285
- else console.error("add_order error:", res.error);
286
- ````
287
-
288
- Dead Man’s Switch:
289
-
290
- ```
291
- // recommended: refresh every 15–30s with timeout=60
292
- await ws.userTrading.cancelAllOrdersAfter({ timeout: 60 });
293
- ```
359
+ Examples are designed to be:
360
+ - Runnable
361
+ - Safe by default
362
+ - Self-contained
363
+ - Easy to copy into your own project
294
364
 
295
365
  ---
296
366
 
297
- ## Options reference
298
-
299
- ### `KrakenSpotRestClient` options
300
-
301
- - `baseUrl?: string`
302
- Default: https://api.kraken.com
303
- - `timeoutMs?: number`
304
- Default: 10_000
305
- - `userAgent?: string`
306
- - `apiKey?: string`
307
- Required for private endpoints
308
- - `apiSecret?: string` (base64)
309
- Required for private endpoints
310
- - `logger?: KrakenLogger`
311
- debug/info/warn/error(msg, meta?)
312
-
313
- ### KrakenSpotWebsocketV2Client options
314
-
315
- - `publicUrl?: string`
316
- Default: wss://ws.kraken.com/v2
317
- - `privateUrl?: string`
318
- Default: wss://ws-auth.kraken.com/v2
319
- - `authToken?: string`
320
- Required for authenticated/private connection features
321
- - `WebSocketImpl?: constructor`
322
- Optional override (browser / custom WS)
323
- - `autoReconnect?: boolean`
324
- Default: true
325
- - `reconnectDelayMs?: number`
326
- Default: 1000
327
- - `requestTimeoutMs?: number`
328
- Default: 10_000
329
- - `logger?: KrakenWebsocketLogger`
330
- debug/info/warn/error(msg, meta?)
367
+ ## Design Philosophy
368
+
369
+ - Explicit APIs over magic
370
+ - Strong typing without sacrificing usability
371
+ - Clear separation of REST vs WebSocket concerns
372
+ - No hidden background workers
373
+ - Safe defaults, especially for trading operations
331
374
 
332
375
  ---
333
376
 
@@ -349,11 +392,11 @@ Build:
349
392
 
350
393
  ## Security notes
351
394
 
352
- - Keep API keys/secrets out of source control.
353
- - Use least-privilege API key permissions.
395
+ - Keep API keys and secrets out of source control
396
+ - Use least-privilege API key permissions
354
397
 
355
398
  ---
356
399
 
357
400
  ## License
358
401
 
359
- MIT (see LICENSE)
402
+ MIT (see LICENSE.md)