@lynx-crypto/kraken-api 0.1.2 → 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 +164 -158
- package/dist/index.cjs +2 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +59 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ TypeScript client for **Kraken SPOT**:
|
|
|
15
15
|
|
|
16
16
|
See [Kraken Official Documentation](https://docs.kraken.com/api/docs/category/guides)
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
## Important
|
|
19
19
|
|
|
20
20
|
- This package is currently **SPOT only**. It does **not** implement Kraken Futures.
|
|
21
21
|
- Unofficial project. Not affiliated with Kraken.
|
|
@@ -25,24 +25,15 @@ IMPORTANT
|
|
|
25
25
|
## Install
|
|
26
26
|
|
|
27
27
|
NPM:
|
|
28
|
-
|
|
29
|
-
```
|
|
30
|
-
npm i @lynx-crypto/kraken-api
|
|
31
|
-
```
|
|
28
|
+
`npm i @lynx-crypto/kraken-api`
|
|
32
29
|
|
|
33
30
|
Yarn:
|
|
34
|
-
|
|
35
|
-
```
|
|
36
|
-
yarn add @lynx-crypto/kraken-api
|
|
37
|
-
```
|
|
31
|
+
`yarn add @lynx-crypto/kraken-api`
|
|
38
32
|
|
|
39
33
|
pnpm:
|
|
34
|
+
`pnpm add @lynx-crypto/kraken-api`
|
|
40
35
|
|
|
41
|
-
|
|
42
|
-
pnpm add @lynx-crypto/kraken-api
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Node support
|
|
36
|
+
### Node support
|
|
46
37
|
|
|
47
38
|
- Node >= 18 recommended (uses built-in fetch / AbortController)
|
|
48
39
|
|
|
@@ -52,14 +43,20 @@ Node support
|
|
|
52
43
|
|
|
53
44
|
ESM:
|
|
54
45
|
|
|
55
|
-
```
|
|
56
|
-
import {
|
|
46
|
+
```ts
|
|
47
|
+
import {
|
|
48
|
+
KrakenSpotRestClient,
|
|
49
|
+
KrakenSpotWebsocketV2Client,
|
|
50
|
+
} from '@lynx-crypto/kraken-api';
|
|
57
51
|
```
|
|
58
52
|
|
|
59
53
|
CJS:
|
|
60
54
|
|
|
61
|
-
```
|
|
62
|
-
const {
|
|
55
|
+
```js
|
|
56
|
+
const {
|
|
57
|
+
KrakenSpotRestClient,
|
|
58
|
+
KrakenSpotWebsocketV2Client,
|
|
59
|
+
} = require('@lynx-crypto/kraken-api');
|
|
63
60
|
```
|
|
64
61
|
|
|
65
62
|
---
|
|
@@ -71,9 +68,9 @@ const { KrakenSpotRestClient, KrakenSpotWebsocketV2Client } = require("@lynx-cry
|
|
|
71
68
|
```ts
|
|
72
69
|
const kraken = new KrakenSpotRestClient({
|
|
73
70
|
// Optional:
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
71
|
+
// baseUrl: "https://api.kraken.com",
|
|
72
|
+
// timeoutMs: 10_000,
|
|
73
|
+
// userAgent: "my-app/1.0.0",
|
|
77
74
|
|
|
78
75
|
// Required for private endpoints:
|
|
79
76
|
apiKey: process.env.KRAKEN_API_KEY,
|
|
@@ -81,26 +78,123 @@ const kraken = new KrakenSpotRestClient({
|
|
|
81
78
|
|
|
82
79
|
// Optional logger:
|
|
83
80
|
// logger: console,
|
|
81
|
+
|
|
82
|
+
// Optional rate limiting:
|
|
83
|
+
// rateLimit: { mode: "auto" },
|
|
84
84
|
});
|
|
85
85
|
```
|
|
86
86
|
|
|
87
87
|
### Public endpoint example
|
|
88
88
|
|
|
89
|
-
(
|
|
90
|
-
|
|
91
|
-
Example shape:
|
|
89
|
+
(Exact public endpoints depend on what you’ve implemented under src/spot/rest.)
|
|
92
90
|
|
|
93
91
|
```ts
|
|
94
92
|
const serverTime = await kraken.public.getServerTime();
|
|
93
|
+
console.log(serverTime);
|
|
95
94
|
```
|
|
96
95
|
|
|
97
96
|
### Private endpoint example
|
|
98
97
|
|
|
99
|
-
(
|
|
98
|
+
(Exact private endpoints depend on what you’ve implemented under src/spot/rest.)
|
|
100
99
|
|
|
101
|
-
|
|
100
|
+
```ts
|
|
102
101
|
const balances = await kraken.accountData.getAccountBalance();
|
|
103
|
-
console.log(
|
|
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).
|
|
104
198
|
|
|
105
199
|
---
|
|
106
200
|
|
|
@@ -108,67 +202,51 @@ console.log("USD:", balances["ZUSD"]);
|
|
|
108
202
|
|
|
109
203
|
This package provides a top-level v2 WS client that creates:
|
|
110
204
|
|
|
111
|
-
- a
|
|
112
|
-
- a
|
|
205
|
+
- a public connection (market data + admin)
|
|
206
|
+
- a private/auth connection (user-data + user-trading)
|
|
113
207
|
|
|
114
208
|
### Create a WS v2 client
|
|
115
209
|
|
|
116
210
|
```ts
|
|
117
211
|
const ws = new KrakenSpotWebsocketV2Client({
|
|
118
|
-
// Optional override URLs:
|
|
119
212
|
// publicUrl: "wss://ws.kraken.com/v2",
|
|
120
213
|
// privateUrl: "wss://ws-auth.kraken.com/v2",
|
|
121
214
|
|
|
122
|
-
// IMPORTANT: private WS requires a session token
|
|
123
215
|
authToken: process.env.KRAKEN_WS_AUTH_TOKEN,
|
|
124
216
|
|
|
125
|
-
// Optional connection tuning:
|
|
126
217
|
// autoReconnect: true,
|
|
127
218
|
// reconnectDelayMs: 1_000,
|
|
128
219
|
// requestTimeoutMs: 10_000,
|
|
129
220
|
|
|
130
|
-
// Optional logger:
|
|
131
221
|
// 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
222
|
// WebSocketImpl: WebSocket,
|
|
137
223
|
});
|
|
138
224
|
```
|
|
139
225
|
|
|
140
226
|
Available sub-APIs:
|
|
141
227
|
|
|
142
|
-
- `ws.admin`
|
|
143
|
-
- `ws.marketData`
|
|
144
|
-
- `ws.userData`
|
|
145
|
-
- `ws.userTrading`
|
|
228
|
+
- `ws.admin`
|
|
229
|
+
- `ws.marketData`
|
|
230
|
+
- `ws.userData`
|
|
231
|
+
- `ws.userTrading`
|
|
146
232
|
|
|
147
233
|
### Connect
|
|
148
234
|
|
|
149
|
-
You can connect explicitly:
|
|
150
|
-
|
|
151
235
|
```ts
|
|
152
236
|
await ws.publicConnection.connect();
|
|
153
237
|
await ws.privateConnection.connect();
|
|
154
238
|
```
|
|
155
239
|
|
|
156
|
-
Or let calls auto-connect (methods like `request()/sendRaw()` will connect if needed).
|
|
157
|
-
|
|
158
240
|
---
|
|
159
241
|
|
|
160
242
|
## WS routing: receiving streaming messages
|
|
161
243
|
|
|
162
|
-
The underlying `KrakenWebsocketBase` supports message fan-out:
|
|
163
|
-
|
|
164
244
|
```ts
|
|
165
245
|
const unsubscribe = ws.publicConnection.addMessageHandler((msg) => {
|
|
166
|
-
//
|
|
167
|
-
// route based on msg.channel / msg.type, etc.
|
|
168
|
-
// console.log(msg);
|
|
246
|
+
// route by msg.channel / msg.type
|
|
169
247
|
});
|
|
170
248
|
|
|
171
|
-
// later
|
|
249
|
+
// later
|
|
172
250
|
unsubscribe();
|
|
173
251
|
```
|
|
174
252
|
|
|
@@ -176,10 +254,6 @@ unsubscribe();
|
|
|
176
254
|
|
|
177
255
|
## WS v2: Admin
|
|
178
256
|
|
|
179
|
-
Admin utilities exist on the public connection (ex: ping/status/heartbeat).
|
|
180
|
-
|
|
181
|
-
Example:
|
|
182
|
-
|
|
183
257
|
```ts
|
|
184
258
|
const pong = await ws.admin.ping({ reqId: 123 });
|
|
185
259
|
if (!pong.success) console.error('ping failed:', pong.error);
|
|
@@ -187,150 +261,82 @@ if (!pong.success) console.error('ping failed:', pong.error);
|
|
|
187
261
|
|
|
188
262
|
---
|
|
189
263
|
|
|
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.)
|
|
194
|
-
|
|
195
|
-
Typical pattern:
|
|
196
|
-
|
|
197
|
-
1. call subscribe helper (await ack)
|
|
198
|
-
2. listen via `addMessageHandler` and route messages by channel/type
|
|
199
|
-
|
|
200
|
-
---
|
|
201
|
-
|
|
202
264
|
## WS v2: User Data (authenticated)
|
|
203
265
|
|
|
204
|
-
User-data streams live on `ws.userData` (private connection).
|
|
205
|
-
|
|
206
266
|
Implemented channels:
|
|
207
267
|
|
|
208
|
-
- executions
|
|
209
|
-
- balances
|
|
268
|
+
- `executions`
|
|
269
|
+
- `balances`
|
|
210
270
|
|
|
211
|
-
|
|
271
|
+
### Executions example
|
|
212
272
|
|
|
213
|
-
```
|
|
273
|
+
```ts
|
|
214
274
|
const ack = await ws.userData.subscribeExecutions({
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
275
|
+
snap_trades: true,
|
|
276
|
+
snap_orders: true,
|
|
277
|
+
order_status: true,
|
|
218
278
|
});
|
|
219
|
-
|
|
220
|
-
if (!ack.success) console.error("executions subscribe error:", ack.error);
|
|
221
279
|
```
|
|
222
280
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
for (const report of msg.data ?? []) {
|
|
229
|
-
console.log("[exec]", report.exec_type, report.order_id, report.order_status);
|
|
230
|
-
}
|
|
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);
|
|
231
286
|
}
|
|
287
|
+
}
|
|
232
288
|
});
|
|
233
289
|
```
|
|
234
290
|
|
|
235
|
-
|
|
291
|
+
### Balances example
|
|
236
292
|
|
|
237
|
-
|
|
293
|
+
```ts
|
|
238
294
|
const ack2 = await ws.userData.subscribeBalances({ snapshot: true });
|
|
239
|
-
|
|
295
|
+
```
|
|
240
296
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
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
|
-
}
|
|
297
|
+
```ts
|
|
298
|
+
ws.privateConnection.addMessageHandler((msg) => {
|
|
299
|
+
if (msg?.channel === 'balances') {
|
|
300
|
+
console.log(msg.data);
|
|
301
|
+
}
|
|
252
302
|
});
|
|
303
|
+
```
|
|
253
304
|
|
|
254
305
|
---
|
|
255
306
|
|
|
256
307
|
## WS v2: User Trading (authenticated RPC)
|
|
257
308
|
|
|
258
|
-
User-trading methods live on `ws.userTrading` (private connection).
|
|
259
|
-
|
|
260
309
|
Implemented RPCs:
|
|
261
310
|
|
|
262
311
|
- `add_order`
|
|
263
312
|
- `amend_order`
|
|
264
|
-
- `edit_order` (legacy)
|
|
265
313
|
- `cancel_order`
|
|
266
314
|
- `cancel_all`
|
|
267
|
-
- `cancel_all_orders_after`
|
|
315
|
+
- `cancel_all_orders_after`
|
|
268
316
|
- `batch_add`
|
|
269
317
|
- `batch_cancel`
|
|
270
318
|
|
|
271
319
|
Add order:
|
|
320
|
+
|
|
272
321
|
```ts
|
|
273
322
|
const res = await ws.userTrading.addOrder({
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
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',
|
|
281
329
|
});
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
if (res.success) console.log("order_id:", res.result?.order_id);
|
|
285
|
-
else console.error("add_order error:", res.error);
|
|
286
|
-
````
|
|
330
|
+
```
|
|
287
331
|
|
|
288
332
|
Dead Man’s Switch:
|
|
289
333
|
|
|
290
|
-
```
|
|
291
|
-
// recommended: refresh every 15–30s with timeout=60
|
|
334
|
+
```ts
|
|
292
335
|
await ws.userTrading.cancelAllOrdersAfter({ timeout: 60 });
|
|
293
336
|
```
|
|
294
337
|
|
|
295
338
|
---
|
|
296
339
|
|
|
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?)
|
|
331
|
-
|
|
332
|
-
---
|
|
333
|
-
|
|
334
340
|
## Development
|
|
335
341
|
|
|
336
342
|
Install:
|
|
@@ -349,11 +355,11 @@ Build:
|
|
|
349
355
|
|
|
350
356
|
## Security notes
|
|
351
357
|
|
|
352
|
-
- Keep API keys
|
|
353
|
-
- Use least-privilege API key permissions
|
|
358
|
+
- Keep API keys and secrets out of source control
|
|
359
|
+
- Use least-privilege API key permissions
|
|
354
360
|
|
|
355
361
|
---
|
|
356
362
|
|
|
357
363
|
## License
|
|
358
364
|
|
|
359
|
-
MIT (see LICENSE)
|
|
365
|
+
MIT (see LICENSE.md)
|