bittrex-api-client
Version:
A client which can be used to interact with Bittrex's API. Entirely developed in TypeScript.
515 lines (481 loc) • 17.9 kB
text/typescript
import * as CryptoJs from "crypto-js";
import * as SignalR from "signalr-client";
import { URL } from "url";
import { Market } from "./model/Market";
import { Currency } from "./model/Currency";
import { Ticker } from "./model/Ticker";
import { MarketSummary } from "./model/MarketSummary";
import { OrderBookType } from "./enum/OrderBookType";
import { Trade } from "./model/Trade";
import { Order } from "./model/Order";
import { Balance } from "./model/Balance";
import { ExchangeStateUpdate } from "./model/ExchangeStateUpdate";
import { isNullOrUndefined } from "util";
import { ApiError } from "./error/ApiError";
import * as Path from "path";
import { CloudscraperError } from "./error/CloudscraperError";
import { OrderBook } from "./model/OrderBook";
import * as request from "requestretry";
import { RequestRetryError } from "./error/RequestRetryError";
import cloudscraper = require("cloudscraper");
/**
* Represents a single Bittrex API client.
*/
export class BittrexApiClient {
/**
* The personal account's API key.
*/
private apiKey: string;
/**
* The personal account's API secret.
*/
private apiSecret: string;
/**
* Initializes a new Bittrex API client.
*
* @param apiKey The personal account API key.
* @param apiSecret The personal account API secret.
*/
constructor(apiKey?: string, apiSecret?: string) {
this.apiKey = apiKey;
this.apiSecret = apiSecret;
}
/**
* Interface to the "public/getmarkets" Bittrex's API operation.
*
* @returns Either a promise of a market, or a market
* if using the await construct.
*/
public async getMarkets(): Promise<Market[]> {
let marketsJson: any = await this.makeRequest("public/getmarkets");
let markets: Market[] = [];
for (let marketJson of marketsJson) {
markets.push(new Market(marketJson));
}
return markets;
}
/**
* Interface to the "public/getcurrencies" Bittrex's API operation.
*
* @returns Either a promise of a currency array, or a currency array
* if using the await construct.
*/
public async getCurrencies(): Promise<Currency[]> {
let currenciesJson: any = await this.makeRequest(
"public/getcurrencies"
);
let currencies: Currency[] = [];
for (let currencyJson of currenciesJson) {
currencies.push(new Currency(currencyJson));
}
return currencies;
}
/**
* Interface to the "public/getticker" Bittrex's API operation.
*
* @param market The market of which we would like
* to retrieve the ticker.
* @returns Either a promise of a ticker, or a ticker
* if using the await construct.
*/
public async getTicker(market: string): Promise<Ticker> {
return new Ticker(
await this.makeRequest("public/getticker", ["market", market])
);
}
/**
* Interface to the "public/getmarketsummaries" Bittrex's API operation.
*
* @returns Either a promise of a market summary array, or a market summary
* array if using the await construct.
*/
public async getMarketSummaries(): Promise<MarketSummary[]> {
let marketSummariesJson: any = await this.makeRequest(
"public/getmarketsummaries"
);
let marketSummaries: MarketSummary[] = [];
for (let marketSummaryJson of marketSummariesJson) {
marketSummaries.push(new MarketSummary(marketSummaryJson));
}
return marketSummaries;
}
/**
* Interface to the "public/getmarketsummary" Bittrex's API operation.
*
* @param market The market of which we would like
* to retrieve the summary.
* @returns Either a promise of a market summary, or a market summary
* if using the await construct.
*/
public async getMarketSummary(market: string): Promise<MarketSummary> {
return new MarketSummary(
(
await this.makeRequest("public/getmarketsummary", [
"market",
market
])
)[0]
);
}
/**
* Interface to the "public/getorderbook" Bittrex's API operation.
*
* @param market The market of which we would like
* to retrieve the order book.
* @param type The type of the order book that we want to
* retrieve, depending on if we want only the
* buys, sells, or both.
* @returns Either a promise of an order book, or an order book
* if using the await construct.
*/
public async getOrderBook(
market: string,
type: OrderBookType
): Promise<OrderBook> {
return new OrderBook(
await this.makeRequest(
"public/getorderbook",
["market", market],
["type", OrderBookType[type].toLowerCase()]
)
);
}
/**
* Interface to the "public/getmarkethistory" Bittrex's API operation.
*
* @param market The market of which we would like
* to retrieve the market history.
* @returns Either a promise of a trade array, or a trade array
* if using the await construct.
*/
public async getMarketHistory(market: string): Promise<Trade[]> {
let tradesJson: any = await this.makeRequest(
"/public/getmarkethistory",
["market", market]
);
let trades: Trade[] = [];
for (let tradeJson of tradesJson) {
trades.push(new Trade(tradeJson));
}
return trades;
}
/**
* Interface to the "public/buylimit" Bittrex's API operation.
*
* @param market The market on which we would like to buy.
* @param quantity The quantity that we would like to buy.
* @param rate The price at which we would like to buy.
* @returns Either a promise of the placed order's identifier, or the placed
* order's identifier if using the await construct.
*/
public async buyWithLimit(
market: string,
quantity: number,
rate: number
): Promise<string> {
return (
await this.makeRequest(
"/market/buylimit",
["market", market],
["quantity", quantity.toString()],
["rate", rate.toString()]
)
).uuid;
}
/**
* Interface to the "public/selllimit" Bittrex's API operation.
*
* @param market The market on which we would like to sell.
* @param quantity The quantity that we would like to sell.
* @param rate The price at which we would like to sell.
* @returns Either a promise of the placed order's identifier, or the placed
* order's identifier if using the await construct.
*/
public async sellWithLimit(
market: string,
quantity: number,
rate: number
): Promise<string> {
return (
await this.makeRequest(
"/market/selllimit",
["market", market],
["quantity", quantity.toString()],
["rate", rate.toString()]
)
).uuid;
}
/**
* Interface to the "public/cancelorder" Bittrex's API operation.
*
* @param orderId The ID of the order we would like to cancel.
* @returns True if the operation resulted in a success, throws
* otherwise.
*/
public async cancelOrder(orderId: string): Promise<boolean> {
await this.makeRequest("/market/cancel", ["uuid", orderId]);
return true;
}
/**
* Interface to the "public/getopenorders" Bittrex's API operation.
*
* @param market The market of which we would like to retrieve
* the open orders.
* @returns Either a promise of an open order array, or an open
* order array if using the await construct.
*/
public async getOpenOrders(market?: string): Promise<Order[]> {
let openOrdersJson: any = await this.makeRequest(
"/market/getopenorders",
["market", market]
);
let openOrders: Order[] = [];
for (let openOrderJson of openOrdersJson) {
openOrders.push(new Order(openOrderJson));
}
return openOrders;
}
/**
* Interface to the "public/getbalances" Bittrex's API operation.
*
* @returns Either a promise of a balance array, or a balance
* array if using the await construct.
*/
public async getBalances(): Promise<Balance[]> {
let balancesJson: any = await this.makeRequest("/account/getbalances");
let balances: Balance[] = [];
for (let balanceJson of balancesJson) {
balances.push(new Balance(balanceJson));
}
return balances;
}
/**
* Interface to the "public/getbalance" Bittrex's API operation.
*
* @param currency The currency of which we would like to retrieve
* the balance.
* @returns Either a promise of a balance, or a balance if using
* the await construct.
*/
public async getBalance(currency: string): Promise<Balance> {
return new Balance(
await this.makeRequest("/account/getbalance", [
"currency",
currency
])
);
}
/**
* Interface to the "public/getdepositaddress" Bittrex's API operation.
*
* @param currency The currency of which we would like to retrieve
* the deposit address.
* @returns Either a promise of a deposit address, or a deposit
* address if using the await construct.
*/
public async getDepositAddress(currency: string): Promise<string> {
return (
await this.makeRequest("/account/getdepositaddress", [
"currency",
currency
])
).Address;
}
/**
* Interface to the "public/getdepositaddress" Bittrex's API operation.
*
* @param currency The currency which we would like to withdraw.
* @param quantity The quantity which we would like to withdraw.
* @param address The address to which we would like to withdraw.
* @param paymentId Optional parameter used for CryptoNotes/BitShareX/Nxt.
*
* @returns Either a promise of a withdrawal ID, or a withdrawal ID
* if using the await construct.
*/
public async withdraw(
currency: string,
quantity: number,
address: string,
paymentId?: string
): Promise<string> {
return (
await this.makeRequest(
"/account/withdraw",
["currency", currency],
["quantity", quantity.toString()],
["address", address],
["paymentId", paymentId]
)
).uuid;
}
/**
* Interface to the "public/getorder" Bittrex's API operation.
*
* @param uuid The uuid of the order of which we would like to get the detail.
*
* @returns Either a promise of an order, or an order if using
* the await construct.
*/
public async getOrder(uuid: string): Promise<Order> {
return new Order(
await this.makeRequest("/account/getorder", ["uuid", uuid])
);
}
/**
* Interface to the Bittrex's API websockets system.
*
* @param watchableMarkets The markets of which we would like
* to receive constant updates.
* @param callback A callback function invoked as soon as new
* updates about the watched markets are received
* from Bittrex.
*/
public getExchangeStateUpdatesStream(
watchableMarkets: string[],
callback: (marketUpdates: ExchangeStateUpdate[]) => any
): void {
let websocketClient: SignalR.client;
require("cloudscraper").get(
{ uri: "https://bittrex.com/" },
(error, response) => {
if (error) {
throw new CloudscraperError(error);
}
websocketClient = new SignalR.client(
"wss://socket.bittrex.com/signalr",
["CoreHub"],
null,
true
);
websocketClient.headers["User-Agent"] =
response.request.headers["User-Agent"] || "";
websocketClient.headers["cookie"] =
response.request.headers["cookie"] || "";
websocketClient.serviceHandlers.reconnecting = () => {
return true;
};
websocketClient.serviceHandlers.disconnected = function() {
websocketClient.start();
};
websocketClient.serviceHandlers.onerror = function() {
websocketClient.start();
};
websocketClient.serviceHandlers.connectionLost = function() {
websocketClient.start();
};
websocketClient.serviceHandlers.connectFailed = function() {
websocketClient.start();
};
websocketClient.serviceHandlers.connected = () => {
for (let watchableMarket of watchableMarkets) {
websocketClient.call(
"CoreHub",
"SubscribeToExchangeDeltas",
watchableMarket
);
}
};
websocketClient.serviceHandlers.messageReceived = (
messageJson: any
): void => {
if (messageJson.type !== "utf8") {
return;
}
try {
messageJson = JSON.parse(messageJson.utf8Data);
} catch (error) {
return;
}
let updatesJson = messageJson.M;
if (updatesJson === undefined || updatesJson.length === 0) {
return;
}
let exchangeStateUpdates: ExchangeStateUpdate[] = [];
for (let updateJson of updatesJson) {
if (updateJson.M !== "updateExchangeState") {
continue;
}
for (let exchangeStateUpdateJson of updateJson.A) {
exchangeStateUpdates.push(
new ExchangeStateUpdate(exchangeStateUpdateJson)
);
}
}
if (exchangeStateUpdates.length === 0) {
return;
}
callback(exchangeStateUpdates);
};
websocketClient.start();
}
);
}
/**
* Utility method that sends a request to the Bittrex's API, handling the
* authentication through the API key and API secret possibly given when
* instantiating the client itself.
*
* @param operation The Bittrex's API operation that we would like to call.
* @param parameters The parameters which the operation takes in.
*
* @returns Either the promise of the Bittrex's API JSON response, or the
* JSON response if using the await construct.
*/
private async makeRequest(
operation: string,
...parameters: [string, string][]
): Promise<any> {
let apiEndpointUrl: URL = new URL(
Path.join("/api/v1.1", operation),
"https://www.bittrex.com/"
);
if (!isNullOrUndefined(this.apiKey)) {
apiEndpointUrl.searchParams.append("apikey", this.apiKey);
}
apiEndpointUrl.searchParams.append(
"nonce",
new Date().getTime().toString()
);
for (let parameter of parameters) {
if (isNullOrUndefined(parameter[1])) {
continue;
}
apiEndpointUrl.searchParams.append(parameter[0], parameter[1]);
}
let apiEndpointUrlString: string = apiEndpointUrl.toString();
let apiSign: string = CryptoJs.HmacSHA512(
apiEndpointUrlString,
this.apiSecret
);
let response: any;
try {
response = await request({
url: apiEndpointUrlString,
headers: {
apisign: apiSign
},
json: true,
maxAttempts: 10,
retryDelay: 2500,
retryStrategy: (error, response) => {
return (
error ||
response.statusCode === 524 ||
response.statusCode === 502 ||
response.statusCode === 504 ||
response.statusCode === 522 ||
response.statusCode === 503 ||
response.statusCode === 1016
);
},
fullResponse: false
});
} catch (error) {
throw new RequestRetryError(error);
}
if (response.success) {
return response.result;
}
throw new ApiError(response);
}
}