@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 +193 -171
- 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
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
# @lynx-crypto/kraken-api
|
|
2
2
|
|
|
3
|
-

|
|
6
|
-

|
|
9
|
-
](https://github.com/lynx-laboratory/kraken-api/actions/workflows/coverage-badge.yml)
|
|
4
|
+
[](https://github.com/lynx-laboratory/kraken-api/actions/workflows/coverage-badge.yml)
|
|
5
|
+
[](https://www.npmjs.com/package/@lynx-crypto/kraken-api)
|
|
6
|
+
[](https://www.npmjs.com/package/@lynx-crypto/kraken-api)
|
|
7
|
+
[](https://bundlephobia.com/package/@lynx-crypto/kraken-api)
|
|
8
|
+
[](https://github.com/lynx-laboratory/kraken-api/commits/master)
|
|
9
|
+
[](./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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import {
|
|
48
|
+
KrakenSpotRestClient,
|
|
49
|
+
KrakenSpotWebsocketV2Client,
|
|
50
|
+
} from '@lynx-crypto/kraken-api';
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
CJS:
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
//
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
+
// Optional:
|
|
71
|
+
// baseUrl: "https://api.kraken.com",
|
|
72
|
+
// timeoutMs: 10_000,
|
|
73
|
+
// userAgent: "my-app/1.0.0",
|
|
70
74
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
75
|
+
// Required for private endpoints:
|
|
76
|
+
apiKey: process.env.KRAKEN_API_KEY,
|
|
77
|
+
apiSecret: process.env.KRAKEN_API_SECRET,
|
|
74
78
|
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
(
|
|
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
|
-
(
|
|
98
|
+
(Exact private endpoints depend on what you’ve implemented under src/spot/rest.)
|
|
92
99
|
|
|
93
|
-
|
|
100
|
+
```ts
|
|
94
101
|
const balances = await kraken.accountData.getAccountBalance();
|
|
95
|
-
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).
|
|
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
|
|
104
|
-
- a
|
|
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
|
-
//
|
|
111
|
-
//
|
|
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
|
-
|
|
118
|
-
// autoReconnect: true,
|
|
119
|
-
// reconnectDelayMs: 1_000,
|
|
120
|
-
// requestTimeoutMs: 10_000,
|
|
215
|
+
authToken: process.env.KRAKEN_WS_AUTH_TOKEN,
|
|
121
216
|
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
134
|
-
- `ws.
|
|
135
|
-
- `ws.
|
|
136
|
-
- `ws.
|
|
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
|
-
|
|
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(
|
|
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
|
|
197
|
-
- balances
|
|
268
|
+
- `executions`
|
|
269
|
+
- `balances`
|
|
198
270
|
|
|
199
|
-
|
|
200
|
-
|
|
271
|
+
### Executions example
|
|
272
|
+
|
|
273
|
+
```ts
|
|
201
274
|
const ack = await ws.userData.subscribeExecutions({
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
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
|
-
|
|
291
|
+
### Balances example
|
|
292
|
+
|
|
222
293
|
```ts
|
|
223
294
|
const ack2 = await ws.userData.subscribeBalances({ snapshot: true });
|
|
224
|
-
|
|
295
|
+
```
|
|
225
296
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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`
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
|
|
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
|
|
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)
|