-
Notifications
You must be signed in to change notification settings - Fork 4
/
index.ts
628 lines (482 loc) · 22 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
import * as axiosDefault from 'axios';
import { AxiosRequestConfig, AxiosResponse } from 'axios';
import * as crypto from 'crypto';
import * as qs from 'qs';
/**
* Just an alias.
*/
const axios = axiosDefault.default;
/**
* Default configuration.
*/
const defaultConfig = {
rootUrl: `https://api.kraken.com`,
timeout: 15000,
version: 0,
};
/**
* Default HTTP agent configuration.
*/
const defaultAgentConfig = {
baseURL: defaultConfig.rootUrl,
headers: {
'User-Agent': `Kraken API Client (kraken-cryptoexchange-api node package)`,
},
method : 'GET',
timeout: defaultConfig.timeout,
};
/**
* The public agent is essentially an alias for the default configuration.
*
* @type {{}}
*/
const publicAgentConfig = {
...defaultAgentConfig,
};
/**
* The private agent begins life the same as the public agent, but with 'POST' specified.
*
* @type {{method: string}}
*/
const privateAgentConfig = {
...defaultAgentConfig,
method: 'POST',
};
/**
* The post body shape.
*
* Nonce and the optional otp parameter are the only knowns. Other parameters may be passed depending on the
* endpoint being called. This accounts for declaration of string keys with string or number values.
*/
export interface IPostBody {
[key: string]: string | number;
nonce: number;
otp?: string;
}
/**
* This function is exported so that a user can experiment with/understand how Kraken wants requests to be signed.
* Essentially, for user edification ;).
*
* @param {string} path
* @param {IPostBody} postBody
* @param {string} privateKey
* @returns {string}
*/
export const signMessage = (path: string, postBody: IPostBody, privateKey: string): string => {
const message = qs.stringify(postBody);
const decryptedKey = new Buffer(privateKey, 'base64');
// Hash the post data
const hashDigest = crypto.createHash('sha256')
.update(postBody.nonce + message)
.digest('latin1');
// Return the HMAC digest
return crypto.createHmac('sha512', decryptedKey)
.update(path + hashDigest, 'latin1')
.digest('base64');
};
/**
* Generates a new nonce.
*
* @returns {number}
*/
//tslint:disable:no-magic-numbers
export const generateNonce = (): number => Date.now() * 1000;
//tslint:enable:no-magic-numbers
/**
* Convenient container for API keys.
*/
export interface IApiAuth {
publicKey: string;
privateKey: string;
}
/**
* The shape of a raw agent.
*/
export interface IRawAgent {
auth?: IApiAuth;
isUpgraded(): boolean;
getPublicEndpoint(endpoint: string,
queryParams?: {},
configOverride?: IKrakenRequestConfig): Promise<IKrakenResponse>;
postToPrivateEndpoint(endpoint: string,
data: IPostBody,
configOverride?: IKrakenRequestConfig): Promise<IKrakenResponse>;
signMessage(path: string, postBody: IPostBody, privateKey: string): string;
upgrade(newAuth: IApiAuth): void;
}
const getRawAgent = (auth?: IApiAuth): IRawAgent => ({
/**
* This holds the user's API keys.
*/
auth,
/**
* Fetches data from the public (unauthenticated) endpoints.
*
* @param {string} endpoint
* @param {{}} queryParams
* @param {{}} configOverride
* @returns {Promise<IKrakenResponse>}
*/
async getPublicEndpoint(endpoint: string,
queryParams?: {},
configOverride?: IKrakenRequestConfig): Promise<IKrakenResponse> {
// Construct local krakenRequestConfig object
const config = { ...defaultConfig, ...configOverride };
// The uri is a relative path to the publicAgentConfig,baseUrl
const uri = `/${config.version}/public/${endpoint}?${qs.stringify(queryParams)}`;
// Construct the actual krakenRequestConfig to be used
const agentConfig = { ...publicAgentConfig, url: uri, ...config };
// Finally, send the request and return the response
return Promise.resolve(await axios(agentConfig));
},
/**
* Checks if the user has supplied API keys.
*
* @returns {boolean}
*/
isUpgraded(): boolean { return this.auth; },
/**
* Posts to the private (authenticated) endpoints. If no API keys have been provided, this function will fail.
*
* @param {string} endpoint
* @param {IPostBody} data
* @param {IKrakenRequestConfig} configOverride
* @returns {Promise<IKrakenResponse>}
*/
async postToPrivateEndpoint(endpoint: string,
data: IPostBody,
configOverride?: IKrakenRequestConfig): Promise<IKrakenResponse> {
// Ensure the user has credentials
if (!this.isUpgraded()) return Promise.reject(`api keys are required to access private endpoints`);
// Construct local krakenRequestConfig object
const config = { ...defaultConfig, ...configOverride };
// The uri is a relative path to the privateAgentConfig,baseUrl
const uri = `/${config.version}/private/${endpoint}`;
const headersOverride = config ? config.headers : null;
// Add the appropriate POST request headers (API-Key and API-Sign)
const headers = {
...privateAgentConfig.headers,
'API-Key' : this.auth.publicKey,
'API-Sign': this.signMessage(uri, data, this.auth.privateKey),
...headersOverride,
};
// Construct the actual krakenRequestConfig to be used
const agentConfig = { ...privateAgentConfig, headers, url: uri, data: qs.stringify(data), ...config };
// Finally, send the request and return the response
return Promise.resolve(await axios(agentConfig));
},
/**
* Include the exported #signMessage function for convenience.
*/
signMessage,
/**
* Upgrades a client with new credentials.
*
* @param {IApiAuth} newAuth
*/
upgrade(newAuth: IApiAuth): void { this.auth = newAuth; },
});
// public market data
export type IAssetsParams = { info?: string, aclass?: string, asset?: string };
export type IAssetPairsParams = { info?: string, pair?: string };
export type ITickerParams = { pair: string };
export type IOhlcParams = { pair: string, interval?: number, since?: number };
export type IDepthParams = { pair: string, count?: number };
export type ITradesParams = { pair: string, since?: number };
export type ISpreadParams = { pair: string, since?: number };
// private user data
export type ITradeBalanceParams = { aclass?: string, asset?: string };
export type IOpenOrdersParams = { trades?: boolean, userref?: string };
export type IClosedOrdersParams =
{ trades?: boolean, userref?: string, start?: string, end?: string, ofs?: number, closetime?: string };
export type IOrdersInfoParams = { trades?: true, userref?: string, txid: string; };
export type ITradesHistoryParams = { type?: string, trades?: boolean, start?: string, end?: string, ofs?: number };
export type ITradesInfoParams = { txid: string, trades?: boolean };
export type IOpenPositionsParams = { txid: string, docalcs?: boolean };
export type ILedgersParams =
{ aclass?: string, asset?: string, type?: string, start?: string, end?: string, ofs?: number };
export type IQueryLedgersParams = { id: string };
export type ITradeVolumeParams = { pair?: string, 'fee-info'?: string };
// private user trading
export type IAddOrderParams = {
[key: string]: string | number | boolean,
pair: string,
type: string,
ordertype: string,
price?: number,
price2?: number,
volume: number,
leverage?: number,
oflags?: string,
starttm?: string,
expiretm?: string,
userref?: string,
validate?: boolean,
'close[ordertype]'?: string,
'close[price]'?: number,
'close[price2]'?: number,
};
export type ICancelOpenOrderParams = { txid: string; };
// private user funding
export type IDepositMethodsParams = { aclass?: string, asset: string };
export type IDepositAddressesParams = {
[key: string]: string | boolean,
aclass?: string,
asset: string,
method: string,
'new'?: boolean,
};
export type IDepositStatusParams = { aclass?: string, asset: string, method: string };
export type IWithdrawInfoParams = { aclass?: string, asset: string, key: string, amount: number };
export type IWithdrawParams = { aclass?: string, asset: string, key: string, amount: number };
export type IWithdrawStatusParams = { aclass?: string, asset: string, method?: string };
export type IWithdrawCancelParams = { aclass?: string, asset: string, refid: string };
/**
* The user client shape.
*/
export interface IKrakenClient {
rawAgent: IRawAgent;
isUpgraded(): boolean;
upgrade(newAuth: IApiAuth): void;
getServerTime(): Promise<IKrakenResponse>;
getAssetInfo(queryParams?: IAssetsParams): Promise<IKrakenResponse>;
getTradableAssetPairs(queryParams?: IAssetPairsParams): Promise<IKrakenResponse>;
getTickerInformation(queryParams: ITickerParams): Promise<IKrakenResponse>;
getOhlcData(queryParams: IOhlcParams): Promise<IKrakenResponse>;
getOrderBook(queryParams: IDepthParams): Promise<IKrakenResponse>;
getRecentTrades(queryParams: ITradesParams): Promise<IKrakenResponse>;
getRecentSpreadData(queryParams: ISpreadParams): Promise<IKrakenResponse>;
getAccountBalance(): Promise<IKrakenResponse>;
getTradeBalance(queryParams?: ITradeBalanceParams): Promise<IKrakenResponse>;
getOpenOrders(queryParams?: IOpenOrdersParams): Promise<IKrakenResponse>;
getClosedOrders(queryParams?: IClosedOrdersParams): Promise<IKrakenResponse>;
queryOrdersInfo(queryParams: IOrdersInfoParams): Promise<IKrakenResponse>;
getTradesHistory(queryParams?: ITradesHistoryParams): Promise<IKrakenResponse>;
queryTradesInfo(queryParams: ITradesInfoParams): Promise<IKrakenResponse>;
getOpenPositions(queryParams: IOpenPositionsParams): Promise<IKrakenResponse>;
getLedgersInfo(queryParams?: ILedgersParams): Promise<IKrakenResponse>;
queryLedgers(queryParams: IQueryLedgersParams): Promise<IKrakenResponse>;
getTradeVolume(queryParams?: ITradeVolumeParams): Promise<IKrakenResponse>;
addStandardOrder(queryParams: IAddOrderParams): Promise<IKrakenResponse>;
cancelOpenOrder(queryParams: ICancelOpenOrderParams): Promise<IKrakenResponse>;
getDepositMethods(queryParams: IDepositMethodsParams): Promise<IKrakenResponse>;
getDepositAddresses(queryParams: IDepositAddressesParams): Promise<IKrakenResponse>;
getStatusOfRecentDeposits(queryParams: IDepositStatusParams): Promise<IKrakenResponse>;
getWithdrawalInformation(queryParams: IWithdrawInfoParams): Promise<IKrakenResponse>;
withdrawFunds(queryParams: IWithdrawParams): Promise<IKrakenResponse>;
getStatusOfRecentWithdrawals(queryParams: IWithdrawStatusParams): Promise<IKrakenResponse>;
requestWithdrawalCancellation(queryParams: IWithdrawCancelParams): Promise<IKrakenResponse>;
}
/**
* Factory function to get a new Kraken client.
*
* @param {IApiAuth} auth
* @param requestConfig
* @returns {IKrakenClient}
*/
export const getClient = (auth?: IApiAuth, requestConfig: IKrakenRequestConfig = null): IKrakenClient => ({
rawAgent: getRawAgent(auth),
isUpgraded(): boolean { return this.rawAgent.isUpgraded(); },
upgrade(newAuth: IApiAuth): void { this.rawAgent.upgrade(newAuth); },
async getServerTime(): Promise<IKrakenResponse> {
return this.rawAgent.getPublicEndpoint('Time', null, requestConfig);
},
async getAssetInfo(queryParams?: IAssetsParams): Promise<IKrakenResponse> {
const params = queryParams ?
(({ info, aclass, asset }) =>
({ info, aclass, asset }))(queryParams) :
null;
return this.rawAgent.getPublicEndpoint('Assets', params, requestConfig);
},
async getTradableAssetPairs(queryParams?: IAssetPairsParams): Promise<IKrakenResponse> {
const params = queryParams ?
(({ info, pair }) =>
({ info, pair }))(queryParams) :
null;
return this.rawAgent.getPublicEndpoint('AssetPairs', params, requestConfig);
},
async getTickerInformation(queryParams: ITickerParams): Promise<IKrakenResponse> {
const params = (({ pair }) =>
({ pair }))(queryParams);
return this.rawAgent.getPublicEndpoint('Ticker', params, requestConfig);
},
async getOhlcData(queryParams: IOhlcParams): Promise<IKrakenResponse> {
const params = (({ pair, interval, since }) =>
({ pair, interval, since }))(queryParams);
return this.rawAgent.getPublicEndpoint('OHLC', params, requestConfig);
},
async getOrderBook(queryParams: IDepthParams): Promise<IKrakenResponse> {
const params = (({ pair, count }) =>
({ pair, count }))(queryParams);
return this.rawAgent.getPublicEndpoint('Depth', params, requestConfig);
},
async getRecentTrades(queryParams: ITradesParams): Promise<IKrakenResponse> {
const params = (({ pair, since }) =>
({ pair, since }))(queryParams);
return this.rawAgent.getPublicEndpoint('Trades', params, requestConfig);
},
async getRecentSpreadData(queryParams: ISpreadParams): Promise<IKrakenResponse> {
const params = (({ pair, since }) =>
({ pair, since }))(queryParams);
return this.rawAgent.getPublicEndpoint('Spread', params, requestConfig);
},
async getAccountBalance(): Promise<IKrakenResponse> {
const nonce = generateNonce();
const params = { nonce };
return this.rawAgent.postToPrivateEndpoint('Balance', params, requestConfig);
},
async getTradeBalance(queryParams?: ITradeBalanceParams): Promise<IKrakenResponse> {
const nonce = generateNonce();
const params = queryParams ?
(({ aclass, asset }) =>
({ nonce, aclass, asset }))(queryParams) :
{ nonce };
return this.rawAgent.postToPrivateEndpoint('TradeBalance', params, requestConfig);
},
async getOpenOrders(queryParams?: IOpenOrdersParams): Promise<IKrakenResponse> {
const nonce = generateNonce();
const params = queryParams ?
(({ trades, userref }) =>
({ nonce, trades, userref }))(queryParams) :
{ nonce };
return this.rawAgent.postToPrivateEndpoint('OpenOrders', params, requestConfig);
},
async getClosedOrders(queryParams?: IClosedOrdersParams): Promise<IKrakenResponse> {
const nonce = generateNonce();
const params = queryParams ?
(({ trades, userref, start, end, ofs, closetime }) =>
({ nonce, trades, userref, start, end, ofs, closetime }))(queryParams) :
{ nonce };
return this.rawAgent.postToPrivateEndpoint('ClosedOrders', params, requestConfig);
},
async queryOrdersInfo(queryParams: IOrdersInfoParams): Promise<IKrakenResponse> {
const nonce = generateNonce();
const params = queryParams ?
(({ trades, userref, txid }) =>
({ nonce, trades, userref, txid }))(queryParams) :
{ nonce };
return this.rawAgent.postToPrivateEndpoint('QueryOrders', params, requestConfig);
},
async getTradesHistory(queryParams?: ITradesHistoryParams): Promise<IKrakenResponse> {
const nonce = generateNonce();
const params = queryParams ?
(({ type, trades, start, end, ofs }) =>
({ nonce, type, trades, start, end, ofs }))(queryParams) :
{ nonce };
return this.rawAgent.postToPrivateEndpoint('TradesHistory', params, requestConfig);
},
async queryTradesInfo(queryParams: ITradesInfoParams): Promise<IKrakenResponse> {
const nonce = generateNonce();
const params = (({ txid, trades }) =>
({ nonce, txid, trades }))(queryParams);
return this.rawAgent.postToPrivateEndpoint('QueryTrades', params, requestConfig);
},
async getOpenPositions(queryParams: IOpenPositionsParams): Promise<IKrakenResponse> {
const nonce = generateNonce();
const params = (({ txid, docalcs }) =>
({ nonce, txid, docalcs }))(queryParams);
return this.rawAgent.postToPrivateEndpoint('OpenPositions', params, requestConfig);
},
async getLedgersInfo(queryParams?: ILedgersParams): Promise<IKrakenResponse> {
const nonce = generateNonce();
const params = queryParams ?
(({ aclass, asset, type, start, end, ofs }) =>
({ nonce, aclass, asset, type, start, end, ofs }))(queryParams) :
{ nonce };
return this.rawAgent.postToPrivateEndpoint('Ledgers', params, requestConfig);
},
async queryLedgers(queryParams: IQueryLedgersParams): Promise<IKrakenResponse> {
const nonce = generateNonce();
const params = (({ id }) =>
({ nonce, id }))(queryParams);
return this.rawAgent.postToPrivateEndpoint('QueryLedgers', params, requestConfig);
},
async getTradeVolume(queryParams?: ITradeVolumeParams): Promise<IKrakenResponse> {
const nonce = generateNonce();
const params: { [k: string]: {} } = queryParams ?
(({ pair }) =>
({ nonce, pair }))(queryParams) :
{ nonce };
//tslint:disable:no-string-literal
//NOTE: only because hyphens don't work through the shorthand
params['fee-info'] = queryParams['fee-info'];
//tslint:enable:no-string-literal
return this.rawAgent.postToPrivateEndpoint('TradeVolume', params, requestConfig);
},
async addStandardOrder(queryParams: IAddOrderParams): Promise<IKrakenResponse> {
const nonce = generateNonce();
const required = (({ pair, type, ordertype }) =>
({ nonce, pair, type, ordertype }))(queryParams);
const optional = (({ price, price2, volume, leverage, oflags, starttm, expiretm, userref, validate }) =>
({ price, price2, volume, leverage, oflags, starttm, expiretm, userref, validate }))(queryParams);
//tslint:disable:object-literal-sort-keys
const optionalClose = ((orderParams: IAddOrderParams) => ({
'close[type]' : orderParams['close[type]'],
'close[price]' : orderParams['close[price]'],
'close[price2]': orderParams['close[price2]'],
}))(queryParams);
//tslint:enable:object-literal-sort-keys
const params = { ...required, ...optional, ...optionalClose };
return this.rawAgent.postToPrivateEndpoint('AddOrder', params, requestConfig);
},
async cancelOpenOrder(queryParams: ICancelOpenOrderParams): Promise<IKrakenResponse> {
const nonce = generateNonce();
const params = (({ txid }) =>
({ nonce, txid }))(queryParams);
return this.rawAgent.postToPrivateEndpoint('CancelOrder', params, requestConfig);
},
async getDepositMethods(queryParams: IDepositMethodsParams): Promise<IKrakenResponse> {
const nonce = generateNonce();
const params = (({ aclass, asset }) =>
({ nonce, aclass, asset }))(queryParams);
return this.rawAgent.postToPrivateEndpoint('DepositMethods', params, requestConfig);
},
async getDepositAddresses(queryParams: IDepositAddressesParams): Promise<IKrakenResponse> {
const nonce = generateNonce();
const params: { [k: string]: {} } = (({ aclass, asset, method }) =>
({ nonce, aclass, asset, method }))(queryParams);
//tslint:disable:no-string-literal
//NOTE: only because keyword 'new' doesn't work through this shorthand
params['new'] = queryParams['new'];
//tslint:enable:no-string-literal
return this.rawAgent.postToPrivateEndpoint('DepositAddresses', params, requestConfig);
},
async getStatusOfRecentDeposits(queryParams: IDepositStatusParams): Promise<IKrakenResponse> {
const nonce = generateNonce();
const params = (({ aclass, asset }) =>
({ nonce, aclass, asset }))(queryParams);
return this.rawAgent.postToPrivateEndpoint('DepositStatus', params, requestConfig);
},
async getWithdrawalInformation(queryParams: IWithdrawInfoParams): Promise<IKrakenResponse> {
const nonce = generateNonce();
const params = (({ aclass, asset }) =>
({ nonce, aclass, asset }))(queryParams);
return this.rawAgent.postToPrivateEndpoint('WithdrawInfo', params, requestConfig);
},
async withdrawFunds(queryParams: IWithdrawParams): Promise<IKrakenResponse> {
const nonce = generateNonce();
const params = (({ aclass, asset }) =>
({ nonce, aclass, asset }))(queryParams);
return this.rawAgent.postToPrivateEndpoint('Withdraw', params, requestConfig);
},
async getStatusOfRecentWithdrawals(queryParams: IWithdrawStatusParams): Promise<IKrakenResponse> {
const nonce = generateNonce();
const params = (({ aclass, asset }) =>
({ nonce, aclass, asset }))(queryParams);
return this.rawAgent.postToPrivateEndpoint('WithdrawStatus', params, requestConfig);
},
async requestWithdrawalCancellation(queryParams: IWithdrawCancelParams): Promise<IKrakenResponse> {
const nonce = generateNonce();
const params = (({ aclass, asset }) =>
({ nonce, aclass, asset }))(queryParams);
return this.rawAgent.postToPrivateEndpoint('WithdrawCancel', params, requestConfig);
},
});
/**
* Alias for Axios request options.
*/
export interface IKrakenRequestConfig extends AxiosRequestConfig {}
/**
* Alias for Axios response.
*/
export interface IKrakenResponse extends AxiosResponse {}