1 |
|
2 |
|
3 | import {Buffer} from 'buffer';
|
4 | import * as querystring from 'querystring';
|
5 | import {createHmac} from 'crypto';
|
6 | import * as request from 'request';
|
7 | import * as cheerio from 'cheerio';
|
8 | import * as VError from 'verror';
|
9 |
|
10 | export default class BTCMarkets
|
11 | {
|
12 |
|
13 | static numberConverter = 100000000;
|
14 |
|
15 | constructor(public key: string,
|
16 | public secret: string,
|
17 | public server: string = 'https:
|
18 | public timeout: number = 20000)
|
19 | {}
|
20 |
|
21 | protected privateRequest(
|
22 | path: string,
|
23 | params: object = {} ): Promise<object>
|
24 | {
|
25 | if(!this.key || !this.secret)
|
26 | {
|
27 | throw new VError('must provide key and secret to make this API request.');
|
28 | }
|
29 |
|
30 | let method = 'POST';
|
31 |
|
32 |
|
33 |
|
34 | if (path.split('/')[1] === 'account' ||
|
35 | path === '/fundtransfer/history')
|
36 | {
|
37 | method = 'GET';
|
38 | }
|
39 |
|
40 |
|
41 | const timestamp = (new Date()).getTime();
|
42 |
|
43 | let message;
|
44 | if (method === 'POST')
|
45 | {
|
46 | message = path + "\n" +
|
47 | timestamp + "\n" +
|
48 | JSON.stringify(params);
|
49 | }
|
50 | else if (Object.keys(params).length > 0)
|
51 | {
|
52 | message = path + "\n" +
|
53 | querystring.stringify(params) + "\n" +
|
54 | timestamp + "\n";
|
55 | }
|
56 | else
|
57 | {
|
58 | message = path + "\n" +
|
59 | timestamp + "\n";
|
60 | }
|
61 |
|
62 | const signer = createHmac('sha512', new Buffer(this.secret, 'base64'));
|
63 | const signature = signer.update(message).digest('base64');
|
64 |
|
65 | const headers = {
|
66 | "User-Agent": "BTC Markets Javascript API Client",
|
67 | "apikey": this.key,
|
68 | "timestamp": timestamp,
|
69 | "signature": signature};
|
70 |
|
71 | const options: request.Options = {
|
72 | url: this.server + path,
|
73 | method: method,
|
74 | headers: headers,
|
75 | timeout: this.timeout,
|
76 | json: params
|
77 | };
|
78 |
|
79 | if (method === 'GET')
|
80 | {
|
81 | options.qs = params;
|
82 | }
|
83 |
|
84 | const requestDesc = `${options.method} request to url ${options.url} with message ${message}`;
|
85 |
|
86 | return this.executeRequest(options, requestDesc);
|
87 | }
|
88 |
|
89 | protected publicRequest(
|
90 | instrument: BTCMarkets.instruments,
|
91 | currency: BTCMarkets.currencies,
|
92 | action: string,
|
93 | params?: object): Promise<object>
|
94 | {
|
95 | const headers = {"User-Agent": "BTC Markets Javascript API Client"};
|
96 |
|
97 | const path = '/market/' + instrument + '/' + currency + '/' + action;
|
98 |
|
99 | const options = {
|
100 | url: this.server + path,
|
101 | method: 'GET',
|
102 | headers: headers,
|
103 | timeout: this.timeout,
|
104 | json: {},
|
105 | qs: params };
|
106 |
|
107 | const requestDesc = `${options.method} request to url ${options.url} with parameters ${JSON.stringify(params)}`;
|
108 |
|
109 | return this.executeRequest(options, requestDesc);
|
110 | };
|
111 |
|
112 | protected executeRequest(
|
113 | options: request.OptionsWithUrl,
|
114 | requestDesc: string): Promise<object>
|
115 | {
|
116 | return new Promise((resolve, reject) =>
|
117 | {
|
118 | request(options, function(err: any,
|
119 | response: request.RequestResponse,
|
120 | data: BTCMarkets.BaseResponse)
|
121 | {
|
122 | let error = null;
|
123 |
|
124 | if(err)
|
125 | {
|
126 | error = new VError(err, `failed ${requestDesc} with error message ${err.message}`);
|
127 | error.name = err.code;
|
128 | }
|
129 | else if (response.statusCode < 200 || response.statusCode >= 300)
|
130 | {
|
131 | error = new VError(`HTTP status code ${response.statusCode} returned from ${requestDesc}. Status message: ${response.statusMessage}`);
|
132 | error.name = response.statusCode.toString();
|
133 | }
|
134 | else if (!data)
|
135 | {
|
136 | error = new VError(`failed ${requestDesc}. No data returned.`);
|
137 | }
|
138 |
|
139 | else if (data !== Object(data))
|
140 | {
|
141 |
|
142 | const $ = cheerio.load(data);
|
143 |
|
144 | const responseBody = $('body').text();
|
145 |
|
146 | if (responseBody)
|
147 | {
|
148 | error = new VError(err, `Could not parse response body from ${requestDesc}\nResponse body: ${responseBody}`);
|
149 | error.name = responseBody;
|
150 | }
|
151 | else
|
152 | {
|
153 | error = new VError(err, `Could not parse json or HTML response from ${requestDesc}`);
|
154 | }
|
155 | }
|
156 | else if (data.hasOwnProperty('success') && !data.success)
|
157 | {
|
158 | error = new VError(`failed ${requestDesc}. Success: ${data.success}. Error message: ${data.errorMessage}`);
|
159 | error.name = data.errorMessage;
|
160 | }
|
161 |
|
162 | if (error) reject(error);
|
163 |
|
164 | resolve(data);
|
165 | });
|
166 | });
|
167 | }
|
168 |
|
169 |
|
170 |
|
171 |
|
172 |
|
173 | getTick(instrument: BTCMarkets.instruments,
|
174 | currency: BTCMarkets.currencies): Promise<BTCMarkets.Tick>
|
175 | {
|
176 |
|
177 | return this.publicRequest(instrument, currency, 'tick');
|
178 | };
|
179 |
|
180 | getOrderBook(instrument: BTCMarkets.instruments,
|
181 | currency: BTCMarkets.currencies): Promise<BTCMarkets.OrderBook>
|
182 | {
|
183 |
|
184 | return this.publicRequest(instrument, currency, 'orderbook');
|
185 | };
|
186 |
|
187 | getTrades(instrument: BTCMarkets.instruments,
|
188 | currency: BTCMarkets.currencies,
|
189 | since?: number): Promise<BTCMarkets.Trade[]>
|
190 | {
|
191 |
|
192 | return this.publicRequest(instrument, currency, 'trades', {
|
193 | since: since}
|
194 | );
|
195 | };
|
196 |
|
197 |
|
198 |
|
199 |
|
200 |
|
201 | createOrder(instrument: BTCMarkets.instruments,
|
202 | currency: BTCMarkets.currencies,
|
203 | price: number | void = 0,
|
204 | volume: number,
|
205 | orderSide: BTCMarkets.OrderSide,
|
206 | ordertype: BTCMarkets.OrderType,
|
207 | clientRequestId: string | void = "",
|
208 | triggerPrice?: number
|
209 | ): Promise<BTCMarkets.NewOrder>
|
210 | {
|
211 | const params = {
|
212 | currency: currency,
|
213 | instrument: instrument,
|
214 | price: ordertype == 'Market'? 0 : price,
|
215 | volume: volume,
|
216 | orderSide: orderSide,
|
217 | ordertype: ordertype,
|
218 | clientRequestId: clientRequestId,
|
219 | triggerPrice: triggerPrice
|
220 | };
|
221 |
|
222 |
|
223 | return this.privateRequest('/order/create', params);
|
224 | };
|
225 |
|
226 | cancelOrders(orderIds: number[]): Promise<BTCMarkets.CancelledOrders>
|
227 | {
|
228 |
|
229 | return this.privateRequest('/order/cancel', {
|
230 | orderIds: orderIds}
|
231 | );
|
232 | };
|
233 |
|
234 | getOrderDetail(orderIds: number[]): Promise<BTCMarkets.Orders>
|
235 | {
|
236 |
|
237 | return this.privateRequest('/order/detail', {
|
238 | orderIds: orderIds}
|
239 | );
|
240 | };
|
241 |
|
242 | getOpenOrders(instrument: BTCMarkets.instruments,
|
243 | currency: BTCMarkets.currencies,
|
244 | limit: number | void = 10,
|
245 | since: number | null = null): Promise<BTCMarkets.Orders>
|
246 | {
|
247 |
|
248 | return this.privateRequest('/order/open', {
|
249 | currency: currency,
|
250 | instrument: instrument,
|
251 | limit: limit,
|
252 | since: since}
|
253 | );
|
254 | };
|
255 |
|
256 | getOrderHistory(instrument: BTCMarkets.instruments,
|
257 | currency: BTCMarkets.currencies,
|
258 | limit: number | void = 100,
|
259 | since: number | null = null): Promise<BTCMarkets.Orders>
|
260 | {
|
261 |
|
262 | return this.privateRequest('/order/history', {
|
263 | currency: currency,
|
264 | instrument: instrument,
|
265 | limit: limit,
|
266 | since: since}
|
267 | );
|
268 | };
|
269 |
|
270 | getTradeHistory(instrument: BTCMarkets.instruments,
|
271 | currency: BTCMarkets.currencies,
|
272 | limit: number | void = 100,
|
273 | since: number | null = null): Promise<BTCMarkets.Trades>
|
274 | {
|
275 |
|
276 | return this.privateRequest('/order/trade/history', {
|
277 | currency: currency,
|
278 | instrument: instrument,
|
279 | limit: limit,
|
280 | since: since}
|
281 | );
|
282 | };
|
283 |
|
284 | getAccountBalances(): Promise<BTCMarkets.Balance[]>
|
285 | {
|
286 |
|
287 | return this.privateRequest('/account/balance');
|
288 | };
|
289 |
|
290 | getTradingFee(instrument: BTCMarkets.instruments,
|
291 | currency: BTCMarkets.currencies): Promise<BTCMarkets.TradingFee>
|
292 | {
|
293 |
|
294 | return this.privateRequest('/account/' + instrument + "/" + currency + "/" + 'tradingfee');
|
295 | };
|
296 |
|
297 | withdrawCrypto(amount: number,
|
298 | address: string,
|
299 | crypto: string): Promise<BTCMarkets.CryptoWithdrawal>
|
300 | {
|
301 |
|
302 | return this.privateRequest('/fundtransfer/withdrawCrypto', {
|
303 | amount: amount,
|
304 | address: address,
|
305 | currency: crypto
|
306 | });
|
307 | };
|
308 |
|
309 | withdrawEFT(accountName: string,
|
310 | accountNumber: string,
|
311 | bankName: string,
|
312 | bsbNumber: string,
|
313 | amount: number): Promise<BTCMarkets.BankWithdrawal>
|
314 | {
|
315 |
|
316 | return this.privateRequest('/fundtransfer/withdrawEFT', {
|
317 | accountName: accountName,
|
318 | accountNumber: accountNumber,
|
319 | bankName: bankName,
|
320 | bsbNumber: bsbNumber,
|
321 | amount: amount,
|
322 | currency: "AUD"
|
323 | });
|
324 | };
|
325 |
|
326 | withdrawHistory(limit: number | void,
|
327 | since: number | void,
|
328 | indexForward: boolean | void): Promise<BTCMarkets.FundWithdrawals>
|
329 | {
|
330 |
|
331 | return this.privateRequest('/fundtransfer/history', {
|
332 | limit: limit,
|
333 | since: since,
|
334 | indexForward: indexForward
|
335 | });
|
336 | };
|
337 | }
|