Skip to content

Commit c25417a

Browse files
authored
Merge pull request #12 from bitholla/develop
Develop
2 parents 6ea93e6 + 072d0dd commit c25417a

File tree

8 files changed

+135
-40
lines changed

8 files changed

+135
-40
lines changed

.env.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
ACCESS_TOKEN = PUT_YOUR_ACCESS_TOKEN_HERE
1+
API_KEY = <PUT_YOUR_API_KEY_HERE>
2+
API_SECRET = <PUT_YOUR_API_SECRET_HERE>

.eslintrc.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
},
77
"extends": "eslint:recommended",
88
"parserOptions": {
9-
"ecmaVersion": 2017
9+
"ecmaVersion": 2018
1010
},
1111
"rules": {
1212
"linebreak-style": ["error", "unix"],

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,22 @@ const HollaEx = require('hollaex-node-lib');
1010
var client = new HollaEx();
1111
```
1212

13-
You can pass your Access_Token generated from the site as follows:
13+
You can pass your `apiKey` and `apiSecret` generated from the site as follows:
1414

1515
```node
16-
var client = new HollaEx({ accessToken: MY_ACCESS_TOKEN });
16+
var client = new HollaEx({ apiKey: <MY_API_KEY>, apiSecret: <MY_API_SECRET> });
1717
```
1818

19+
You can also pass the field `apiExpiresAfter` which is the length of time in seconds each request is valid for. The default value is `60`.
20+
1921
There is a list of functions you can call which will be added later and they are not at this point implemented yet.
2022

21-
### getTicker
23+
> - **Note**: v1 has a new authentication mechanism using HMAC signature. HollaEx previously was using JSON Web Token (JWT) which is now changed to HMAC authentication.
24+
25+
### Example:
2226

2327
```node
24-
var client = new HollaEx({ accessToken: MY_ACCESS_TOKEN });
28+
var client = new HollaEx({ apiKey: <MY_API_KEY>, apiSecret: <MY_API_SECRET> });
2529
client
2630
.getTicker('hex-usdt')
2731
.then((res) => {
@@ -33,6 +37,8 @@ client
3337
});
3438
```
3539

40+
### Available functions:
41+
3642
| Command | Parameters | Description |
3743
| ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
3844
| `getTicker` | **symbol** e.g. `hex-usdt` | Last, high, low, open and close price and volume within the last 24 hours |

example/hollaex.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
const HollaEx = require('../index');
22
require('dotenv').load();
33

4-
const ACCESS_TOKEN = process.env.ACCESS_TOKEN || '';
5-
const client = new HollaEx({ accessToken: ACCESS_TOKEN });
4+
const API_KEY = process.env.API_KEY || '';
5+
const API_SECRET = process.env.API_SECRET || '';
6+
const client = new HollaEx({ apiKey: API_KEY, apiSecret: API_SECRET });
67

78
client
89
.getTicker('hex-usdt')

index.js

Lines changed: 86 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,28 @@
11
const io = require('socket.io-client');
22
const EventEmitter = require('events');
3-
4-
const { createRequest } = require('./utils');
3+
const moment = require('moment');
4+
const { createRequest, createSignature, generateHeaders } = require('./utils');
55

66
class HollaEx {
77
constructor(
88
opts = {
99
apiURL: 'https://api.hollaex.com',
1010
baseURL: '/v1',
11-
accessToken: ''
11+
apiKey: '',
12+
apiSecret: '',
13+
apiExpiresAfter: 60
1214
}
1315
) {
1416
this._url = opts.apiURL + opts.baseURL || 'https://api.hollaex.com/v1';
1517
this._wsUrl = opts.apiURL || 'https://api.hollaex.com';
16-
this._accessToken = opts.accessToken || '';
18+
this._baseUrl = opts.baseURL || '/v1';
19+
this.apiKey = opts.apiKey;
20+
this.apiSecret = opts.apiSecret;
21+
this.apiExpiresAfter = opts.apiExpiresAfter || 60;
1722
this._headers = {
1823
'content-type': 'application/json',
1924
Accept: 'application/json',
20-
Authorization: 'Bearer ' + this._accessToken
25+
'api-key': opts.apiKey,
2126
};
2227
}
2328

@@ -81,15 +86,29 @@ class HollaEx {
8186
* @return {string} A stringified JSON object showing user's information such as id, email, bank_account, crypto_wallet, balance, etc
8287
*/
8388
getUser() {
84-
return createRequest('GET', `${this._url}/user`, this._headers);
89+
const verb = 'GET';
90+
const path = this._baseUrl + '/user';
91+
const headers = generateHeaders(this._headers, this.apiSecret, verb, path, this.apiExpiresAfter);
92+
return createRequest(
93+
verb,
94+
`${this._url}/user`,
95+
headers
96+
);
8597
}
8698

8799
/**
88100
* Retrieve user's wallet balance
89101
* @return {string} A stringified JSON object with the keys updated_at(string), usdt_balance(number), usdt_pending(number), usdt_available(number), hex_balance, hex_pending, hex_available, eth_balance, eth_pending, eth_available, bch_balance, bch_pending, bch_available
90102
*/
91103
getBalance() {
92-
return createRequest('GET', `${this._url}/user/balance`, this._headers);
104+
const verb = 'GET';
105+
const path = this._baseUrl + '/user/balance';
106+
const headers = generateHeaders(this._headers, this.apiSecret, verb, path, this.apiExpiresAfter);
107+
return createRequest(
108+
verb,
109+
`${this._url}/user/balance`,
110+
headers
111+
);
93112
}
94113

95114
/**
@@ -102,12 +121,15 @@ class HollaEx {
102121
* @return {string} A stringified JSON object with the keys count(total number of user's deposits) and data(array of deposits as objects with keys id(number), type(string), amount(number), transaction_id(string), currency(string), created_at(string), status(boolean), fee(number), dismissed(boolean), rejected(boolean), description(string))
103122
*/
104123
getDeposit(currency, limit = 50, page = 1, orderBy, order = 'asc') {
124+
const verb = 'GET';
125+
const path = this._baseUrl + `/user/deposits?limit=${limit}&page=${page}&currency=${currency}&order_by=${orderBy}&order=${order}`;
126+
const headers = generateHeaders(this._headers, this.apiSecret, verb, path, this.apiExpiresAfter);
105127
return createRequest(
106-
'GET',
128+
verb,
107129
`${
108130
this._url
109131
}/user/deposits?limit=${limit}&page=${page}&currency=${currency}&order_by=${orderBy}&order=${order}`,
110-
this._headers
132+
headers
111133
);
112134
}
113135

@@ -122,12 +144,15 @@ class HollaEx {
122144
* @return {string} A stringified JSON object with the keys count(total number of user's withdrawals) and data(array of withdrawals as objects with keys id(number), type(string), amount(number), transaction_id(string), currency(string), created_at(string), status(boolean), fee(number), dismissed(boolean), rejected(boolean), description(string))
123145
*/
124146
getWithdrawal(currency, limit = 50, page = 1, orderBy, order = 'asc') {
147+
const verb = 'GET';
148+
const path = this._baseUrl + `/user/withdrawals?limit=${limit}&page=${page}&currency=${currency}&order_by=${orderBy}&order=${order}`;
149+
const headers = generateHeaders(this._headers, this.apiSecret, verb, path, this.apiExpiresAfter);
125150
return createRequest(
126-
'GET',
151+
verb,
127152
`${
128153
this._url
129154
}/user/withdrawals?limit=${limit}&page=${page}&currency=${currency}&order_by=${orderBy}&order=${order}`,
130-
this._headers
155+
headers
131156
);
132157
}
133158

@@ -155,11 +180,14 @@ class HollaEx {
155180
* @return {string} A stringified JSON object {message:"Success"}
156181
*/
157182
requestWithdrawal(currency, amount, address) {
158-
let data = { currency, amount, address, fee: 0 };
183+
const verb = 'POST';
184+
const path = this._baseUrl + '/user/request-withdrawal';
185+
const data = { currency, amount, address, fee: 0 };
186+
const headers = generateHeaders(this._headers, this.apiSecret, verb, path, this.apiExpiresAfter, data);
159187
return createRequest(
160-
'POST',
188+
verb,
161189
`${this._url}/user/request-withdrawal`,
162-
this._headers,
190+
headers,
163191
data
164192
);
165193
}
@@ -172,14 +200,17 @@ class HollaEx {
172200
* @return {string} A stringified JSON object with the keys count(total number of user's completed trades) and data(array of up to the user's last 50 completed trades as objects with keys side(string), symbol(string), size(number), price(number), timestamp(string), and fee(number))
173201
*/
174202
getUserTrade(symbol, limit = 50, page = 1) {
203+
const verb = 'GET';
175204
let queryString = `?limit=${limit}&page=${page}`;
176205
if (symbol) {
177206
queryString += `&symbol=${symbol}`;
178207
}
208+
const path = this._baseUrl + `/user/trades${queryString}`;
209+
const headers = generateHeaders(this._headers, this.apiSecret, verb, path, this.apiExpiresAfter);
179210
return createRequest(
180-
'GET',
211+
verb,
181212
`${this._url}/user/trades${queryString}`,
182-
this._headers
213+
headers
183214
);
184215
}
185216

@@ -190,10 +221,13 @@ class HollaEx {
190221
* @return {string} The selected order as a stringified JSON object with keys created_at(string), title(string), symbol(string), side(string), size(number), type(string), price(number), id(string), created_by(number), filled(number)
191222
*/
192223
getOrder(orderId) {
224+
const verb = 'GET';
225+
const path = this._baseUrl + `/user/orders/${orderId}`;
226+
const headers = generateHeaders(this._headers, this.apiSecret, verb, path, this.apiExpiresAfter);
193227
return createRequest(
194-
'GET',
228+
verb,
195229
`${this._url}/user/orders/${orderId}`,
196-
this._headers
230+
headers
197231
);
198232
}
199233

@@ -203,10 +237,13 @@ class HollaEx {
203237
* @return {string} A stringified JSON array of objects containing the user's active orders
204238
*/
205239
getAllOrder(symbol = '') {
240+
const verb = 'GET';
241+
const path = this._baseUrl + `/user/orders?symbol=${symbol}`;
242+
const headers = generateHeaders(this._headers, this.apiSecret, verb, path, this.apiExpiresAfter);
206243
return createRequest(
207-
'GET',
244+
verb,
208245
`${this._url}/user/orders?symbol=${symbol}`,
209-
this._headers
246+
headers
210247
);
211248
}
212249

@@ -220,8 +257,11 @@ class HollaEx {
220257
* @return {string} The new order as a stringified JSON object with keys symbol(string), side(string), size(number), type(string), price(number), id(string), created_by(number), and filled(number)
221258
*/
222259
createOrder(symbol, side, size, type, price) {
223-
let data = { symbol, side, size, type, price };
224-
return createRequest('POST', `${this._url}/order`, this._headers, data);
260+
const verb = 'POST';
261+
const path = this._baseUrl + '/order';
262+
const data = { symbol, side, size, type, price };
263+
const headers = generateHeaders(this._headers, this.apiSecret, verb, path, this.apiExpiresAfter, data);
264+
return createRequest(verb, `${this._url}/order`, headers, data);
225265
}
226266

227267
/**
@@ -230,10 +270,13 @@ class HollaEx {
230270
* @return {string} The cancelled order as a stringified JSON object with keys symbol(string), side(string), size(number), type(string), price(number), id(string), created_by(number), and filled(number)
231271
*/
232272
cancelOrder(orderId) {
273+
const verb = 'DELETE';
274+
const path = this._baseUrl + `/user/orders/${orderId}`;
275+
const headers = generateHeaders(this._headers, this.apiSecret, verb, path, this.apiExpiresAfter);
233276
return createRequest(
234-
'DELETE',
277+
verb,
235278
`${this._url}/user/orders/${orderId}`,
236-
this._headers
279+
headers
237280
);
238281
}
239282

@@ -243,10 +286,13 @@ class HollaEx {
243286
* @return {string} A stringified JSON array of objects containing the cancelled orders
244287
*/
245288
cancelAllOrder(symbol = '') {
289+
const verb = 'DELETE';
290+
const path = this._baseUrl + `/user/orders?symbol=${symbol}`;
291+
const headers = generateHeaders(this._headers, this.apiSecret, verb, path, this.apiExpiresAfter);
246292
return createRequest(
247-
'DELETE',
293+
verb,
248294
`${this._url}/user/orders?symbol=${symbol}`,
249-
this._headers
295+
headers
250296
);
251297
}
252298

@@ -256,15 +302,17 @@ class HollaEx {
256302
* @return {class} A new socket class that listens to the hollaEx websocket server and emits the event being passed
257303
*/
258304
connect(events) {
259-
return new Socket(events, this._wsUrl, this._accessToken);
305+
const apiExpires = moment().unix() + this.apiExpiresAfter;
306+
const signature = createSignature(this.apiSecret, 'CONNECT', '/socket', apiExpires);
307+
return new Socket(events, this._wsUrl, this.apiKey, signature, apiExpires);
260308
}
261309
}
262310

263311
/*******************
264312
Websocket
265313
*******************/
266314
class Socket extends EventEmitter {
267-
constructor(events = '', url, accessToken) {
315+
constructor(events = '', url, apiKey, apiSignature, apiExpires) {
268316
super();
269317
if (!Array.isArray(events)) {
270318
let listeners = [];
@@ -287,7 +335,11 @@ class Socket extends EventEmitter {
287335
break;
288336
case 'user':
289337
ioLink = io(`${url}/user`, {
290-
query: { token: `Bearer ${accessToken}` }
338+
query: {
339+
'api-key': apiKey,
340+
'api-signature': apiSignature,
341+
'api-expires': apiExpires
342+
}
291343
});
292344

293345
listeners.push(ioLink);
@@ -319,7 +371,11 @@ class Socket extends EventEmitter {
319371
});
320372

321373
ioLink = io(`${url}/user`, {
322-
query: { token: `Bearer ${accessToken}` }
374+
query: {
375+
'api-key': apiKey,
376+
'api-signature': apiSignature,
377+
'api-expires': apiExpires
378+
}
323379
});
324380
listeners.push(ioLink);
325381
listeners[listeners.length - 1].on('user', (data) => {

package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
{
22
"name": "hollaex-node-lib",
3-
"version": "0.4.1",
3+
"version": "1.0.0",
44
"description": "",
55
"main": "index.js",
66
"dependencies": {
7+
"moment": "2.24.0",
78
"lodash": "4.17.13",
89
"request": "2.88.0",
910
"request-promise": "4.2.2",

utils.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
const rp = require('request-promise');
2+
const crypto = require('crypto');
3+
const moment = require('moment');
24

35
const createRequest = (verb, url, headers, data) => {
46
const body = JSON.stringify(data);
@@ -10,6 +12,29 @@ const createRequest = (verb, url, headers, data) => {
1012
return rp[verb.toLowerCase()](requestObj);
1113
};
1214

15+
const createSignature = (secret = '', verb, path, expires, data = '') => {
16+
const stringData = typeof data === 'string' ? data : JSON.stringify(data);
17+
18+
const signature = crypto
19+
.createHmac('sha256', secret)
20+
.update(verb + path + expires + stringData)
21+
.digest('hex');
22+
return signature;
23+
};
24+
25+
const generateHeaders = (headers, secret, verb, path, expiresAfter, data) => {
26+
const expires = moment().unix() + expiresAfter;
27+
const signature = createSignature(secret, verb, path, expires, data);
28+
const header = {
29+
...headers,
30+
'api-signature': signature,
31+
'api-expires': expires
32+
};
33+
return header;
34+
};
35+
1336
module.exports = {
14-
createRequest
37+
createRequest,
38+
createSignature,
39+
generateHeaders
1540
};

0 commit comments

Comments
 (0)