@lynx-crypto/kraken-api 0.1.1 → 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 CHANGED
@@ -1,20 +1,21 @@
1
1
  # @lynx-crypto/kraken-api
2
2
 
3
- ![Build](https://github.com/lynx-laboratory/kraken-api/actions/workflows/ci.yml/badge.svg)
4
- ![Coverage](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/lynx-laboratory/kraken-api/master/badges/coverage.json)
5
- ![npm](https://img.shields.io/npm/v/@lynx-crypto/kraken-api)
6
- ![minzipped size](https://img.shields.io/bundlephobia/minzip/@lynx-crypto/kraken-api)
7
- ![downloads](https://img.shields.io/npm/dm/@lynx-crypto/kraken-api)
8
- ![last commit](https://img.shields.io/github/last-commit/lynx-laboratory/kraken-api)
9
- ![license](https://img.shields.io/npm/l/@lynx-crypto/kraken-api)
3
+ [![Build](https://img.shields.io/github/actions/workflow/status/lynx-laboratory/kraken-api/coverage-badge.yml?branch=master)](https://github.com/lynx-laboratory/kraken-api/actions/workflows/coverage-badge.yml)
4
+ [![Coverage](https://github.com/lynx-laboratory/kraken-api/raw/master/badges/coverage.svg)](https://github.com/lynx-laboratory/kraken-api/actions/workflows/coverage-badge.yml)
5
+ [![npm](https://img.shields.io/npm/v/%40lynx-crypto%2Fkraken-api)](https://www.npmjs.com/package/@lynx-crypto/kraken-api)
6
+ [![downloads](https://img.shields.io/npm/dm/%40lynx-crypto%2Fkraken-api)](https://www.npmjs.com/package/@lynx-crypto/kraken-api)
7
+ [![bundlephobia](https://img.shields.io/bundlephobia/minzip/%40lynx-crypto%2Fkraken-api)](https://bundlephobia.com/package/@lynx-crypto/kraken-api)
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
+ [![license](https://img.shields.io/github/license/lynx-laboratory/kraken-api)](./LICENSE.md)
10
10
 
11
11
  TypeScript client for **Kraken SPOT**:
12
+
12
13
  - **REST API** (public + private endpoints)
13
14
  - **WebSocket v2** (public market-data + authenticated user-data/trading)
14
15
 
15
16
  See [Kraken Official Documentation](https://docs.kraken.com/api/docs/category/guides)
16
17
 
17
- IMPORTANT
18
+ ## Important
18
19
 
19
20
  - This package is currently **SPOT only**. It does **not** implement Kraken Futures.
20
21
  - Unofficial project. Not affiliated with Kraken.
@@ -24,21 +25,16 @@ IMPORTANT
24
25
  ## Install
25
26
 
26
27
  NPM:
27
- ```
28
- npm i @lynx-crypto/kraken-api
29
- ```
28
+ `npm i @lynx-crypto/kraken-api`
30
29
 
31
30
  Yarn:
32
- ```
33
- yarn add @lynx-crypto/kraken-api
34
- ```
31
+ `yarn add @lynx-crypto/kraken-api`
35
32
 
36
33
  pnpm:
37
- ```
38
- pnpm add @lynx-crypto/kraken-api
39
- ```
34
+ `pnpm add @lynx-crypto/kraken-api`
35
+
36
+ ### Node support
40
37
 
41
- Node support
42
38
  - Node >= 18 recommended (uses built-in fetch / AbortController)
43
39
 
44
40
  ---
@@ -46,13 +42,21 @@ Node support
46
42
  ## Quick start
47
43
 
48
44
  ESM:
49
- ```
50
- import { KrakenSpotRestClient, KrakenSpotWebsocketV2Client } from "@lynx-crypto/kraken-api";
45
+
46
+ ```ts
47
+ import {
48
+ KrakenSpotRestClient,
49
+ KrakenSpotWebsocketV2Client,
50
+ } from '@lynx-crypto/kraken-api';
51
51
  ```
52
52
 
53
53
  CJS:
54
- ```
55
- const { KrakenSpotRestClient, KrakenSpotWebsocketV2Client } = require("@lynx-crypto/kraken-api");
54
+
55
+ ```js
56
+ const {
57
+ KrakenSpotRestClient,
58
+ KrakenSpotWebsocketV2Client,
59
+ } = require('@lynx-crypto/kraken-api');
56
60
  ```
57
61
 
58
62
  ---
@@ -63,36 +67,134 @@ const { KrakenSpotRestClient, KrakenSpotWebsocketV2Client } = require("@lynx-cry
63
67
 
64
68
  ```ts
65
69
  const kraken = new KrakenSpotRestClient({
66
- // Optional:
67
- // baseUrl: "https://api.kraken.com",
68
- // timeoutMs: 10_000,
69
- // userAgent: "my-app/1.0.0",
70
+ // Optional:
71
+ // baseUrl: "https://api.kraken.com",
72
+ // timeoutMs: 10_000,
73
+ // userAgent: "my-app/1.0.0",
70
74
 
71
- // Required for private endpoints:
72
- apiKey: process.env.KRAKEN_API_KEY,
73
- apiSecret: process.env.KRAKEN_API_SECRET,
75
+ // Required for private endpoints:
76
+ apiKey: process.env.KRAKEN_API_KEY,
77
+ apiSecret: process.env.KRAKEN_API_SECRET,
74
78
 
75
- // Optional logger:
76
- // logger: console,
79
+ // Optional logger:
80
+ // logger: console,
81
+
82
+ // Optional rate limiting:
83
+ // rateLimit: { mode: "auto" },
77
84
  });
78
85
  ```
79
86
 
80
87
  ### Public endpoint example
81
88
 
82
- (Your exact public endpoints depend on what you’ve implemented in src/spot/rest.)
89
+ (Exact public endpoints depend on what you’ve implemented under src/spot/rest.)
83
90
 
84
- Example shape:
85
91
  ```ts
86
92
  const serverTime = await kraken.public.getServerTime();
93
+ console.log(serverTime);
87
94
  ```
88
95
 
89
96
  ### Private endpoint example
90
97
 
91
- (Your exact private endpoints depend on what you’ve implemented in src/spot/rest.)
98
+ (Exact private endpoints depend on what you’ve implemented under src/spot/rest.)
92
99
 
93
- Example shape:
100
+ ```ts
94
101
  const balances = await kraken.accountData.getAccountBalance();
95
- console.log("USD:", balances["ZUSD"]);
102
+ console.log('USD:', balances['ZUSD']);
103
+ ```
104
+
105
+ ---
106
+
107
+ ## REST rate limiting & retries
108
+
109
+ This library supports Kraken-style rate limiting with optional automatic retries:
110
+
111
+ - Lightweight in-memory token bucket limiter by default
112
+ - Automatic retries are configurable
113
+ - Handles:
114
+ - EAPI:Rate limit exceeded
115
+ - EService: Throttled: <unix timestamp>
116
+ - HTTP 429 Too Many Requests
117
+
118
+ Example:
119
+
120
+ ```ts
121
+ const kraken = new KrakenSpotRestClient({
122
+ apiKey: process.env.KRAKEN_API_KEY,
123
+ apiSecret: process.env.KRAKEN_API_SECRET,
124
+ rateLimit: {
125
+ mode: 'auto',
126
+ tier: 'starter',
127
+ retryOnRateLimit: true,
128
+ maxRetries: 5,
129
+ // restCostFn: (path) => (path.includes("Ledgers") ? 2 : 1),
130
+ },
131
+ });
132
+ ```
133
+
134
+ Disable built-in throttling:
135
+
136
+ ```ts
137
+ rateLimit: {
138
+ mode: 'off';
139
+ }
140
+ ```
141
+
142
+ ### Redis rate limiting (multi-process / multi-container)
143
+
144
+ 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.
145
+
146
+ For cross-process coordination, you can use the Redis-backed token bucket limiter.
147
+
148
+ Example (you provide the Redis client + EVAL wrapper):
149
+
150
+ ```ts
151
+ import { KrakenSpotRestClient } from '@lynx-crypto/kraken-api';
152
+ import { RedisTokenBucketLimiter } from '@lynx-crypto/kraken-api/base/redisRateLimit';
153
+
154
+ // Your Redis EVAL wrapper should return a number:
155
+ // - 0 means "proceed now"
156
+ // - >0 means "wait this many ms then retry"
157
+ const evalRedis = async (
158
+ key: string,
159
+ maxCounter: number,
160
+ decayPerSec: number,
161
+ cost: number,
162
+ ttlSeconds: number,
163
+ minWaitMs: number,
164
+ ): Promise<number> => {
165
+ // Example shape (pseudo-code):
166
+ // return await redis.eval(luaScript, { keys: [key], arguments: [maxCounter, decayPerSec, cost, ttlSeconds, minWaitMs] });
167
+ return 0;
168
+ };
169
+
170
+ const kraken = new KrakenSpotRestClient({
171
+ apiKey: process.env.KRAKEN_API_KEY,
172
+ apiSecret: process.env.KRAKEN_API_SECRET,
173
+ rateLimit: {
174
+ mode: 'auto',
175
+ tier: 'starter',
176
+ retryOnRateLimit: true,
177
+ maxRetries: 5,
178
+
179
+ // Cross-process limiter (Redis):
180
+ redis: {
181
+ limiter: new RedisTokenBucketLimiter({
182
+ key: 'kraken:rest:global',
183
+ maxCounter: 15,
184
+ decayPerSec: 0.33,
185
+ ttlSeconds: 30,
186
+ minWaitMs: 50,
187
+ evalRedis,
188
+ }),
189
+ },
190
+ },
191
+ });
192
+ ```
193
+
194
+ Notes:
195
+
196
+ - Redis is optional. Only use it when you need cross-process coordination.
197
+ - If Redis is down / eval fails, the request fails (no silent bypass).
96
198
 
97
199
  ---
98
200
 
@@ -100,65 +202,51 @@ console.log("USD:", balances["ZUSD"]);
100
202
 
101
203
  This package provides a top-level v2 WS client that creates:
102
204
 
103
- - a **public connection** (market data + admin)
104
- - a **private/auth connection** (user-data + user-trading)
205
+ - a public connection (market data + admin)
206
+ - a private/auth connection (user-data + user-trading)
105
207
 
106
208
  ### Create a WS v2 client
107
209
 
108
210
  ```ts
109
211
  const ws = new KrakenSpotWebsocketV2Client({
110
- // Optional override URLs:
111
- // publicUrl: "wss://ws.kraken.com/v2",
112
- // privateUrl: "wss://ws-auth.kraken.com/v2",
113
-
114
- // IMPORTANT: private WS requires a session token
115
- authToken: process.env.KRAKEN_WS_AUTH_TOKEN,
212
+ // publicUrl: "wss://ws.kraken.com/v2",
213
+ // privateUrl: "wss://ws-auth.kraken.com/v2",
116
214
 
117
- // Optional connection tuning:
118
- // autoReconnect: true,
119
- // reconnectDelayMs: 1_000,
120
- // requestTimeoutMs: 10_000,
215
+ authToken: process.env.KRAKEN_WS_AUTH_TOKEN,
121
216
 
122
- // Optional logger:
123
- // logger: console,
124
-
125
- // Optional WS implementation:
126
- // - In Node, ws is used by default.
127
- // - In browsers, pass the browser WebSocket if needed.
128
- // WebSocketImpl: WebSocket,
217
+ // autoReconnect: true,
218
+ // reconnectDelayMs: 1_000,
219
+ // requestTimeoutMs: 10_000,
129
220
 
221
+ // logger: console,
222
+ // WebSocketImpl: WebSocket,
130
223
  });
131
224
  ```
225
+
132
226
  Available sub-APIs:
133
- - `ws.admin` (public connection)
134
- - `ws.marketData` (public connection)
135
- - `ws.userData` (private connection)
136
- - `ws.userTrading` (private connection)
227
+
228
+ - `ws.admin`
229
+ - `ws.marketData`
230
+ - `ws.userData`
231
+ - `ws.userTrading`
137
232
 
138
233
  ### Connect
139
234
 
140
- You can connect explicitly:
141
235
  ```ts
142
236
  await ws.publicConnection.connect();
143
237
  await ws.privateConnection.connect();
144
238
  ```
145
239
 
146
- Or let calls auto-connect (methods like `request()/sendRaw()` will connect if needed).
147
-
148
240
  ---
149
241
 
150
242
  ## WS routing: receiving streaming messages
151
243
 
152
- The underlying `KrakenWebsocketBase` supports message fan-out:
153
-
154
244
  ```ts
155
245
  const unsubscribe = ws.publicConnection.addMessageHandler((msg) => {
156
- // msg is already JSON-parsed when possible
157
- // route based on msg.channel / msg.type, etc.
158
- // console.log(msg);
246
+ // route by msg.channel / msg.type
159
247
  });
160
248
 
161
- // later:
249
+ // later
162
250
  unsubscribe();
163
251
  ```
164
252
 
@@ -166,155 +254,89 @@ unsubscribe();
166
254
 
167
255
  ## WS v2: Admin
168
256
 
169
- Admin utilities exist on the public connection (ex: ping/status/heartbeat).
170
-
171
- Example:
172
257
  ```ts
173
258
  const pong = await ws.admin.ping({ reqId: 123 });
174
- if (!pong.success) console.error("ping failed:", pong.error);
259
+ if (!pong.success) console.error('ping failed:', pong.error);
175
260
  ```
176
261
 
177
262
  ---
178
263
 
179
- ## WS v2: Market Data (public)
180
-
181
- Market data subscriptions live on ws.marketData (public connection).
182
- (Exact channel helpers depend on your implemented market-data modules.)
183
-
184
- Typical pattern:
185
- 1. call subscribe helper (await ack)
186
- 2. listen via `addMessageHandler` and route messages by channel/type
187
-
188
- ---
189
-
190
264
  ## WS v2: User Data (authenticated)
191
265
 
192
- User-data streams live on `ws.userData` (private connection).
193
-
194
266
  Implemented channels:
195
267
 
196
- - executions (order lifecycle + fills)
197
- - balances (balance snapshots + ledger-derived updates)
268
+ - `executions`
269
+ - `balances`
198
270
 
199
- Example (executions):
200
- ```
271
+ ### Executions example
272
+
273
+ ```ts
201
274
  const ack = await ws.userData.subscribeExecutions({
202
- snap_trades: true,
203
- snap_orders: true,
204
- order_status: true,
275
+ snap_trades: true,
276
+ snap_orders: true,
277
+ order_status: true,
205
278
  });
206
-
207
- if (!ack.success) console.error("executions subscribe error:", ack.error);
208
279
  ```
209
280
 
210
- Then route messages:
211
- ```
212
- ws.privateConnection.addMessageHandler((msg: any) => {
213
- if (msg?.channel === "executions" && (msg.type === "snapshot" || msg.type === "update")) {
214
- for (const report of msg.data ?? []) {
215
- console.log("[exec]", report.exec_type, report.order_id, report.order_status);
216
- }
281
+ ```ts
282
+ ws.privateConnection.addMessageHandler((msg) => {
283
+ if (msg?.channel === 'executions') {
284
+ for (const report of msg.data ?? []) {
285
+ console.log(report.order_id, report.order_status);
217
286
  }
287
+ }
218
288
  });
219
289
  ```
220
290
 
221
- Example (balances):
291
+ ### Balances example
292
+
222
293
  ```ts
223
294
  const ack2 = await ws.userData.subscribeBalances({ snapshot: true });
224
- if (!ack2.success) console.error("balances subscribe error:", ack2.error);
295
+ ```
225
296
 
226
- ws.privateConnection.addMessageHandler((msg: any) => {
227
- if (msg?.channel === "balances" && msg.type === "snapshot") {
228
- for (const asset of msg.data ?? []) {
229
- console.log("[balances snapshot]", asset.asset, "total:", asset.balance);
230
- }
231
- }
232
- if (msg?.channel === "balances" && msg.type === "update") {
233
- for (const tx of msg.data ?? []) {
234
- console.log("[balances update]", tx.asset, tx.type, "delta:", tx.amount, "new:", tx.balance);
235
- }
236
- }
297
+ ```ts
298
+ ws.privateConnection.addMessageHandler((msg) => {
299
+ if (msg?.channel === 'balances') {
300
+ console.log(msg.data);
301
+ }
237
302
  });
303
+ ```
238
304
 
239
305
  ---
240
306
 
241
307
  ## WS v2: User Trading (authenticated RPC)
242
308
 
243
- User-trading methods live on `ws.userTrading` (private connection).
244
-
245
309
  Implemented RPCs:
246
310
 
247
311
  - `add_order`
248
312
  - `amend_order`
249
- - `edit_order` (legacy)
250
313
  - `cancel_order`
251
314
  - `cancel_all`
252
- - `cancel_all_orders_after` (Dead Man’s Switch)
315
+ - `cancel_all_orders_after`
253
316
  - `batch_add`
254
317
  - `batch_cancel`
255
318
 
256
319
  Add order:
320
+
257
321
  ```ts
258
322
  const res = await ws.userTrading.addOrder({
259
- order_type: "limit",
260
- side: "buy",
261
- symbol: "BTC/USD",
262
- order_qty: 0.01,
263
- limit_price: 30000,
264
- time_in_force: "gtc",
265
- cl_ord_id: "demo-0001",
323
+ order_type: 'limit',
324
+ side: 'buy',
325
+ symbol: 'BTC/USD',
326
+ order_qty: 0.01,
327
+ limit_price: 30000,
328
+ time_in_force: 'gtc',
266
329
  });
267
-
268
-
269
- if (res.success) console.log("order_id:", res.result?.order_id);
270
- else console.error("add_order error:", res.error);
271
330
  ```
272
331
 
273
332
  Dead Man’s Switch:
274
- ```
275
- // recommended: refresh every 15–30s with timeout=60
333
+
334
+ ```ts
276
335
  await ws.userTrading.cancelAllOrdersAfter({ timeout: 60 });
277
336
  ```
278
337
 
279
338
  ---
280
339
 
281
- ## Options reference
282
-
283
- ### `KrakenSpotRestClient` options
284
-
285
- - `baseUrl?: string`
286
- Default: https://api.kraken.com
287
- - `timeoutMs?: number`
288
- Default: 10_000
289
- - `userAgent?: string`
290
- - `apiKey?: string`
291
- Required for private endpoints
292
- - `apiSecret?: string` (base64)
293
- Required for private endpoints
294
- - `logger?: KrakenLogger`
295
- debug/info/warn/error(msg, meta?)
296
-
297
- ### KrakenSpotWebsocketV2Client options
298
-
299
- - `publicUrl?: string`
300
- Default: wss://ws.kraken.com/v2
301
- - `privateUrl?: string`
302
- Default: wss://ws-auth.kraken.com/v2
303
- - `authToken?: string`
304
- Required for authenticated/private connection features
305
- - `WebSocketImpl?: constructor`
306
- Optional override (browser / custom WS)
307
- - `autoReconnect?: boolean`
308
- Default: true
309
- - `reconnectDelayMs?: number`
310
- Default: 1000
311
- - `requestTimeoutMs?: number`
312
- Default: 10_000
313
- - `logger?: KrakenWebsocketLogger`
314
- debug/info/warn/error(msg, meta?)
315
-
316
- ---
317
-
318
340
  ## Development
319
341
 
320
342
  Install:
@@ -333,11 +355,11 @@ Build:
333
355
 
334
356
  ## Security notes
335
357
 
336
- - Keep API keys/secrets out of source control.
337
- - Use least-privilege API key permissions.
358
+ - Keep API keys and secrets out of source control
359
+ - Use least-privilege API key permissions
338
360
 
339
361
  ---
340
362
 
341
363
  ## License
342
364
 
343
- MIT (see LICENSE)
365
+ MIT (see LICENSE.md)