From 98fe6ce8f1dc8b0aa94bb6ffd7f762b13540ed99 Mon Sep 17 00:00:00 2001 From: "T. Ismael Verdugo" Date: Mon, 21 Nov 2022 11:13:15 -0300 Subject: [PATCH 01/16] feat: api-v3 --- README.md | 184 ++- lib/client.js | 811 ---------- lib/client.ts | 1444 ++++++++++++++++++ lib/constants.ts | 164 ++ lib/exceptions.js | 43 - lib/exceptions.ts | 22 + lib/index.js | 15 - lib/index.ts | 21 + lib/models.ts | 24 + lib/models/ACLSettings.ts | 8 + lib/models/Address.ts | 6 + lib/models/AmountLock.ts | 11 + lib/models/Balance.ts | 6 + lib/models/Candle.ts | 9 + lib/models/Commission.ts | 5 + lib/models/Currency.ts | 12 + lib/models/Network.ts | 17 + lib/models/Order.ts | 45 + lib/models/OrderBook.ts | 22 + lib/models/Price.ts | 5 + lib/models/PriceHistory.ts | 12 + lib/models/PublicTrade.ts | 7 + lib/models/Report.ts | 23 + lib/models/SubAccount.ts | 7 + lib/models/SubAccountBalance.ts | 6 + lib/models/Symbol.ts | 13 + lib/models/Ticker.ts | 11 + lib/models/Trade.ts | 12 + lib/models/TradeOfOrder.ts | 8 + lib/models/Transactions.ts | 44 + lib/models/WSCandle.ts | 11 + lib/models/WSTicker.ts | 16 + lib/models/WSTrades.ts | 9 + lib/websocket/accountClient.js | 129 -- lib/websocket/authClient.js | 50 - lib/websocket/authClient.ts | 59 + lib/websocket/clientBase.js | 113 -- lib/websocket/clientBase.ts | 144 ++ lib/websocket/marketDataClient.ts | 495 ++++++ lib/websocket/orderbook_cache.js | 114 -- lib/websocket/publicClient.js | 299 ---- lib/websocket/tradingClient.js | 132 -- lib/websocket/tradingClient.ts | 293 ++++ lib/websocket/walletClient.ts | 221 +++ package-lock.json | 1375 ++++------------- package.json | 12 +- test/rest/market_data.test.ts | 254 +++ test/rest/spot_trading.test.ts | 151 ++ test/rest/spot_trading_history.test.ts | 31 + test/rest/wallet_management.test.ts | 229 +++ test/test_helpers.js | 270 ---- test/test_helpers.ts | 359 +++++ test/test_rest_account_management.js | 61 - test/test_rest_public.js | 249 --- test/test_rest_trading.js | 85 -- test/test_rest_trading_history.js | 43 - test/test_websocket_account.js | 55 - test/test_websocket_public.js | 114 -- test/test_websocket_subscriptions_account.js | 49 - test/test_websocket_subscriptions_public.js | 175 --- test/test_websocket_subscriptions_trading.js | 42 - test/test_websocket_trading.js | 57 - test/websocket/market_data.test.ts | 234 +++ test/websocket/trading.test.ts | 137 ++ test/websocket/trading_subscriptions.test.ts | 43 + test/websocket/wallet.test.ts | 59 + test/websocket/wallet_subscriptions.test.ts | 84 + 67 files changed, 5231 insertions(+), 4039 deletions(-) delete mode 100644 lib/client.js create mode 100644 lib/client.ts create mode 100644 lib/constants.ts delete mode 100644 lib/exceptions.js create mode 100644 lib/exceptions.ts delete mode 100644 lib/index.js create mode 100644 lib/index.ts create mode 100644 lib/models.ts create mode 100644 lib/models/ACLSettings.ts create mode 100644 lib/models/Address.ts create mode 100644 lib/models/AmountLock.ts create mode 100644 lib/models/Balance.ts create mode 100644 lib/models/Candle.ts create mode 100644 lib/models/Commission.ts create mode 100644 lib/models/Currency.ts create mode 100644 lib/models/Network.ts create mode 100644 lib/models/Order.ts create mode 100644 lib/models/OrderBook.ts create mode 100644 lib/models/Price.ts create mode 100644 lib/models/PriceHistory.ts create mode 100644 lib/models/PublicTrade.ts create mode 100644 lib/models/Report.ts create mode 100644 lib/models/SubAccount.ts create mode 100644 lib/models/SubAccountBalance.ts create mode 100644 lib/models/Symbol.ts create mode 100644 lib/models/Ticker.ts create mode 100644 lib/models/Trade.ts create mode 100644 lib/models/TradeOfOrder.ts create mode 100644 lib/models/Transactions.ts create mode 100644 lib/models/WSCandle.ts create mode 100644 lib/models/WSTicker.ts create mode 100644 lib/models/WSTrades.ts delete mode 100644 lib/websocket/accountClient.js delete mode 100644 lib/websocket/authClient.js create mode 100644 lib/websocket/authClient.ts delete mode 100644 lib/websocket/clientBase.js create mode 100644 lib/websocket/clientBase.ts create mode 100644 lib/websocket/marketDataClient.ts delete mode 100644 lib/websocket/orderbook_cache.js delete mode 100644 lib/websocket/publicClient.js delete mode 100644 lib/websocket/tradingClient.js create mode 100644 lib/websocket/tradingClient.ts create mode 100644 lib/websocket/walletClient.ts create mode 100644 test/rest/market_data.test.ts create mode 100644 test/rest/spot_trading.test.ts create mode 100644 test/rest/spot_trading_history.test.ts create mode 100644 test/rest/wallet_management.test.ts delete mode 100644 test/test_helpers.js create mode 100644 test/test_helpers.ts delete mode 100644 test/test_rest_account_management.js delete mode 100644 test/test_rest_public.js delete mode 100644 test/test_rest_trading.js delete mode 100644 test/test_rest_trading_history.js delete mode 100644 test/test_websocket_account.js delete mode 100644 test/test_websocket_public.js delete mode 100644 test/test_websocket_subscriptions_account.js delete mode 100644 test/test_websocket_subscriptions_public.js delete mode 100644 test/test_websocket_subscriptions_trading.js delete mode 100644 test/test_websocket_trading.js create mode 100644 test/websocket/market_data.test.ts create mode 100644 test/websocket/trading.test.ts create mode 100644 test/websocket/trading_subscriptions.test.ts create mode 100644 test/websocket/wallet.test.ts create mode 100644 test/websocket/wallet_subscriptions.test.ts diff --git a/README.md b/README.md index f77cb30..66b4850 100644 --- a/README.md +++ b/README.md @@ -1,148 +1,196 @@ # CryptoMarket-javascript -[main page](https://www.cryptomkt.com/) +[main page](https://www.cryptomkt.com/) [sign up in CryptoMarket](https://www.cryptomkt.com/account/register). # Installation + To install Cryptomarket use npm + ``` npm install cryptomarket ``` + # Documentation -This sdk makes use of the [api version 2](https://api.exchange.cryptomkt.com/v2) of cryptomarket. +This sdk makes use of the [api version 3](https://api.exchange.cryptomkt.com) of cryptomarket. # Quick Start ## rest client + ```javascript -const { Client } = require('cryptomarket') +const { Client } = require("cryptomarket"); // instance a client -let apiKey='AB32B3201' -let api_secret='21b12401' -let client = new Client(apiKey, api_secret) +let apiKey = "AB32B3201"; +let apiSecret = "21b12401"; +let client = new Client(apiKey, apiSecret); // get currencies -let currencies = await client.getCurrencies() +let currencies = await client.getCurrencies(); // get order books -let orderBook = await client.getOrderBook('EOSETH') +let orderBook = await client.getOrderBook("EOSETH"); // get your account balances -let accountBalance = await client.getAccountBalance() +let accountBalances = await client.getWalletBalances(); // get your trading balances -let tradingBalance = await client.getTradingBalance() +let tradingBalances = await client.getSpotTradingBalances(); -// move balance from account to trading -let result = await client.transferMoneyFromAccountBalanceToTradingBalance('ETH', '3.2') +// move balance from wallet to spot trading +let result = await client.transferBetweenWalletAndExchange({ + currency: "EOS", + amount: "3.2", + source: Account.Wallet, + destination: Account.Spot, +}); // get your active orders -let orders = await client.getActiveOrders('EOSETH') +let orders = await client.getAllActiveSpotOrders("EOSETH"); // create a new order -let newOrder = await client.createOrder({'symbol':'EOSETH', 'side':'buy', 'quantity':'10', 'price':'10'}) +let newOrder = await client.createOrder({ + symbol: "EOSETH", + side: "buy", + quantity: "10", + price: "10", +}); ``` ## websocket client -There are three websocket clients, one for public request (WSPublicClient), one for trading request (WSTradingClient) and one for the account requests (WSAccountClient). -Clients work with promises +There are three websocket clients, the market data client, the spot trading client and the wallet client. +The market data client requires no authentication, while the spot trading client and the wallet client do require it. -Clients must be connected before any request. Authentication for WSTradingClient and WSAccountClient is automatic. +All websocket methods return promises. Subscriptions also take in a function of two parameters, the notification data, and the notification type. The notification type is of type NOTIFICATION_TYPE, and is either SNAPSHOT, NOTIFICATION or DATA. -```javascript -const { WSPublicClient, WSTradingClient, WSAccountClient} = require('cryptomarket') +The documentation of a specific subscriptions explains with of this types of +notification uses. -let publicClient = new WSPublicClient() -await publicClient.connect() +### MarketDataClient -// get currencies -await publicClient.getCurrencies() +Example of use of the market data client -let apiKey='AB32B3201' -let apiSecret='21b12401' -let tradingClient = new WSTradingClient(apiKey, apiSecret) +```typescript +// instantiate a market data client +const wsclient = new WSMarketDataClient(); -await tradingClient.connect() +// make a partial orderbook subscription -// get your trading balance -let balance = await tradingClient.getTradingBalance() +// make subscription +await marketDataClient.subscribeToPartialOrderBook( + callback:(notification, type) => { + if (type === NOTIFICATION_TYPE.DATA) { + System.out.println("this subscription only recieves data notifications"); + } + for (const symbol in notification) { + console.log(symbol); + const orderbook = notification[symbol]; + console.log(orderbook); + } + }, + params: { + speed: ORDER_BOOK_SPEED._100_MS, + depth: DEPTH._5, + symbols: ["EOSETH", "ETHBTC"], + } +); -// get your active orders -let activeOrders = await tradingClient.getActiveOrders() -await tradingClient.createOrder({ - clientOrderId:"qqwe123qwe", - symbol:'EOSETH', - side:'buy', - quantity:'10', - price:'10' -}) +``` -let accountClient = new WSAccountClient(apiKey, apiSecret) -await accountClient.connect() +### SpotTradingClient -// get your account balance -let accBalance = await accountCilent.getAccountBalance() -``` -### subscriptions +Example of use of the spot trading client -all subscriptions take a callback argument to call with each feed of the subscription +```typescript +const apiKey = "AB32B3201"; +const apiSecret= "21b12401"; -```javascript +// instantiate a spot trading websocket client with a window of 10 seconds +const wsclient = new WSTradingClient(apiKey, apiSecret, 10_000); -// callback is for the subscription feed -function callback(feed) { - // handle feed - console.log(feed) +// connect the client (and authenticate it automatically) +await wsclient.connect(); + +// get all the spot trading balances +const balanceList = await tradingClient.getSpotTradingBalances() +console.log(balanceList); + + +let clientOrderID = Math.floor(Date.now() / 1000).toString(); +// make a spot order +const report = await tradingClient.createSpotOrder({ + client_order_id: clientOrderID, + symbol: "EOSETH", + side: "sell", + quantity: "0.01", + price: "1000", } +console.log(report); +``` + +### WalletClient + +Example of use of the wallet client -await publicClient.subscribeToOrderBook('EOSETH', callabck) +```typescript +// instantiate a wallet websocket client with a default window of 10 seconds +const wsclient = new WSWalletClient(keys.apiKey, keys.apiSecret); +// get a list of transactions +const transactionList = await walletClient.getTransactions({ + currencies: ["EOS"], + limit: 3, +}); +console.log(transactionList); -await accountClient.subscribeToTransactions(callback) +// subscribe to a feed of transactions +await walletClient.subscribeToTransactions((notification, type) => { + if (type === NOTIFICATION_TYPE.UPDATE) { + console.log("this subscription only recieves update notifications"); + } + console.log(notification); +}); ``` ## error handling -```javascript +```typescript -{ CryptomarketSDKException, Client, WSPublicClient } = require('cryptomarket') +{ CryptomarketSDKException, Client, WSMarketDataClient } = require('cryptomarket') // exceptions derive from the CryptomarketSDKException class. -client = new Client() +const client = new Client() // catch a failed transaction try { - order = client.createOrder({ + const order = await client.createOrder({ 'symbol':'EOSETHH', // non existant symbol 'side':'sell', - 'quantity':'10', + 'quantity':'10', 'price':'10'}) } catch (error) { console.log(error) } -wsclient = new WSPublicClient() -await wsclient.connect() +const wsclient = new WSMarketDataClient() +await wsclient.connect(); -// catch a missing argument +// catch missing arguments try { - await wsclient.subscribeToOrderbook('ETHBTC') // needs a callback + await wsclient.subscribeToMiniTickers() } catch (error) { console.log(error) } - -// also catches forwarded errors from the cryptomarket exchange server -try { - let trades = await wsclient.getTrades("abcde", myCallback) // not a real symbol -} catch (err) { - console.log(err) -} ``` +# Constants + +All constants used for calls are in the `constants` module. + # Checkout our other SDKs [python sdk](https://github.com/cryptomkt/cryptomkt-python) diff --git a/lib/client.js b/lib/client.js deleted file mode 100644 index a09416b..0000000 --- a/lib/client.js +++ /dev/null @@ -1,811 +0,0 @@ -const CryptoJS = require('crypto-js'); -const fetch = require("node-fetch"); -const { CryptomarketSDKException, CryptomarketAPIException, ArgumentFormatException } = require('./exceptions'); -const { URL, URLSearchParams } = require('url'); - -const apiUrl = 'https://api.exchange.cryptomkt.com' -const apiVersion = '/api/2/' - -const methodGet = "GET" -const methodPut = "PUT" -const methodPost = "POST" -const methodDelete = "DELETE" - -class Client { - constructor(apiKey, apiSecret) { - this.apiKey = apiKey - this.apiSecret = apiSecret - } - - async publicGet(endpoint, params) { - return this.makeRequest(methodGet, endpoint, params, true) - } - - async get(endpoint, params) { - return this.makeRequest(methodGet, endpoint, params) - } - - async post(endpoint, params) { - return this.makeRequest(methodPost, endpoint, params) - } - - async delete(endpoint, params) { - return this.makeRequest(methodDelete, endpoint, params) - } - - async put(endpoint, params) { - return this.makeRequest(methodPut, endpoint, params) - } - - async makeRequest(method, endpoint, params, publc=false) { - let url = new URL(apiUrl + apiVersion + endpoint) - let rawQuery = new URLSearchParams(params) - rawQuery.sort() - let query = rawQuery.toString() - - // build fetch options - let opts = { - method: method, - headers: { - 'User-Agent': 'cryptomarket/node', - 'Content-type': 'application/x-www-form-urlencoded' - } - } - // add auth header if not public endpoint - if (!publc) opts.headers['Authorization'] = this.buildCredential(method, endpoint, query) - - // include query params to call - if (method === methodGet || method === methodPut) url.search = query - else opts.body = query - - // make request - let response - try { - response = await fetch(url, opts) - } catch (e) { - throw new CryptomarketSDKException('Failed request to server', e) - } - let jsonResponse - try { - jsonResponse = await response.json() - } catch (e) { - throw new CryptomarketSDKException(`Failed to parse response: ${response}`, e) - } - if (!response.ok) { - throw new CryptomarketAPIException(jsonResponse, response.status) - } - return jsonResponse - } - - buildCredential(httpMethod, method, query) { - let timestamp = Math.floor(Date.now() / 1000).toString() - let msg = httpMethod + timestamp + apiVersion + method - if (query) { - if (httpMethod === methodGet || httpMethod === methodPut) msg += "?" - msg += query - } - let signature = CryptoJS.HmacSHA256(msg, this.apiSecret).toString() - return "HS256 " + Buffer.from(this.apiKey + ":" + timestamp + ":" + signature).toString('base64') - } - - ////////////////// - // PUBLIC CALLS // - ////////////////// - - /** - * Get a list of all currencies or specified currencies - * - * https://api.exchange.cryptomkt.com/#currencies - * - * @param {string[]} [currencies] Optional. A list of currencies ids - * - * @return A list of available currencies - */ - getCurrencies(currencies=[]) { - return this.get('public/currency/', {currencies}) - } - - /** - * Get the data of a currency - * - * https://api.exchange.cryptomkt.com/#currencies - * - * @param {string} currency A currency id - * - * @return A currency - */ - getCurrency(currency) { - return this.get(`public/currency/${currency}`) - } - - /** - * Get a list of all symbols or for specified symbols - * - * A symbol is the combination of the base currency (first one) and quote currency (second one) - * - * https://api.exchange.cryptomkt.com/#symbols - * - * @param {string[]} [symbols] Optional. A list of symbol ids - * - * @return A list of symbols traded on the exchange - */ - async getSymbols(symbols=[]) { - return this.get('public/symbol/', {symbols}) - } - - /** - * Get a symbol by its id - * - * A symbol is the combination of the base currency (first one) and quote currency (second one) - * - * https://api.exchange.cryptomkt.com/#symbols - * - * @param {string} symbol A symbol id - * - * @return A symbol traded on the exchange - */ - getSymbol(symbol) { - return this.get(`public/symbol/${symbol}`) - } - - /** - * Get tickers for all symbols or for specified symbols - * - * https://api.exchange.cryptomkt.com/#tickers - * - * @param {string[]} [symbols] Optional. A list of symbol ids - * - * @returns A list of tickers - */ - getTickers(symbols = []) { - return this.get('public/ticker/', {symbols}) - } - - /** - * Get the ticker of a symbol - * - * https://api.exchange.cryptomkt.com/#tickers - * - * @param {string} [symbol] A symbol id - * - * @returns A ticker of a symbol - */ - getTicker(symbol) { - return this.get(`public/ticker/${symbol}`) - } - - /** - * Get trades for all symbols or for specified symbols - * - * 'from' param and 'till' param must have the same format, both index of both timestamp - * - * https://api.exchange.cryptomkt.com/#trades - * - * @param {string[]} [params.symbols] Optional. A list of symbol ids - * @param {string} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC' - * @param {string} [params.from] Optional. Initial value of the queried interval - * @param {string} [params.till] Optional. Last value of the queried interval - * @param {number} [params.limit] Optional. Trades per query. Defaul is 100. Max is 1000 - * @param {number} [params.offset] Optional. Default is 0. Max is 100000 - * - * @return A list of trades for each symbol of the query - */ - getTrades(params) { - return this.get('public/trades/', params) - } - - /** - * Get trades of a symbol - * - * 'from' param and 'till' param must have the same format, both index of both timestamp - * - * https://api.exchange.cryptomkt.com/#trades - * - * @param {string} symbol A symbol id - * @param {string} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC' - * @param {string} [params.from] Optional. Initial value of the queried interval - * @param {string} [params.till] Optional. Last value of the queried interval - * @param {number} [params.limit] Optional. Trades per query. Defaul is 100. Max is 1000 - * @param {number} [params.offset] Optional. Default is 0. Max is 100000 - * - * @return A list of trades of the symbol - */ - getTradesOfSymbol(symbol, params) { - return this.get(`public/trades/${symbol}`, params) - } - - /** - * Get orderbooks for all symbols or for the specified symbols - * - * An Order Book is an electronic list of buy and sell orders for a specific symbol, structured by price level - * - * https://api.exchange.cryptomkt.com/#order-book - * - * @param {string[]} [symbols] Optional. A list of symbol ids - * @param {number} [limit] Optional. Limit of order book levels. Set to 0 to view full list of order book levels - * - * @return The order book for each queried symbol - */ - getOrderBooks(symbols=[], limit) { - let params = {symbols} - if (limit) { - params['limit'] = limit - } - return this.get('public/orderbook/', params) - } - - /** - * Get order book of a symbol - * - * An Order Book is an electronic list of buy and sell orders for a specific symbol, structured by price level - * - * https://api.exchange.cryptomkt.com/#order-book - * - * @param {string} symbol The symbol id - * @param {number} [limit] Optional. Limit of order book levels. Set to 0 to view full list of order book levels - * - * @return The order book of the symbol - */ - getOrderBook(symbol, limit) { - let params = {} - if (limit) { - params['limit'] = limit - } - return this.get(`public/orderbook/${symbol}`, params) - } - - /** - * Get order book of a symbol with market depth info - * - * An Order Book is an electronic list of buy and sell orders for a specific symbol, structured by price level - * - * https://api.exchange.cryptomkt.com/#order-book - * - * @param {string} symbol The symbol id - * @param {number} volume Desired volume for market depth search - * - * @return The order book of the symbol - */ - marketDepthSearch(symbol, volume) { - let params = {volume} - return this.get(`public/orderbook/${symbol}`, params) - } - - - - /** - * Get candles for all symbols or for specified symbols - * - * Candels are used for OHLC representation - * - * https://api.exchange.cryptomkt.com/#candles - * - * @param {string[]} [params.symbols] Optional. A list of symbol ids - * @param {string} [params.period] Optional. A valid tick interval. 'M1' (one minute), 'M3', 'M5', 'M15', 'M30', 'H1' (one hour), 'H4', 'D1' (one day), 'D7', '1M' (one month). Default is 'M30' - * @param {string} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC' - * @param {string} [params.from] Optional. Initial value of the queried interval - * @param {string} [params.till] Optional. Last value of the queried interval - * @param {number} [params.limit] Optional. Candles per query. Defaul is 100. Max is 1000 - * @param {number} [params.offset] Optional. Default is 0. Max is 100000 - * - * @return A list of candles for each symbol of the query - */ - getCandles(params) { - return this.get('public/candles/', params) - } - - - /** - * Get candle for all symbols or for specified symbols - * - * Candels are used for OHLC representation - * - * https://api.exchange.cryptomkt.com/#candles - * - * @param {string} symbol A symbol id - * @param {string} [params.period] Optional. A valid tick interval. 'M1' (one minute), 'M3', 'M5', 'M15', 'M30', 'H1' (one hour), 'H4', 'D1' (one day), 'D7', '1M' (one month). Default is 'M30' - * @param {string} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC' - * @param {string} [params.from] Optional. Initial value of the queried interval - * @param {string} [params.till] Optional. Last value of the queried interval - * @param {number} [params.limit] Optional. Candles per query. Defaul is 100. Max is 1000 - * @param {number} [params.offset] Optional. Default is 0. Max is 100000 - * - * @return A list of candles of a symbol - */ - getCandlesOfSymbol(symbol, params) { - return this.get(`public/candles/${symbol}`, params) - } - - ///////////////////////// - // AUTHENTICATED CALLS // - ///////////////////////// - - ///////////// - // TRADING // - ///////////// - - /** - * Get the account trading balance - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#trading-balance - * - * @return the account trading balance. - */ - getTradingBalance() { - return this.get('trading/balance') - } - - /** - * Get the account active orders - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#get-active-orders - * - * @param {string} symbol Optional. A symbol for filtering active orders - * - * @return The account active orders - */ - getActiveOrders(symbol) { - let params = {} - if (symbol) params["symbol"] = symbol - return this.get('order', params) - } - - /** - * Get an active order by its client order id - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#get-active-orders - * - * @param {string} clientOrderId The clientOrderId of the order - * @param {number} [wait] Optional. Time in milliseconds Max value is 60000. Default value is None. While using long polling request: if order is filled, cancelled or expired order info will be returned instantly. For other order statuses, actual order info will be returned after specified wait time. - * - * @return An order of the account - */ - getActiveOrder(clientOrderId, wait) { - let params = {} - if (wait) { - params["wait"] = wait - } - return this.get(`order/${clientOrderId}`, params) - } - - /** - * Creates a new order - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#create-new-order - * - * @param {string} params.symbol Trading symbol - * @param {string} params.side 'buy' or 'sell' - * @param {string} params.quantity Order quantity - * @param {string} [params.clientOrderId] Optional. If given must be unique within the trading day, including all active orders. If not given, is generated by the server - * @param {string} [params.type] Optional. 'limit', 'market', 'stopLimit' or 'stopMarket'. Default is 'limit' - * @param {string} [params.timeInForce] Optional. 'GTC', 'IOC', 'FOK', 'Day', 'GTD'. Default to 'GTC' - * @param {string} [params.price] Required for 'limit' and 'stopLimit'. limit price of the order - * @param {string} [params.stopPrice] Required for 'stopLimit' and 'stopMarket' orders. stop price of the order - * @param {string} [params.expireTime] Required for orders with timeInForce = GDT - * @param {boolean} [params.strictValidate] Optional. If False, the server rounds half down for tickerSize and quantityIncrement. Example of ETHBTC: tickSize = '0.000001', then price '0.046016' is valid, '0.0460165' is invalid - * @param {boolean} [params.postOnly] Optional. If True, your post_only order causes a match with a pre-existing order as a taker, then the order will be cancelled - * - * @return An order of the account - */ - createOrder(params={}) { - if (!('symbol' in params)) throw new ArgumentFormatException('missing "symbol" argument for order creation') - if (!('side' in params)) throw new ArgumentFormatException('missing "side" argument for order creation', ['buy', 'sell']) - if (!('quantity' in params)) throw new ArgumentFormatException('missing "quantity" argument for order creation') - if ('clientOrderId' in params) { - let clientOrderId = params['clientOrderId'] - delete params['clientOrderId'] - return this.put(`order/${clientOrderId}`, params) - } - return this.post('order', params) - } - - /** - * Cancel all active orders, or all active orders for a specified symbol - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#cancel-orders - * - * @param {string} [symbol] Optional. If given, cancels all orders of the symbol. If not given, cancels all orders of all symbols - * - * @returns All the canceled orders - */ - cancelAllOrders(symbol) { - let params = {} - if (symbol) params['symbol'] = symbol - return this.delete('order', params) - } - - /** - * Cancel the order with clientOrderId - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#cancel-order-by-clientorderid - * - * @param {string} clientOrderId the client id of the order to cancel - * - * @return The canceled order - */ - cancelOrder(clientOrderId) { - return this.delete(`order/${clientOrderId}`) - } - - /** - * Get personal trading commission rates for a symbol - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#get-trading-commission - * - * @param {string} symbol The symbol of the comission rates - * - * @return The commission rate for a symbol - */ - tradingFee(symbol) { - return this.get(`trading/fee/${symbol}`) - } - - ///////////////////// - // TRADING HISTORY // - ///////////////////// - - - /** - * Get the account order history - * - * All not active orders older than 24 are deleted - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#orders-history - * - * @param {string} [params.symbol] Optional. Filter orders by symbol - * @param {string} [params.from] Optional. Initial value of the queried interval - * @param {string} [params.till] Optional. Last value of the queried interval - * @param {number} [params.limit] Optional. Trades per query. Defaul is 100. Max is 1000 - * @param {number} [params.offset] Optional. Default is 0. Max is 100000 - * - * @return Orders in the interval - */ - getOrdersHistory(params={}) { - return this.get('history/order', params) - } - - /** - * Get orders with the clientOrderId - * - * All not active orders older than 24 are deleted - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#orders-history - * - * @param {string} clientOrderId the clientOrderId of the orders - * - * @return An order in a list - */ - getOrders(clientOrderId) { - return this.get('history/order', {clientOrderId}) - } - - - /** - * Get the user's trading history - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#orders-history - * - * @param {string} [params.symbol] Optional. Filter trades by symbol - * @param {string} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC' - * @param {string} [params.by] Optional. Defines the sorting type.'timestamp' or 'id'. Default is 'timestamp' - * @param {string} [params.from] Optional. Initial value of the queried interval. Id or datetime - * @param {string} [params.till] Optional. Last value of the queried interval. Id or datetime - * @param {number} [params.limit] Optional. Trades per query. Defaul is 100. Max is 1000 - * @param {number} [params.offset] Optional. Default is 0. Max is 100000 - * @param {string} [params.margin] Optional. Filtering of margin orders. 'include', 'only' or 'ignore'. Default is 'include' - * @return Trades in the interval - */ - getTradesHistory(params = {}) { - return this.get('history/trades', params) - } - - - /** - * Get the account's trading order with a specified order id - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#trades-by-order - * - * @param {string} id Order unique identifier assigned by exchange - * - * @return The trades of an order - */ - getTradesByOrderId(id) { - return this.get(`history/order/${id}/trades`) - } - - //////////////////////// - // ACCOUNT MANAGEMENT // - //////////////////////// - - /** - * Get the user account balance - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#account-balance - * - * @return The user's account balance - */ - getAccountBalance() { - return this.get('account/balance') - } - - /** - * Get the current address of a currency - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#deposit-crypto-address - * - * @param {string} currency currency to get the address - * - * @return The currenct address for the currency - */ - getDepositCryptoAddress(currency) { - return this.get(`account/crypto/address/${currency}`) - } - - /** - * Creates a new address for the currency - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#deposit-crypto-address - * - * @param {string} currency currency to create a new address - * - * @return The created address for the currency - */ - createDepositCryptoAddress(currency) { - return this.post(`account/crypto/address/${currency}`) - } - - /** - * Get the last 10 addresses used for deposit by currency - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#last-10-deposit-crypto-address - * - * @param {string} currency currency to get the list of addresses - * - * @return A list of addresses - */ - getLast10DepositCryptoAddresses(currency) { - return this.get(`account/crypto/addresses/${currency}`) - } - - /** - * Get the last 10 unique addresses used for withdraw by currency - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#last-10-used-crypto-address - * - * @param {string} currency currency to get the list of addresses - * - * @return A list of addresses - */ - getLast10UsedCryptoAddresses(currency) { - return this.get(`account/crypto/used-addresses/${currency}`) - } - - /** - * Withdraw cryptocurrency - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#withdraw-crypto - * - * @param {string} currency currency code of the crypto to withdraw - * @param {number} amount the amount to be sent to the specified address - * @param {string} address the address identifier - * @param {string} [params.paymentId] Optional. - * @param {boolean} [params.includeFee] Optional. If true then the total spent amount includes fees. Default false - * @param {boolean} [params.autoCommit] Optional. If false then you should commit or rollback transaction in an hour. Used in two phase commit schema. Default true - * - * @return The transaction id, asigned by the exchange - */ - withdrawCrypto(currency, amount, address, params = {}) { - params['currency'] = currency - params['amount'] = amount - params['address'] = address - return this.post('account/crypto/withdraw', params) - } - - /** - * Converts between currencies - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#transfer-convert-between-currencies - * - * @param {string} fromCurrency currency code of origin - * @param {string} toCurrency currency code of destiny - * @param {number} amount the amount to be sent - * - * @return A list of transaction identifiers - */ - transferConvert(fromCurrency, toCurrency, amount) { - let params = {fromCurrency, toCurrency, amount} - return this.post('account/crypto/transfer-convert', params) - } - - /** - * Commit a withdrawal of cryptocurrency - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#withdraw-crypto-commit-or-rollback - * - * @param {string} id the withdrawal transaction identifier - * - * @return The transaction result. true if the commit is successful - */ - commitWithdrawCrypto(id) { - return this.put(`account/crypto/withdraw/${id}`) - } - - /** - * Rollback a withdrawal of cryptocurrency - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#withdraw-crypto-commit-or-rollback - * - * @param {string} id the withdrawal transaction identifier - * - * @return The transaction result. true if the rollback is successful - */ - rollbackWithdrawCrypto(id) { - return this.delete(`account/crypto/withdraw/${id}`) - } - - /** - * Get an estimate of the withdrawal fee - * - * Requires authetication - * - * https://api.exchange.cryptomkt.com/#estimate-withdraw-fee - * - * @param {string} currency the currency code for withdraw - * @param {number} amount the expected withdraw amount - * - * @return The expected fee - */ - getEstimatesWithdrawFee(currency, amount) { - let params = {currency, amount} - return this.get('account/crypto/estimate-withdraw', params) - } - - /** - * Check if an address is from this account - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#check-if-crypto-address-belongs-to-current-account - * - * @param {string} address The address to check - * - * @return The transaction result. true if it is from the current account - */ - checkIfCryptoAddressIsMine(address) { - return this.get(`account/crypto/is-mine/${address}`) - } - - /** - * Transfer money from the trading balance to the account balance - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#transfer-money-between-trading-account-and-bank-account - * - * @param {string} currency Currency code for transfering - * @param {number} amount Amount to be transfered - * - * @return the transaction identifier of the transfer - */ - transferMoneyFromTradingBalanceToAccountBalance(currency, amount) { - let params = {currency, amount, type:"exchangeToBank"} - return this.post('account/transfer', params) - } - - /** - * Transfer money from the account balance to the trading balance - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#transfer-money-between-trading-account-and-bank-account - * - * @param {string} currency Currency code for transfering - * @param {number} amount Amount to be transfered - * - * @return the transaction identifier of the transfer - */ - transferMoneyFromAccountBalanceToTradingBalance(currency, amount) { - let params = {currency, amount, type:"bankToExchange"} - return this.post('account/transfer', params) - } - - /** - * Transfer money to another user - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#transfer-money-to-another-user-by-email-or-username - * - * @param {string} currency currency code - * @param {number} amount amount to be transfered between balances - * @param {string} by either 'email' or 'username' - * @param {string} identifier the email or the username - * - * @return The transaction identifier of the transfer - */ - transferMoneyToAnotherUser(currency, amount, by, identifier) { - let params = {currency, amount, by, identifier} - return this.post('account/transfer/internal', params) - } - - /** - * Get the transactions of the account by currency - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#get-transactions-history - * - * @param {string} currency Currency code to get the transaction history - * @param {string} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC'. - * @param {string} [params.by] Optional. Defines the sorting type.'timestamp' or 'id'. Default is 'timestamp' - * @param {string} [params.from] Optional. Initial value of the queried interval. Id or datetime - * @param {string} [params.till] Optional. Last value of the queried interval. Id or datetime - * @param {number} [params.limit] Optional. Transactions per query. Defaul is 100. Max is 1000 - * @param {number} [params.offset] Optional. Default is 0. Max is 100000 - * - * @return A list with the transactions in the interval - */ - getTransactionHistory(currency, params={}) { - params['currency'] = currency - return this.get('account/transactions', params) - } - - /** - * Get the transactions of the account by its identifier - * - * Requires authentication - * - * https://api.exchange.cryptomkt.com/#get-transactions-history - * - * @param {string} id The identifier of the transaction - * - * @return The transaction with the given id - */ - getTransaction(id) { - return this.get(`account/transactions/${id}`) - } -} - -module.exports = { - Client, -} \ No newline at end of file diff --git a/lib/client.ts b/lib/client.ts new file mode 100644 index 0000000..7b31eed --- /dev/null +++ b/lib/client.ts @@ -0,0 +1,1444 @@ +import CryptoJS from "crypto-js"; +import fetch from "node-fetch"; +import { + CryptomarketSDKException, + CryptomarketAPIException, +} from "./exceptions"; +import { URL, URLSearchParams } from "url"; +import { + ACCOUNT, + CONTINGENCY, + IDENTIFY_BY, + ORDER_TYPE, + PERIOD, + SIDE, + SORT, + SORT_BY, + TIME_IN_FORCE, + TRANSACTION_STATUS, + TRANSACTION_SUBTYPE, + TRANSACTION_TYPE, + TRANSFER_TYPE, + USE_OFFCHAIN, +} from "./constants"; +import { + ACLSettings, + Address, + AmountLock, + Balance, + Candle, + Commission, + Currency, + Order, + OrderBook, + OrderRequest, + Price, + PriceHistory, + PublicTrade, + SubAccount, + SubAccountBalance, + Ticker, + Trade, + Transaction, +} from "./models"; + +const apiUrl = "https://api.exchange.cryptomkt.com"; +const apiVersion = "/api/3/"; + +const methodGet = "GET"; +const methodPut = "PUT"; +const methodPatch = "PATCH"; +const methodPost = "POST"; +const methodDelete = "DELETE"; + +export class Client { + apiKey: string; + apiSecret: string; + window: number | null; + + constructor(apiKey: string, apiSecret: string, window: number | null = null) { + this.apiKey = apiKey; + this.apiSecret = apiSecret; + this.window = window; + } + + async publicGet(endpoint: string, params: any) { + return this.makeRequest(methodGet, endpoint, params, true); + } + + async get(endpoint: string, params: any | null = null) { + return this.makeRequest(methodGet, endpoint, params); + } + + async patch(endpoint: string, params: any) { + return this.makeRequest(methodPatch, endpoint, params); + } + + async post(endpoint: string, params: any) { + return this.makeRequest(methodPost, endpoint, params); + } + + async delete(endpoint: string, params: any | null = null) { + return this.makeRequest(methodDelete, endpoint, params); + } + + async put(endpoint: string, params: any | null = null) { + return this.makeRequest(methodPut, endpoint, params); + } + + async makeRequest( + method: string, + endpoint: string, + params: any, + publc: boolean = false + ) { + let url = new URL(apiUrl + apiVersion + endpoint); + for (let key in params) { + if (params[key] == null) { + delete params[key]; + } + } + let rawQuery = new URLSearchParams(params); + rawQuery.sort(); + let query = rawQuery.toString(); + + // build fetch options + let opts: any = { + method: method, + headers: { + "User-Agent": "cryptomarket/node", + "Content-type": "application/x-www-form-urlencoded", + }, + }; + // add auth header if not public endpoint + if (!publc) + opts.headers["Authorization"] = this.buildCredential(method, url, query); + + // include query params to call + if (method === methodGet || method === methodPut) url.search = query; + else opts.body = query; + + // make request + let response: { json: () => any; ok: boolean; status: any }; + try { + response = await fetch(url, opts); + } catch (e) { + throw new CryptomarketSDKException("Failed request to server", e); + } + let jsonResponse: any; + try { + jsonResponse = await response.json(); + } catch (e) { + throw new CryptomarketSDKException( + `Failed to parse response: ${response}`, + e + ); + } + if (!response.ok) { + throw new CryptomarketAPIException( + jsonResponse["error"], + response.status + ); + } + return jsonResponse; + } + + /** + * + * @param {URL} url + * @returns + */ + buildCredential(httpMethod: string, url: URL, query: string) { + let timestamp = Math.floor(Date.now()).toString(); + let msg = httpMethod + url.pathname; + if (query) { + if (httpMethod === methodGet) msg += "?"; + msg += query; + } + msg += timestamp; + if (this.window) { + msg += this.window; + } + let signature = CryptoJS.HmacSHA256(msg, this.apiSecret).toString(); + let signed = this.apiKey + ":" + signature + ":" + timestamp; + if (this.window) { + signed += ":" + this.window; + } + return `HS256 ${Buffer.from(signed).toString("base64")}`; + } + + ////////////////// + // PUBLIC CALLS // + ////////////////// + + /** + * Get a list of all currencies or specified currencies + * + * Requires no API key Access Rights. + * + * https://api.exchange.cryptomkt.com/#currencies + * + * @param {string[]} [currencies] Optional. A list of currencies ids + * + * @return A list of available currencies + */ + getCurrencies(currencies?: string[]): Promise<{ [key: string]: Currency[] }> { + return this.get("public/currency/", { currencies }); + } + + /** + * Get the data of a currency + * + * Requires no API key Access Rights. + * + * https://api.exchange.cryptomkt.com/#currencies + * + * @param {string} currency A currency id + * @return A currency + */ + getCurrency(currency: string): Promise { + return this.get(`public/currency/${currency}`); + } + + /** + * Get a list of all symbols or for specified symbols + * + * A symbol is the combination of the base currency (first one) and quote currency (second one) + * + * Requires no API key Access Rights. + * + * https://api.exchange.cryptomkt.com/#symbols + * + * @param {string[]} [symbols] Optional. A list of symbol ids + * + * @return A list of symbols traded on the exchange + */ + getSymbols(symbols?: string[]): Promise<{ [key: string]: Symbol[] }> { + return this.get("public/symbol/", { symbols }); + } + + /** + * Get a symbol by its id + * + * A symbol is the combination of the base currency (first one) and quote currency (second one) + * + * Requires no API key Access Rights. + * + * https://api.exchange.cryptomkt.com/#symbols + * + * @param {string} symbol A symbol id + * + * @return A symbol traded on the exchange + */ + getSymbol(symbol: string): Promise { + return this.get(`public/symbol/${symbol}`); + } + + /** + * Get tickers for all symbols or for specified symbols + * + * Requires no API key Access Rights. + * + * https://api.exchange.cryptomkt.com/#tickers + * + * @param {string[]} [symbols] Optional. A list of symbol ids + * + * @returns An object/dict with symbols ids as keys. + */ + getTickers(symbols?: string[]): Promise<{ [key: string]: Ticker[] }> { + return this.get("public/ticker/", { symbols }); + } + + /** + * Get the ticker of a symbol + * + * Requires no API key Access Rights. + * + * https://api.exchange.cryptomkt.com/#tickers + * + * @param {string} symbol A symbol id + * + * @returns A ticker of a symbol + */ + getTicker(symbol: string): Promise { + return this.get(`public/ticker/${symbol}`); + } + + /** + * Get quotation prices of currencies + * + * Requires no API key Access Rights. + * + * https://api.exchange.cryptomkt.com/#prices + * + * @param {object} params + * @param {string} params.to - Target currenct code. + * @param {string} [params.from] - Optional. Source currency code. + * + * @returns An object/dict of quotation prices of currencies, indexed by source currency code. + */ + getPrices(params: { + to: string; + from?: string; + }): Promise<{ [key: string]: Price[] }> { + return this.get(`public/price/rate`, params); + } + + /** + * Get quotation prices history + * + * Requires no API key Access Rights. + * + * https://api.exchange.cryptomkt.com/#prices + * + * @param {object} [params] + * @param {string} [params.from] Optional. Source currency code. + * @param {string} [params.to] Target currenct code. + * @param {string} [params.until] Optional. Last value of the queried interval + * @param {string} [params.since] Optional. Initial value of the queried interval + * @param {number} [params.limit] Optional. Prices per currency pair. Defaul is 1. Min is 1. Max is 1000 + * @param {PERIOD} [params.period] Optional. A valid tick interval. 'M1' (one minute), 'M3', 'M5', 'M15', 'M30', 'H1' (one hour), 'H4', 'D1' (one day), 'D7', '1M' (one month). Default is 'M30' + * @param {SORT} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC' + * + * @returns An object/dict of quotation prices of currencies, indexed by source currency code. + */ + getPricesHistory(params?: { + from?: string; + to?: string; + until?: string; + since?: string; + limit?: number; + period?: PERIOD; + sort?: SORT; + }): Promise<{ [key: string]: PriceHistory }> { + return this.get(`public/price/history`, params); + } + + /** + * Get ticker's last prices for all symbols or for the specified symbol + * + * Requires no API key Access Rights. + * + * https://api.exchange.cryptomkt.com/#prices + * + * @param {string[]} [symbols] Optional. A list of symbol ids + * @returns An object/dict of ticker prices of currencies, indexed by symbol. + */ + getTickerLastPrices(symbols?: string[]): Promise<{ [key: string]: Ticker }> { + return this.get(`public/price/ticker`, { symbols }); + } + + /** + * Get ticker's last prices of a symbol + * + * Requires no API key Access Rights. + * + * https://api.exchange.cryptomkt.com/#prices + * + * @param {string} symbol A symbol id. + * @returns The ticker's last price of a symbol. + */ + getTickerLastPriceOfSymbol(symbol: string): Promise { + return this.get(`public/price/ticker/${symbol}`); + } + + /** + * Get trades for all symbols or for specified symbols + * + * 'from' param and 'till' param must have the same format, both id or both timestamp + * + * Requires no API key Access Rights. + * + * https://api.exchange.cryptomkt.com/#trades + * + * @param {object} [params] + * @param {string[]} [params.symbols] Optional. A list of symbol ids + * @param {SORT_BY} [params.by] Optional. Sorting parameter. 'id' or 'timestamp'. Default is 'timestamp' + * @param {SORT} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC' + * @param {string} [params.from] Optional. Initial value of the queried interval + * @param {string} [params.till] Optional. Last value of the queried interval + * @param {number} [params.limit] Optional. Trades per query. Defaul is 10. Min is 1, Max is 1000 + * + * @return An object/dict with a list of trades for each symbol of the query. Indexed by symbol. + */ + getTrades(params?: { + symbols?: string[]; + by?: SORT_BY; + sort?: SORT; + from?: string; + till?: string; + limit?: number; + }): Promise<{ [key: string]: PublicTrade[] }> { + return this.get("public/trades", params); + } + + /** + * Get trades of a symbol + * + * 'from' param and 'till' param must have the same format, both id or both timestamp + * + * Requires no API key Access Rights. + * + * https://api.exchange.cryptomkt.com/#trades + * + * @param {string} symbol A symbol id + * @param {object} params + * @param {SORT} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC' + * @param {SORT_BY} [params.by] Optional. Sorting parameter. 'id' or 'timestamp'. Default is 'timestamp + * @param {string} [params.from] Optional. Initial value of the queried interval + * @param {string} [params.till] Optional. Last value of the queried interval + * @param {number} [params.limit] Optional. Trades per query. Defaul is 100. Min is 1. Max is 1000 + * @param {number} [params.offset] Optional. Default is 0. Min is 0. Max is 100000 + * + * @return A list of trades of the symbol + */ + getTradesOfSymbol( + symbol: string, + params?: { + sort?: SORT; + by?: SORT_BY; + from?: string; + till?: string; + limit?: number; + offset?: number; + } + ) { + return this.get(`public/trades/${symbol}`, params); + } + + /** + * Get orderbooks for all symbols or for the specified symbols + * + * An Order Book is an electronic list of buy and sell orders for a specific symbol, structured by price level + * + * Requires no API key Access Rights. + * + * https://api.exchange.cryptomkt.com/#order-books + * + * @param {object} [params] + * @param {string[]} [params.symbols] Optional. A list of symbol ids + * @param {number} [params.depth] Optional. Order Book depth. Default value is 100. Set to 0 to view the full Order Book. + * + * @return An object/dict with the order book for each queried symbol. indexed by symbol. + */ + getOrderBooks(params?: { + symbols?: string[]; + depth?: number; + }): Promise<{ [key: string]: OrderBook }> { + return this.get("public/orderbook/", params); + } + + /** + * Get order book of a symbol + * + * An Order Book is an electronic list of buy and sell orders for a specific symbol, structured by price level + * + * Requires no API key Access Rights. + * + * https://api.exchange.cryptomkt.com/#order-books + * + * @param {string} symbol The symbol id + * @param {number} [.depth] Optional. Order Book depth. Default value is 100. Set to 0 to view the full Order Book. + * + * @return The order book of the symbol + */ + getOrderBookOfSymbol(symbol: string, depth?: number): Promise { + return this.get(`public/orderbook/${symbol}`, { depth }); + } + + /** + * Get order book of a symbol with the desired volume for market depth search. + * + * An Order Book is an electronic list of buy and sell orders for a specific symbol, structured by price level + * + * Requires no API key Access Rights. + * + * https://api.exchange.cryptomkt.com/#order-books + * + * @param {object} params + * @param {string} params.symbol The symbol id + * @param {number} [params.volume] Desired volume for market depth search + * + * @return The order book of the symbol + */ + getOrderBookVolume({ + symbol, + volume, + }: { + symbol: string; + volume?: number; + }): Promise { + return this.get(`public/orderbook/${symbol}`, { volume }); + } + + /** + * Get candles for all symbols or for specified symbols + * + * Candels are used for OHLC representation + * + * The result contains candles with non-zero volume only (no trades = no candles). + * + * Requires no API key Access Rights. + * + * https://api.exchange.cryptomkt.com/#candles + * + * @param {object} [params] + * @param {string[]} [params.symbols] Optional. A list of symbol ids + * @param {PERIOD} [params.period] Optional. A valid tick interval. 'M1' (one minute), 'M3', 'M5', 'M15', 'M30', 'H1' (one hour), 'H4', 'D1' (one day), 'D7', '1M' (one month). Default is 'M30' + * @param {SORT} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC' + * @param {string} [params.from] Optional. Initial value of the queried interval. As Datetime + * @param {string} [params.till] Optional. Last value of the queried interval. As Datetime + * @param {number} [params.limit] Optional. Candles per query. Defaul is 10. Min is 1. Max is 1000 + * + * @return An object/dict with a list of candles for each symbol of the query. indexed by symbol. + */ + getCandles(params?: { + symbols?: string[]; + period?: PERIOD; + sort?: SORT; + from?: string; + till?: string; + limit?: number; + }): Promise<{ [key: string]: Candle[] }> { + return this.get("public/candles/", params); + } + + /** + * Get candles for a symbol + * + * Candels are used for OHLC representation + * + * The result contains candles with non-zero volume only (no trades = no candles). + * + * Requires no API key Access Rights. + * + * https://api.exchange.cryptomkt.com/#candles + * + * @param {string} symbol A symbol id + * @param {object} [params] + * @param {PERIOD} [params.period] Optional. A valid tick interval. 'M1' (one minute), 'M3', 'M5', 'M15', 'M30', 'H1' (one hour), 'H4', 'D1' (one day), 'D7', '1M' (one month). Default is 'M30' + * @param {SORT} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC' + * @param {string} [params.from] Optional. Initial value of the queried interval + * @param {string} [params.till] Optional. Last value of the queried interval + * @param {number} [params.limit] Optional. Candles per query. Defaul is 100. Max is 1000 + * @param {number} [params.offset] Optional. Default is 0. Min is 0. Max is 100000 + * + * @return A list of candles of a symbol + */ + getCandlesOfSymbol( + symbol: string, + params?: { + period?: PERIOD; + sort?: SORT; + from?: string; + till?: string; + limit?: number; + offset?: number; + } + ): Promise { + return this.get(`public/candles/${symbol}`, params); + } + + ///////////////////////// + // AUTHENTICATED CALLS // + ///////////////////////// + + ///////////// + // TRADING // + ///////////// + + /** + * Get the user's spot trading balance for all currencies with balance. + * + * Requires the "Orderbook, History, Trading balance" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#get-spot-trading-balance + * + * @return A list of spot balances. + */ + getSpotTradingBalances(): Promise { + return this.get("spot/balance"); + } + + /** + * Get the user spot trading balance of a currency + * + * Requires the "Orderbook, History, Trading balance" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#get-spot-trading-balance + * + * @param {string} currency The currency code to query the balance + * + * @return the spot trading balance of a currency. + */ + async getSpotTradingBalanceOfCurrency(currency: string): Promise { + const balance = await this.get(`spot/balance/${currency}`); + balance.currency = currency; + return balance; + } + + /** + * Get the user's active spot orders + * + * Requires the "Place/cancel orders" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#get-all-active-spot-orders + * + * @param {string} symbol Optional. A symbol for filtering the active spot orders + * + * @return A list of orders + */ + getAllActiveSpotOrders(symbol?: string): Promise { + return this.get("spot/order", { symbol }); + } + + /** + * Get an active spot order by its client order id + * + * Requires the "Place/cancel orders" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#get-active-spot-orders + * + * @param {string} client_order_id The clientOrderId of the order + * + * @return An order of the account + */ + getActiveSpotOrder(client_order_id: string): Promise { + return this.get(`spot/order/${client_order_id}`); + } + + /** + * Creates a new spot order + * + * Requires the "Place/cancel orders" API key Access Right. + * + * For fee, price accuracy and quantity, and for order status information see the docs. + * + * https://api.exchange.cryptomkt.com/#create-new-spot-order + * + * @param {object} params + * @param {string} params.symbol Trading symbol + * @param {SIDE} params.side 'buy' or 'sell' + * @param {string} params.quantity Order quantity + * @param {string} [params.client_order_id] Optional. If given must be unique within the trading day, including all active orders. If not given, is generated by the server + * @param {ORDER_TYPE} [params.type] Optional. 'limit', 'market', 'stopLimit', 'stopMarket', 'takeProfitLimit' or 'takeProfitMarket'. Default is 'limit' + * @param {TIME_IN_FORCE} [params.time_in_force] Optional. 'GTC', 'IOC', 'FOK', 'Day', 'GTD'. Default to 'GTC' + * @param {string} [params.price] Required for 'limit' and 'stopLimit'. limit price of the order + * @param {string} [params.stop_price] Required for 'stopLimit' and 'stopMarket' orders. stop price of the order + * @param {string} [params.expire_time] Required for orders with timeInForce = GDT + * @param {boolean} [params.strict_validate] Optional. If False, the server rounds half down for tickerSize and quantityIncrement. Example of ETHBTC: tickSize = '0.000001', then price '0.046016' is valid, '0.0460165' is invalid + * @param {boolean} [params.post_only] Optional. If True, your post_only order causes a match with a pre-existing order as a taker, then the order will be cancelled + * @param {string} [params.take_rate] Optional. Liquidity taker fee, a fraction of order volume, such as 0.001 (for 0.1% fee). Can only increase the fee. Used for fee markup. + * @param {string} [params.make_rate] Optional. Liquidity provider fee, a fraction of order volume, such as 0.001 (for 0.1% fee). Can only increase the fee. Used for fee markup. + * + * @return A new spot order + */ + createNewSpotOrder(params: { + symbol: string; + side: SIDE; + quantity: string; + client_order_id?: string; + type?: ORDER_TYPE; + time_in_force?: TIME_IN_FORCE; + price?: string; + stop_price?: string; + expire_time?: string; + strict_validate?: boolean; + post_only?: boolean; + take_rate?: string; + make_rate?: string; + }): Promise { + return this.post("spot/order", params); + } + + /** + * creates a list of spot orders + * + * Types or contingency: + * + * - CONTINGENCY.ALL_OR_NONE (CONTINGENCY.AON) + * - CONTINGENCY.ONE_CANCEL_OTHER (CONTINGENCY.OCO) + * - CONTINGENCY.ONE_TRIGGER_ONE_CANCEL_OTHER (CONTINGENCY.OTOCO) + * + * Restriction in the number of orders: + * + * - An AON list must have 2 or 3 orders + * - An OCO list must have 2 or 3 orders + * - An OTOCO must have 3 or 4 orders + * + * Symbol restrictions: + * + * - For an AON order list, the symbol code of orders must be unique for each order in the list. + * - For an OCO order list, there are no symbol code restrictions. + * - For an OTOCO order list, the symbol code of orders must be the same for all orders in the list (placing orders in different order books is not supported). + * + * ORDER_TYPE restrictions: + * - For an AON order list, orders must be ORDER_TYPE.LIMIT or ORDER_TYPE.Market + * - For an OCO order list, orders must be ORDER_TYPE.LIMIT, ORDER_TYPE.STOP_LIMIT, ORDER_TYPE.STOP_MARKET, ORDER_TYPE.TAKE_PROFIT_LIMIT or ORDER_TYPE.TAKE_PROFIT_MARKET. + * - An OCO order list cannot include more than one limit order (the same + * applies to secondary orders in an OTOCO order list). + * - For an OTOCO order list, the first order must be ORDER_TYPE.LIMIT, ORDER_TYPE.MARKET, ORDER_TYPE.STOP_LIMIT, ORDER_TYPE.STOP_MARKET, ORDER_TYPE.TAKE_PROFIT_LIMIT or ORDER_TYPE.TAKE_PROFIT_MARKET. + * - For an OTOCO order list, the secondary orders have the same restrictions as an OCO order + * - Default is ORDER_TYPE.Limit + * + * https://api.exchange.cryptomkt.com/#create-new-spot-order-list-2 + * + * @param {string} params.order_list_id Order list identifier. If omitted, it will be generated by the system upon order list creation. Must be equal to client_order_id of the first order in the request. + * @param {string} params.contingency_type Order list type. + * @param {OrderRequest[]} params.orders Orders in the list. + * @return A promise that resolves with a list of reports of the created orders + */ + async createNewSpotOrderList(params: { + order_list_id: string; + contingency_type: CONTINGENCY; + orders: OrderRequest[]; + }): Promise { + return this.post("spot/order/list", params); + } + + /** + * Replaces a spot order + * + * Requires the "Place/cancel orders" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#replace-spot-order + * + * @param {string} client_order_id client_order_id of the old order + * @param {object} params Parameters + * @param {string} params.new_client_order_id client_order_id for the new order. + * @param {string} params.quantity Order quantity. + * @param {string} [params.price] Required if order type is limit, stopLimit, or takeProfitLimit. Order price. + * @param {boolean} [params.strict_validate] Optional. Price and quantity will be checked for incrementation within the symbol’s tick size and quantity step. See the symbol's tick_size and quantity_increment. + * + * @returns the new spot order + */ + replaceSpotOrder( + client_order_id: string, + params: { + new_client_order_id: string; + quantity: string; + price?: string; + strictValidate?: boolean; + } + ): Promise { + return this.patch(`spot/order/${client_order_id}`, params); + } + + /** + * Cancel all active spot orders, or all active orders for a specified symbol + * + * Requires the "Place/cancel orders" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#cancel-all-spot-orders + * + * @returns A list with the canceled orders + */ + cancelAllSpotOrders(): Promise { + return this.delete("spot/order"); + } + + /** + * Cancel the order with clientOrderId + * + * Requires authentication + * + * https://api.exchange.cryptomkt.com/#cancel-spot-order + * + * @param {string} client_order_id the client_order_id of the order to cancel + * + * @return The canceled order + */ + cancelSpotOrder(client_order_id: string): Promise { + return this.delete(`spot/order/${client_order_id}`); + } + + /** + * Get the personal trading commission rates for all symbols + * + * Requires the "Place/cancel orders" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#get-all-trading-commission + * + * @return A list of commission rates + */ + getAllTradingCommissions(): Promise { + return this.get(`spot/fee`); + } + + /** + * Get the personal trading commission rate for a symbol + * + * Requires the "Place/cancel orders" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#get-trading-commission + * + * @param {string} symbol The symbol of the commission rate + * + * @return The commission rate of a symbol + */ + async getTradingCommission(symbol: string): Promise { + const commission = await this.get(`spot/fee/${symbol}`); + commission.symbol = symbol; + return commission; + } + + ////////////////////////// + // SPOT TRADING HISTORY // + ////////////////////////// + + /** + * Get all the spot orders + * + * Orders without executions are deleted after 24 hours + * + * Requires the "Orderbook, History, Trading balance" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#spot-orders-history + * + * @param {object} [params] + * @param {string} [params.symbol] Optional. Filter orders by symbol + * @param {SORT_BY} [params.by] Optional. Sorting type. 'timestamp' or 'id'. Default is 'timestamp' + * @param {SORT} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC' + * @param {string} [params.from] Optional. Initial value of the queried interval. + * @param {string} [params.till] Optional. Last value of the queried interval + * @param {number} [params.limit] Optional. Trades per query. Defaul is 100. Max is 1000 + * @param {number} [params.offset] Optional. Default is 0. Min is 0. Max is 100000 + * + * @return A list of orders + */ + getSpotOrdersHistory(params?: { + symbol?: string; + by?: SORT_BY; + sort?: SORT; + from?: string; + till?: string; + limit?: number; + offset?: number; + }): Promise { + return this.get("spot/history/order", params); + } + + /** + * Get the user's spot trading history + * + * Requires the "Orderbook, History, Trading balance" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#spot-trades-history + * + * @param {object} [params] + * @param {string} [params.order_id] Optional. Order unique identifier as assigned by the exchange + * @param {string} [params.symbol] Optional. Filter trades by symbol + * @param {SORT} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC' + * @param {SORT_BY} [params.by] Optional. Sorting type.'timestamp' or 'id'. Default is 'id' + * @param {string} [params.from] Optional. Initial value of the queried interval. + * @param {string} [params.till] Optional. Last value of the queried interval. + * @param {number} [params.limit] Optional. Trades per query. Defaul is 100. Max is 1000 + * @param {number} [params.offset] Optional. Default is 0. Max is 100000 + * @return A list of trades + */ + getSpotTradesHistory(params?: { + order_id?: string; + symbol?: string; + sort?: SORT; + by?: SORT_BY; + from?: string; + till?: string; + limit?: number; + offset?: number; + }): Promise { + return this.get("spot/history/trade", params); + } + + /////////////////////// + // WALLET MANAGEMENT // + /////////////////////// + + /** + * Get the user's wallet balances, except zero balances. + * + * Requires the "Payment information" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#wallet-balance + * + * @return A list of wallet balances + */ + getWalletBalance(): Promise { + return this.get("wallet/balance"); + } + + /** + * Get the user's wallet balance of a currency + * + * Requires the "Payment information" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#wallet-balance + * + * @param {string} currency Currency to get the balance + * + * @return The wallet balance of the currency + */ + async getWalletBalanceOfCurrency(currency: string): Promise { + let balance = await this.get(`wallet/balance/${currency}`); + balance.currency = currency; + return balance; + } + + /** + * Get the current addresses of the user + * + * Requires the "Payment information" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#deposit-crypto-address + * + * @return A list of currency addresses + */ + getDepositCryptoAddresses(): Promise { + return this.get(`wallet/crypto/address`); + } + + /** + * Get the current address of a currency + * + * Requires the "Payment information" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#deposit-crypto-address + * + * @param {string} currency currency to get the address + * + * @return the currency address + */ + async getDepositCryptoAddressOfCurrency(currency: string): Promise
{ + const addressList = await this.get(`wallet/crypto/address`, { currency }); + return addressList[0]; + } + + /** + * Creates a new address for the currency + * + * Requires the "Payment information" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#deposit-crypto-address + * + * @param {string} currency currency to create a new address + * + * @return The created address for the currency + */ + createDepositCryptoAddress(currency: string): Promise
{ + return this.post(`wallet/crypto/address`, { currency }); + } + + /** + * Get the last 10 unique addresses used for deposit, by currency + * + * Addresses used a long time ago may be omitted, even if they are among the last 10 unique addresses + * + * Requires the "Payment information" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#last-10-deposit-crypto-address + * + * @param {string} currency currency to get the list of addresses + * + * @return A list of addresses + */ + getLast10DepositCryptoAddresses(currency: string): Promise { + return this.get(`wallet/crypto/address/recent-deposit`, { currency }); + } + + /** + * Get the last 10 unique addresses used for withdrawals, by currency + * + * Addresses used a long time ago may be omitted, even if they are among the last 10 unique addresses + * + * Requires the "Payment information" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#last-10-withdrawal-crypto-addresses + * + * @param {string} currency currency to get the list of addresses + * + * @return A list of addresses + */ + getLast10WithdrawalCryptoAddresses(currency: string): Promise { + return this.get(`wallet/crypto/address/recent-withdraw`, { currency }); + } + + /** + * Withdraw cryptocurrency + * + * Please take note that changing security settings affects withdrawals: + * - It is impossible to withdraw funds without enabling the two-factor authentication (2FA) + * - Password reset blocks withdrawals for 72 hours + * - Each time a new address is added to the whitelist, it takes 48 hours before that address becomes active for withdrawal + * + * Successful response to the request does not necessarily mean the resulting transaction got executed immediately. It has to be processed first and may eventually be rolled back. + * + * To see whether a transaction has been finalized, call {@link getTransaction} + * + * Requires the "Withdraw cryptocurrencies" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#withdraw-crypto + * + * @param {object} params + * @param {string} params.currency currency code of the crypto to withdraw + * @param {string} params.amount the amount to be sent to the specified address + * @param {string} params.address the address identifier + * @param {string} [params.payment_id] Optional. + * @param {boolean} [params.include_fee] Optional. If true then the total spent amount includes fees. Default false + * @param {boolean} [params.auto_commit] Optional. If false then you should commit or rollback transaction in an hour. Used in two phase commit schema. Default true + * @param {USE_OFFCHAIN} [params.use_offchain] Whether the withdrawal may be committed offchain. 'never', 'optionally', 'required' +Accepted values: never, optionally, required + * @param {string} [params.public_comment] Optional. Maximum length is 255. + * + * @return The transaction id, asigned by the exchange + */ + async withdrawCrypto(params: { + currency: string; + amount: string; + address: string; + paymend_id?: string; + include_fee?: boolean; + auto_commit?: boolean; + use_offchain?: USE_OFFCHAIN; + public_comment?: string; + }): Promise { + const response = await this.post("wallet/crypto/withdraw", params); + return response["id"]; + } + + /** + * Commit a withdrawal + * + * Requires the "Withdraw cryptocurrencies" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#withdraw-crypto-commit-or-rollback + * + * @param {string} id the withdrawal transaction identifier + * + * @return The transaction result. true if the commit is successful + */ + async withdrawCryptoCommit(id: string): Promise { + const response = await this.put(`wallet/crypto/withdraw/${id}`); + return response["result"]; + } + + /** + * Rollback a withdrawal + * + * Requires the "Withdraw cryptocurrencies" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#withdraw-crypto-commit-or-rollback + * + * @param {string} id the withdrawal transaction identifier + * + * @return The transaction result. true if the rollback is successful + */ + async withdrawCryptoRollback(id: string): Promise { + const response = await this.delete(`wallet/crypto/withdraw/${id}`); + return response["result"]; + } + + /** + * Get an estimate of the withdrawal fee + * + * Requires the "Payment information" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#estimate-withdraw-fee + * + * @param {object} params + * @param {string} params.currency the currency code for withdraw + * @param {string} params.amount the expected withdraw amount + * + * @return The expected fee + */ + async getEstimateWithdrawFee(params: { + currency: string; + amount: string; + }): Promise { + const response = await this.get("wallet/crypto/fee/estimate", params); + return response["fee"]; + } + + /** + * Converts between currencies + * + * Successful response to the request does not necessarily mean the resulting transaction got executed immediately. It has to be processed first and may eventually be rolled back. + * + * To see whether a transaction has been finalized, call {@link getTransaction} + * + * Requires the "Payment information" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#convert-between-currencies + * + * @param {object} params + * @param {string} params.from_currency currency code of origin + * @param {string} params.to_currency currency code of destiny + * @param {string} params.amount the amount to be sent + * + * @return A list of transaction identifiers of the convertion + */ + async convertBetweenCurrencies(params: { + from_currency: string; + to_currency: string; + amount: string; + }): Promise { + const response = await this.post("wallet/convert", params); + return response["result"]; + } + + /** + * Check if an address is from this account + * + * Requires the "Payment information" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#check-if-crypto-address-belongs-to-current-account + * + * @param {string} address The address to check + * + * @return True if it is from the current account + */ + async checkIfCryptoAddressBelongsToCurrentAccount( + address: string + ): Promise { + const response = await this.get(`wallet/crypto/address/check-mine`, { + address, + }); + return response["result"]; + } + + /** + * Transfer funds between account types. + * + * Requires the "Payment information" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#transfer-between-wallet-and-exchange + * + * @param {object} params + * @param {string} params.currency Currency code for transfering + * @param {number} params.amount Amount to be transfered + * @param {ACCOUNT} params.source Transfer source account type. "wallet" or "spot". Must not be the same as destination + * @param {ACCOUNT} params.destination Transfer destination account type. +Accepted values: wallet, spot. Must not be the same as source + * + * @return the transaction identifier of the transfer + */ + async transferBetweenWalletAndExchange(params: { + currency: string; + amount: number; + source: ACCOUNT; + destination: ACCOUNT; + }): Promise { + const response = await this.post("wallet/transfer", params); + return response["id"]; + } + + /** + * Transfer funds to another user + * + * Requires the "Withdraw cryptocurrencies" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#transfer-money-to-another-user + * + * @param {object} params + * @param {string} params.currency currency code + * @param {number} params.amount amount to be transfered between balances + * @param {IDENTIFY_BY} params.by type of identifier. 'email' or 'username' + * @param {string} params.identifier the email or the username of the recieving user + * + * @return The transaction identifier of the transfer + */ + async transferMoneyToAnotherUser(params: { + currency: string; + amount: number; + by: IDENTIFY_BY; + identifier: string; + }): Promise { + const response = await this.post("wallet/internal/withdraw", params); + return response["result"]; + } + + /** + * Get transactions of the account + * + * Important: + * - The list of supported transaction types may be expanded in future versions. + * - Some transaction subtypes are reserved for future use and do not purport to provide any functionality on the platform. + * - The list of supported transaction subtypes may be expanded in future versions. + * + * Requires the "Payment information" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#get-transactions-history + * + * + * @param {object} [params] + * @param {string[]} [params.tx_ids] Optional. List of transaction identifiers to query + * @param {TRANSACTION_TYPE[]} [params.types] Optional. List of types to query. valid types are: 'DEPOSIT', 'WITHDRAW', 'TRANSFER' and 'SWAP' + * @param {TRANSACTION_SUBTYPE[]} [params.subtypes] Optional. List of subtypes to query. valid subtypes are: 'UNCLASSIFIED', 'BLOCKCHAIN', 'AIRDROP', 'AFFILIATE', 'STAKING', 'BUY_CRYPTO', 'OFFCHAIN', 'FIAT', 'SUB_ACCOUNT', 'WALLET_TO_SPOT', 'SPOT_TO_WALLET', 'WALLET_TO_DERIVATIVES', 'DERIVATIVES_TO_WALLET', 'CHAIN_SWITCH_FROM', 'CHAIN_SWITCH_TO' and 'INSTANT_EXCHANGE' + * @param {TRANSACTION_STATUS[]} [params.statuses] Optional. List of statuses to query. valid subtypes are: 'CREATED', 'PENDING', 'FAILED', 'SUCCESS' and 'ROLLED_BACK' + * @param {SORT_BY} [params.order_by] Optional. Defines the sorting type.'created_at' or 'id'. Default is 'created_at' + * @param {string} [params.from] Optional. Interval initial value when ordering by 'created_at'. As Datetime + * @param {string} [params.till] Optional. Interval end value when ordering by 'created_at'. As Datetime + * @param {string} [params.id_from] Optional. Interval initial value when ordering by id. Min is 0 + * @param {string} [params.id_till] Optional. Interval end value when ordering by id. Min is 0 + * @param {string} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC'. + * @param {number} [params.limit] Optional. Transactions per query. Defaul is 100. Max is 1000 + * @param {number} [params.offset] Optional. Default is 0. Max is 100000 + * + * @return A list of transactions + */ + getTransactionHistory(params?: { + tx_ids?: string[]; + types?: TRANSACTION_TYPE[]; + subtypes?: TRANSACTION_SUBTYPE[]; + statuses?: TRANSACTION_STATUS[]; + order_by: SORT_BY; + from: string; + till?: string; + id_from?: string; + id_till?: string; + sort?: string; + limit?: number; + offset?: number; + }): Promise { + return this.get("wallet/transactions", params); + } + + /** + * Get the transactions of the account by its identifier + * + * Requires the "Payment information" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#get-transactions-history + * + * @param {string} id The identifier of the transaction + * + * @return The transaction with the given id + */ + getTransaction(id: string): Promise { + return this.get(`wallet/transactions/${id}`); + } + + /** + * get the status of the offchain + * + * Requires the "Payment information" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#check-if-offchain-is-available + * + * @param {object} params + * @param {string} params.currency currency code + * @param {string} params.address address identifier + * @param {string} [params.payment_id] Optional + * + * @return True if the offchain is available + */ + async checkIfOffchainIsAvailable(params: { + currency: string; + address: string; + payment_id?: string; + }): Promise { + const response = await this.post( + `wallet/crypto/check-offchain-available`, + params + ); + return response["result"]; + } + + /** + * Get the list of amount locks + * + * Requires the "Payment information" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#get-amount-locks + * + * @param {object} [params] + * @param {string} [params.currency] Optional. Currency code + * @param {boolean} [params.active] Optional. value showing whether the lock is active + * @param {number} [params.limit] Optional. Dafault is 100. Min is 0. Max is 1000 + * @param {number} [params.offset] Optional. Default is 0. Min is 0 + * @param {Datetime} [params.from] Optional. Interval initial value + * @param {Datetime} [params.till] Optional. Interval end value + * + * @return A list of locks + */ + getAmountLocks(params?: { + currency: string; + active?: boolean; + limit?: number; + offset?: number; + from?: string; + till?: string; + }): Promise { + return this.get("wallet/amount-locks", params); + } + + ////////////////// + // SUB ACCOUNTS // + ////////////////// + + /** + * Returns list of sub-accounts per a super account. + * + * Requires no API key Access Rights. + * + * https://api.exchange.cryptomkt.com/#get-sub-accounts-list + * + * @return A list of subaccounts + */ + async getSubAccountList(): Promise { + const response = await this.get("sub-account"); + return response["result"]; + } + + /** + * Freezes sub-accounts listed. It implies that the Sub-accounts frozen wouldn't be able to: + * - login + * - withdraw funds + * - trade + * - complete pending orders + * - use API keys + * + * For any sub-account listed, all orders will be canceled and all funds will be transferred form the Trading balance. + * + * Requires no API key Access Rights + * + * https://api.exchange.cryptomkt.com/#freeze-sub-account + * + * @param {string[]} sub_account_ids A list of sub-account IDs to freeze + * + * @return true if the freeze was successful + */ + async freezeSubAccounts(sub_account_ids: string[]): Promise { + const response = await this.post("sub-account/freeze", { sub_account_ids }); + return response["result"]; + } + + /** + * Activates sub-accounts listed. It would make sub-accounts active after being frozen + * + * Requires no API key Access Rights + * + * https://api.exchange.cryptomkt.com/#activate-sub-account + * + * @param {string[]} sub_account_ids A list of account IDs to activate + * + * @return true if the activation was successful + */ + async activateSubAccounts(sub_account_ids: string[]): Promise { + const response = await this.post("sub-account/activate", { + sub_account_ids, + }); + return response["result"]; + } + + /** + * Transfers funds from the super-account to a sub-account or from a sub-account to the super-account + * + * Requires the "Withdraw cryptocurrencies" API key Access Right + * + * https://api.exchange.cryptomkt.com/#transfer-funds + * + * @param {object} params Parameters + * @param {number} params.sub_account_id The account ID of the account to transfer from or to + * @param {number} params.amount the amount of currency to transfer + * @param {string} params.currency the currency to transfer + * @param {TRANSFER_TYPE} params.type the type of transfer. TransferType.TO_SUB_ACCOUNT or TransferType.FROM_SUB_ACCOUNT + * + * @return The transaction ID of the tranfer + */ + async transferFunds(params: { + sub_account_id: number; + amount: number; + currency: string; + type: TRANSFER_TYPE; + }): Promise { + const response = await this.post("sub-account/transfer", params); + return response["response"]; + } + + /** + * Returns a list of withdrawal settings for sub-accounts listed + * + * Requires the "Payment information" API key Access Right + * + * https://api.exchange.cryptomkt.com/#get-acl-settings + * + * @param {string[]} sub_account_ids The list of sub-account IDs of the sub-accounts to get the ACL settings. The subAccountID filed is ignored. + * + * @return A list of withdraw settings for sub-accounts listed + */ + async getACLSettings(sub_account_ids: string[]): Promise { + const response = await this.get("sub-account/acl", { sub_account_ids }); + return response["result"]; + } + + /** + * Disables or enables withdrawals for a sub-account + * + * Requires the "Payment information" API key Access Right + * + * https://api.exchange.cryptomkt.com/#change-acl-settings + * + * @param {object} params Parameters + * @param {string[]} params.sub_account_ids The list of sub-account IDs to change the ACL settings + * @param {boolean} [params.deposit_address_generation_enabled] value indicaiting permission for deposits + * @param {boolean} [params.withdraw_enabled] value indicating permission for withdrawals + * @param {string} [params.description] Textual description. + * @param {string} [params.created_at] ACL creation time + * @param {string} [params.updated_at] ACL update time + * @return The list of the updated withdraw settings of the changed sub-account + */ + async changeACLSettings(params: { + sub_account_ids: string[]; + deposit_address_generation_enabled?: boolean; + withdraw_enabled?: boolean; + description?: string; + created_at?: string; + updated_at?: string; + }): Promise { + const response = await this.post("sub-account/acl", params); + return response["result"]; + } + + /** + * Returns non-zero balance values for a sub-account. + * + * Report will include the wallet and Trading balances for each currency. + * + * It is functional with no regard to the state of a sub-account. All account types are optional and appears only in case of non-zero balance + * + * Requires the "Payment information" API key Access Right + * + * https://api.exchange.cryptomkt.com/#get-sub-account-balance + * + * @param {string} sub_account_id the sub-account ID of the sub-account to get the balances + * + * @return A list of balances of the sub-account + */ + async getSubAccountBalance( + sub_account_id: string + ): Promise { + const response = await this.get(`sub-account/balance/${sub_account_id}`); + return response["result"]; + } + + /** + * Returns sub-account crypto address for currency + * + * Requires the "Payment information" API key Access Right + * + * https://api.exchange.cryptomkt.com/#get-sub-account-crypto-address + * + * @param {string} sub_account_id the sub-account ID to get the crypto address + * @param {string} currency currency code to get the crypto address + * + * @return The crypto address + */ + async getSubAccountCryptoAddress( + sub_account_id: string, + currency: string + ): Promise { + const response = await this.get( + `sub-account/crypto/address/${sub_account_id}/${currency}` + ); + return response["result"]; + } +} diff --git a/lib/constants.ts b/lib/constants.ts new file mode 100644 index 0000000..dc016f0 --- /dev/null +++ b/lib/constants.ts @@ -0,0 +1,164 @@ +export enum SIDE { + BUY = "buy", + SELL = "sell", +} + +export enum ORDER_TYPE { + LIMIT = "limit", + MARKET = "market", + STOP_LIMIT = "stopLimit", + STOP_MARKET = "stopMarket", + TAKE_PROFIT_LIMIT = "takeProfitLimit", + TAKE_PROFIT_MARKET = "takeProfitMarket", +} + +export enum TIME_IN_FORCE { + GTC = "GTC", // Good Till Cancel + IOC = "IOC", // Immediate or Cancel + FOK = "FOK", // Fill or Kill + DAY = "Day", // valid during Day + GTD = "GTD", // Good Till Date +} + +export enum SORT { + ASC = "ASC", + DESC = "DESC", +} + +export enum SORT_BY { + TIMESTAMP = "timestamp", + CREATED_AT = "created_at", + ID = "id", +} + +export enum PERIOD { + _1_MINUTE = "M1", + _3_MINUTES = "M3", + _5_MINUTES = "M5", + _15_MINUTES = "M15", + _30_MINUTES = "M30", + _1_HOUR = "H1", + _4_HOURS = "H4", + _1_DAY = "D1", + _7_DAYS = "D7", + _1_MONTH = "1M", +} + +export enum IDENTIFY_BY { + EMAIL = "email", + USERNAME = "username", +} + +export enum ACCOUNT { + WALLET = "wallet", + SPOT = "spot", +} + +export enum USE_OFFCHAIN { + NEVER = "never", + OPTIONALY = "optionaly", + REQUIRED = "required", +} + +export enum TRANSACTION_TYPE { + DEPOSIT = "DEPOSIT", + WITHDRAW = "WITHDRAW", + TRANSFER = "TRANSFER", + SWAP = "SWAP", +} + +export enum TRANSACTION_SUBTYPE { + UNCLASSIFIED = "UNCLASSIFIED", + BLOCKCHAIN = "BLOCKCHAIN", + AFFILIATE = "AFFILIATE", + OFFCHAIN = "OFFCHAIN", + FIAT = "FIAT", + SUBACCOUNT = "SUB_ACCOUNT", + WALLETTOSPOT = "WALLET_TO_SPOT", + SPOTTOWALLET = "SPOT_TO_WALLET", + CHAIN_SWITCH_FROM = "CHAIN_SWITCH_FROM", + CHAIN_SWITCH_TO = "CHAIN_SWITCH_TO", +} + +export enum TRANSACTION_STATUS { + CREATED = "CREATED", + PENDING = "PENDING", + FAILED = "FAILED", + SUCCESS = "SUCCESS", + ROLLEDBACK = "ROLLED_BACK", +} + +export enum TICKER_SPEED { + _1_S = "1s", + _3_S = "3s", +} + +export enum ORDER_BOOK_SPEED { + _100_MS = "100ms", + _500_MS = "500ms", + _1000_MS = "1000ms", +} + +export enum DEPTH { + _5 = "D5", + _10 = "D10", + _20 = "D20", +} + +export enum NOTIFICATION { + SNAPSHOT = "snapshot", + UPDATE = "update", + DATA = "data", +} + +export enum CONTINGENCY { + AON = "allOrNone", + OCO = "oneCancelOther", + OTOCO = "oneTriggerOneCancelOther", + ALL_OR_NONE = AON, + ONE_CANCEL_OTHER = OCO, + ONE_TRIGGER_ONE_CANCEL_OTHER = OTOCO, +} + +export enum ORDER_STATUS { + NEW = "new", + SUSPENDED = "suspended", + PARTIALLY_FILLED = "partiallyFilled", + FILLED = "filled", + CANCELED = "canceled", + EXPIRED = "expired", +} + +export enum REPORT_TYPE { + STATUS = "status", + NEW = "new", + CANCELED = "canceled", + REJECTED = "rejected", + EXPIRED = "expired", + SUSPENDED = "suspended", + TRADE = "trade", + REPLACED = "replaced", +} + +export enum SYMBOL_STATUS { + WORKING = "working", + SUSPENDED = "suspended", +} + +export enum TRANSFER_TYPE { + TO_SUB_ACCOUNT = "to_sub_account", + FROM_SUB_ACCOUNT = "from_sub_account", +} + +export enum SUB_ACCOUNT_STATUS { + NEW = "new", + ACTIVE = "active", + DISABLE = "disable", +} + +export enum NOTIFICATION_TYPE { + SNAPSHOT = "snapshot", + UPDATE = "update", + DATA = "data", + COMMAND = "COMMAND", +} diff --git a/lib/exceptions.js b/lib/exceptions.js deleted file mode 100644 index 1070925..0000000 --- a/lib/exceptions.js +++ /dev/null @@ -1,43 +0,0 @@ -class CryptomarketSDKException extends Error { - constructor(...args) { - super(...args) - } -} - - -class CryptomarketAPIException extends CryptomarketSDKException { - constructor(jsonResponse, status) { - let error = jsonResponse['error'] - let message = error['message'] - super(message) - if ('description' in error) { - this.description = error['description'] - } - this.responseStatus = status - this.response = jsonResponse - this.code = error['code'] - } -} - - -class ArgumentFormatException extends CryptomarketSDKException { - constructor(message, valid_options) { - if (valid_options) { - message += ' Valid options are:' - for (option of valid_options) { - message += ` ${option},` - } - if (valid_options.lenght > 0) { - message = this.message.slice(0,this.message.lenght-1) - } - } - super(message) - } -} - - -module.exports = { - CryptomarketSDKException, - CryptomarketAPIException, - ArgumentFormatException -} \ No newline at end of file diff --git a/lib/exceptions.ts b/lib/exceptions.ts new file mode 100644 index 0000000..d3a8cbf --- /dev/null +++ b/lib/exceptions.ts @@ -0,0 +1,22 @@ +export class CryptomarketSDKException extends Error { + constructor(...args: any) { + super(...args); + } +} + +export class CryptomarketAPIException extends CryptomarketSDKException { + status: any; + code: number; + constructor( + { + code, + message, + description, + }: { code: any; message: string; description: any }, + status?: any + ) { + super(`(code=${code}) ${message}. ${description}`); + this.code = code; + this.status = status; + } +} diff --git a/lib/index.js b/lib/index.js deleted file mode 100644 index 4d1420c..0000000 --- a/lib/index.js +++ /dev/null @@ -1,15 +0,0 @@ -const { Client } = require("./client") -const { PublicClient } = require ("./websocket/publicClient") -const { AccountClient } = require ("./websocket/accountClient") -const { TradingClient } = require ("./websocket/tradingClient") -const { CryptomarketSDKException, CryptomarketAPIException, ArgumentFormatException} = require("./exceptions") - -module.exports = { - Client, - WSPublicClient:PublicClient, - WSTradingClient:TradingClient, - WSAccountClient:AccountClient, - CryptomarketSDKException, - CryptomarketAPIException, - ArgumentFormatException, -} \ No newline at end of file diff --git a/lib/index.ts b/lib/index.ts new file mode 100644 index 0000000..a1d99c5 --- /dev/null +++ b/lib/index.ts @@ -0,0 +1,21 @@ +import { Client } from "./client"; +import { MarketDataClient as WSMarketDataClient } from "./websocket/marketDataClient"; +import { WalletClient as WSWalletClient } from "./websocket/walletClient"; +import { TradingClient as WSTradingClient } from "./websocket/tradingClient"; +import { + CryptomarketSDKException, + CryptomarketAPIException, +} from "./exceptions"; +import * as constants from "./constants"; +import * as models from "./models"; + +export { + Client, + WSMarketDataClient, + WSTradingClient, + WSWalletClient, + CryptomarketSDKException, + CryptomarketAPIException, + constants, + models, +}; diff --git a/lib/models.ts b/lib/models.ts new file mode 100644 index 0000000..32ae30e --- /dev/null +++ b/lib/models.ts @@ -0,0 +1,24 @@ +export * from "./models/ACLSettings"; +export * from "./models/Address"; +export * from "./models/AmountLock"; +export * from "./models/Balance"; +export * from "./models/Candle"; +export * from "./models/Commission"; +export * from "./models/Currency"; +export * from "./models/Network"; +export * from "./models/Order"; +export * from "./models/OrderBook"; +export * from "./models/Price"; +export * from "./models/PriceHistory"; +export * from "./models/PublicTrade"; +export * from "./models/Report"; +export * from "./models/SubAccount"; +export * from "./models/SubAccountBalance"; +export * from "./models/Symbol"; +export * from "./models/Ticker"; +export * from "./models/TradeOfOrder"; +export * from "./models/Trade" +export * from "./models/Transactions"; +export * from "./models/WSCandle"; +export * from "./models/WSTicker"; +export * from "./models/WSTrades"; \ No newline at end of file diff --git a/lib/models/ACLSettings.ts b/lib/models/ACLSettings.ts new file mode 100644 index 0000000..bc85777 --- /dev/null +++ b/lib/models/ACLSettings.ts @@ -0,0 +1,8 @@ +export interface ACLSettings { + sub_account_id: string; + deposit_address_generation_enabled: boolean; + withdraw_enabled: boolean; + description: string; + created_at: string; + updated_at: string; +} diff --git a/lib/models/Address.ts b/lib/models/Address.ts new file mode 100644 index 0000000..fab1ada --- /dev/null +++ b/lib/models/Address.ts @@ -0,0 +1,6 @@ +export interface Address { + address: string; + currency: string; + payment_id: string; + public_key: string; +} diff --git a/lib/models/AmountLock.ts b/lib/models/AmountLock.ts new file mode 100644 index 0000000..6083c18 --- /dev/null +++ b/lib/models/AmountLock.ts @@ -0,0 +1,11 @@ +export interface AmountLock { + id: number; + currency: string; + amount: string; + date_end: string; + description: string; + cancelled: boolean; + cancelled_at: string; + cancel_description: string; + created_at: string; +} diff --git a/lib/models/Balance.ts b/lib/models/Balance.ts new file mode 100644 index 0000000..28e1337 --- /dev/null +++ b/lib/models/Balance.ts @@ -0,0 +1,6 @@ +export interface Balance { + currency: string; + available: string; + reserved: string; + reserved_margin: string; +} diff --git a/lib/models/Candle.ts b/lib/models/Candle.ts new file mode 100644 index 0000000..4cf7186 --- /dev/null +++ b/lib/models/Candle.ts @@ -0,0 +1,9 @@ +export interface Candle { + timestamp: string; + open: string; + close: string; + min: string; + max: string; + volume: string; + volume_quote: string; +} diff --git a/lib/models/Commission.ts b/lib/models/Commission.ts new file mode 100644 index 0000000..ae80e71 --- /dev/null +++ b/lib/models/Commission.ts @@ -0,0 +1,5 @@ +export interface Commission { + symbol: string; + take_rate: string; + make_rate: string; +} diff --git a/lib/models/Currency.ts b/lib/models/Currency.ts new file mode 100644 index 0000000..911fb6c --- /dev/null +++ b/lib/models/Currency.ts @@ -0,0 +1,12 @@ +import { Network } from "./Network"; + +export interface Currency { + full_name: string; + crypto: boolean; + payin_enabled: boolean; + payout_enabled: boolean; + transfer_enabled: boolean; + precision_transfer: string; + networks: Network[]; +} + diff --git a/lib/models/Network.ts b/lib/models/Network.ts new file mode 100644 index 0000000..da79b30 --- /dev/null +++ b/lib/models/Network.ts @@ -0,0 +1,17 @@ +export interface Network { + network: string; + protocol: string; + default: boolean; + payin_enabled: boolean; + payout_enabled: boolean; + precision_payout: string; + payout_fee: string; + payout_is_payment_id: boolean; + payin_payment_id: boolean; + payin_confirmations: number; + address_regrex: string; + payment_id_regex: string; + low_processing_time: string; + high_processing_time: string; + avg_processing_time: string; +} diff --git a/lib/models/Order.ts b/lib/models/Order.ts new file mode 100644 index 0000000..43a74a0 --- /dev/null +++ b/lib/models/Order.ts @@ -0,0 +1,45 @@ +import { + CONTINGENCY, + ORDER_STATUS, + ORDER_TYPE, + SIDE, + TIME_IN_FORCE, +} from "../constants"; + +export interface Order { + id: number; + client_order_id: string; + symbol: string; + side: SIDE; + status: ORDER_STATUS; + type: ORDER_TYPE; + time_in_force: TIME_IN_FORCE; + quantity: string; + price: string; + quantity_cumulative: string; + created_at: string; + updated_at: string; + expire_time: string; + stop_price: string; + post_only: boolean; + trades: string; + original_client_order_id: string; + order_list_id: string; + contingency_type: CONTINGENCY; +} + +export interface OrderRequest { + symbol: string; + side: SIDE; + quantity: string; + client_order_id?: string; + type?: ORDER_TYPE; + time_in_force?: TIME_IN_FORCE; + price?: string; + stop_price?: string; + expire_time?: string; + strict_validate?: boolean; + post_only?: boolean; + take_rate?: string; + make_rate?: string; +} diff --git a/lib/models/OrderBook.ts b/lib/models/OrderBook.ts new file mode 100644 index 0000000..85c3098 --- /dev/null +++ b/lib/models/OrderBook.ts @@ -0,0 +1,22 @@ +export interface WSOrderBook { + t: number; + s: number; + a: OrderBookLevel[]; + b: OrderBookLevel[]; +} + +export type OrderBookLevel = [price: number, amount: number]; + +export interface OrderBookTop { + t: number; + a: string; + A: string; + b: string; + B: string; +} + +export interface OrderBook { + timestamp: string; + ask: OrderBookLevel[]; + bid: OrderBookLevel[]; +} diff --git a/lib/models/Price.ts b/lib/models/Price.ts new file mode 100644 index 0000000..1383de2 --- /dev/null +++ b/lib/models/Price.ts @@ -0,0 +1,5 @@ +export interface Price { + currency: string; + price: string; + timestamp: string; +} diff --git a/lib/models/PriceHistory.ts b/lib/models/PriceHistory.ts new file mode 100644 index 0000000..520ed29 --- /dev/null +++ b/lib/models/PriceHistory.ts @@ -0,0 +1,12 @@ +export interface PriceHistory { + currency: string; + history: PricePoint[]; +} + +export interface PricePoint { + timestamp: string; + open: string; + close: string; + min: string; + max: string; +} diff --git a/lib/models/PublicTrade.ts b/lib/models/PublicTrade.ts new file mode 100644 index 0000000..b674c84 --- /dev/null +++ b/lib/models/PublicTrade.ts @@ -0,0 +1,7 @@ +export interface PublicTrade { + id: number; + price: string; + qty: string; + side: string; + timestamp: string; +} diff --git a/lib/models/Report.ts b/lib/models/Report.ts new file mode 100644 index 0000000..c091874 --- /dev/null +++ b/lib/models/Report.ts @@ -0,0 +1,23 @@ +export interface Report { + id: number; + client_order_id: string; + symbol: string; + side: string; + status: string; + type: string; + time_in_force: string; + quantity: number; + price: number; + cum_quantity: number; + post_only: boolean; + created_at: string; + updated_at: string; + stop_price: number; + original_client_order_id: string; + trade_id: number; + trade_quantity: number; + trade_price: number; + trade_fee: number; + trade_taker: boolean; + report_type: string; +} diff --git a/lib/models/SubAccount.ts b/lib/models/SubAccount.ts new file mode 100644 index 0000000..db4e381 --- /dev/null +++ b/lib/models/SubAccount.ts @@ -0,0 +1,7 @@ +import { SUB_ACCOUNT_STATUS } from "../constants"; + +export interface SubAccount { + sub_account_id: string; + email: string; + status: SUB_ACCOUNT_STATUS; +} diff --git a/lib/models/SubAccountBalance.ts b/lib/models/SubAccountBalance.ts new file mode 100644 index 0000000..38a88ff --- /dev/null +++ b/lib/models/SubAccountBalance.ts @@ -0,0 +1,6 @@ +import { Balance } from "./Balance"; + +export interface SubAccountBalance { + wallet: Balance[]; + spot: Balance[]; +} diff --git a/lib/models/Symbol.ts b/lib/models/Symbol.ts new file mode 100644 index 0000000..7395cb9 --- /dev/null +++ b/lib/models/Symbol.ts @@ -0,0 +1,13 @@ +export interface Symbol { + type:string; + base_currency:string; + quote_currency:string; + status:string; + quantity_increment:string; + tick_size:string; + take_rate:string; + make_rate:string; + fee_currency:string; + margin_trading:boolean; + max_initial_leverage:string; +} \ No newline at end of file diff --git a/lib/models/Ticker.ts b/lib/models/Ticker.ts new file mode 100644 index 0000000..db09891 --- /dev/null +++ b/lib/models/Ticker.ts @@ -0,0 +1,11 @@ +export interface Ticker { + timestamp: string; + ask: string; + bid: string; + open: string; + last: string; + high: string; + low: string; + volume: string; + volume_quote: string; +} diff --git a/lib/models/Trade.ts b/lib/models/Trade.ts new file mode 100644 index 0000000..91c8aa8 --- /dev/null +++ b/lib/models/Trade.ts @@ -0,0 +1,12 @@ +export interface Trade { + id: string; + order_id: string; + client_order_id: string; + symbol: string; + side: string; + quantity: string; + price: string; + fee: string; + timestamp: string; + taker: string; +} diff --git a/lib/models/TradeOfOrder.ts b/lib/models/TradeOfOrder.ts new file mode 100644 index 0000000..d249549 --- /dev/null +++ b/lib/models/TradeOfOrder.ts @@ -0,0 +1,8 @@ +export interface TradeOfOrder { + id: number; + quantity: string; + price: string; + fee: string; + taker: string; + timestamp: string; +} diff --git a/lib/models/Transactions.ts b/lib/models/Transactions.ts new file mode 100644 index 0000000..33e608e --- /dev/null +++ b/lib/models/Transactions.ts @@ -0,0 +1,44 @@ +export interface Transaction { + id: number; + status: string; + type: string; + subtype: string; + created_at: string; + updated_at: string; + native: NativeTransaction; + primetrust: any; + meta: MetaTransaction; +} + +export interface NativeTransaction { + tx_id: string; + index: number; + currency: string; + amount: number; + fee: number; + address: string; + payment_id: string; + hash: string; + offchain_id: string; + confirmations: number; + public_comment: string; + senders: string[]; +} + +export interface MetaTransaction { + fiat_to_crypto: JSON; + id: number; + provider_name: string; + order_type: string; + source_currency: string; + target_currency: string; + wallet_address: string; + tx_hash: string; + target_amount: string; + source_amount: string; + status: string; + created_at: string; + updated_at: string; + deleted_at: string; + payment_method_type: string; +} diff --git a/lib/models/WSCandle.ts b/lib/models/WSCandle.ts new file mode 100644 index 0000000..8766be0 --- /dev/null +++ b/lib/models/WSCandle.ts @@ -0,0 +1,11 @@ +export interface WSCandle { + t: number; + o: string; + c: string; + h: string; + l: string; + v: string; + q: string; +} + +export type MiniTicker = WSCandle; \ No newline at end of file diff --git a/lib/models/WSTicker.ts b/lib/models/WSTicker.ts new file mode 100644 index 0000000..13c262d --- /dev/null +++ b/lib/models/WSTicker.ts @@ -0,0 +1,16 @@ +export interface WSTicker { + t: number; + a: string; + A: string; + b: string; + B: string; + c: string; + o: string; + h: string; + l: string; + v: string; + q: string; + p: string; + P: string; + L: number; +} diff --git a/lib/models/WSTrades.ts b/lib/models/WSTrades.ts new file mode 100644 index 0000000..6a50c62 --- /dev/null +++ b/lib/models/WSTrades.ts @@ -0,0 +1,9 @@ +import { SIDE } from "../constants"; + +export interface WSTrade { + t: number; + i: number; + p: string; + q: string; + s: SIDE; +} \ No newline at end of file diff --git a/lib/websocket/accountClient.js b/lib/websocket/accountClient.js deleted file mode 100644 index 274845a..0000000 --- a/lib/websocket/accountClient.js +++ /dev/null @@ -1,129 +0,0 @@ -const { AuthClient } = require("./authClient") - -/** - * AccountClient connects via websocket to cryptomarket to get account information of the user. uses SHA256 as auth method and authenticates automatically. - */ -class AccountClient extends AuthClient { - constructor(apiKey, apiSecret) { - super( - "wss://api.exchange.cryptomkt.com/api/2/ws/account", - apiKey, - apiSecret, - { - // transaction - "unsubscribeTransactions":"transaction", - "subscribeTransactions":"transaction", - "updateTransaction":"transaction", - // balance - "unsubscribeBalance":"balance", - "subscribeBalance":"balance", - "balance":"balance", - }, - ) - } - - /** - * Get the user account balance. - * - * https://api.exchange.cryptomkt.com/#request-balance - * - * @return {Promise<[object]>} A Promise of the list of balances of the account. Non-zero balances only - */ - getAccountBalance() { - return this.sendById('getBalance') - } - - /** - * Get a list of transactions of the account. Accepts only filtering by Datetime - * - * https://api.exchange.cryptomkt.com/#find-transactions - * - * @param {string} currency Currency code to get the transaction history - * @param {string} sort Optional. a Sort enum type. Sort.ASC (ascending) or Sort.DESC (descending). Default is Sort.DESC. sorting direction - * @param {string} from Optional. Initial value of the queried interval. As timestamp - * @param {number} till Optional. Last value of the queried interval. As timestamp - * @param {number} limit Optional. Trades per query. Defaul is 100. Max is 1000 - * @param {string} offset Optional. Default is 0. Max is 100000 - * @param {boolean} showSenders Optional. If True, show the sender address for payins. - * @return {Promise<[object]>} A Promise of the list with the transactions in the interval. - */ - findTransactions(params) { - return this.sendById('findTransactions', params) - } - - /** - * Get a list of transactions of the account. Accepts only filtering by Index. - * - * https://api.exchange.cryptomkt.com/#load-transactions - * - * @param {string} currency Currency code to get the transaction history - * @param {string} sort Optional. a Sort enum type. Sort.ASC (ascending) or Sort.DESC (descending). Default is Sort.ASC. sorting direction - * @param {string} from Optional. Initial value of the queried interval. As id - * @param {number} till Optional. Last value of the queried interval. As id - * @param {number} limit Optional. Trades per query. Defaul is 100. Max is 1000 - * @param {string} offset Optional. Default is 0. Max is 100000 - * @param {boolean} showSenders Optional. If True, show the sender address for payins. - * @return {Promise<[object]>} A Promise of the list with the transactions in the interval. - */ - loadTransactions(params) { - return this.sendById('loadTransactions', params) - } - - /////////////////// - // subscriptions // - /////////////////// - - /** - * Subscribe to a feed of trading events of the account - * - * https://api.exchange.cryptomkt.com/#subscribe-to-reports - * - * - * @param {function} callback A function to call with the feed data. It takes one argument. - * @return {Promise} A Promise of the subscription result. True if success - */ - subscribeToTransactions(callback) { - this.checkDefined({callback}) - return this.sendSubscription('subscribeTransactions', callback) - } - - /** - * unsubscribe to the transaction feed. - * - * https://api.exchange.cryptomkt.com/#subscription-to-the-transactions - * - * @return {Promise} A Promise of the unsubscription result. True if success - */ - unsubscribeToTransactions() { - return this.sendUnsubscription('unsubscribeTransactions') - } - - /** - * Subscribe to a feed of the balances of the account - * - * https://api.exchange.cryptomkt.com/#subscription-to-the-balance - * - * @param {function} callback A function to call with the feed data. It takes one argument. - * @return {Promise} A Promise of the subscription result. True if success - */ - subscribeToBalance(callback) { - this.checkDefined({callback}) - return this.sendSubscription('subscribeBalance', callback) - } - - /** - * unsubscribe to the balance feed. - * - * https://api.exchange.cryptomkt.com/#subscription-to-the-balance - * - * @return {Promise} A Promise of the unsubscription result. True if success - */ - unsubscribeToBalance() { - return this.sendUnsubscription('unsubscribeBalance') - } - -} - -module.exports = { - AccountClient -} \ No newline at end of file diff --git a/lib/websocket/authClient.js b/lib/websocket/authClient.js deleted file mode 100644 index ab86522..0000000 --- a/lib/websocket/authClient.js +++ /dev/null @@ -1,50 +0,0 @@ -const CryptoJS = require('crypto-js') -const { WSClientBase } = require("./clientBase") - -class AuthClient extends WSClientBase { - constructor(url, apiKey, apiSecret, subscriptionKeys={}) { - super(url, subscriptionKeys=subscriptionKeys) - this.apiKey = apiKey - this.apiSecret = apiSecret - } - - async connect() { - await super.connect() - await this.authenticate() - } - - makeNonce(length) { - let result = [] - let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' - let charactersLength = characters.length - for (let i = 0; i < length; i++ ) { - result.push(characters.charAt(Math.floor(Math.random() * charactersLength))); - } - return result.join('') - } - - /** - * Authenticates the websocket - * - * https://api.exchange.cryptomkt.com/#socket-session-authentication - * - * @param {function} [callback] Optional. A function to call with the result data. It takes two arguments, err and result. err is None for successful calls, result is None for calls with error: callback(err, result) - * - * @return The transaction status as result argument for the callback. - */ - authenticate() { - let nonce = this.makeNonce(30) - let signature = CryptoJS.HmacSHA256(nonce, this.apiSecret).toString() - let params = { - 'algo': 'HS256', - 'pKey': this.apiKey, - 'nonce': nonce, - 'signature': signature - } - return this.sendById('login', params) - } -} - -module.exports = { - AuthClient -} \ No newline at end of file diff --git a/lib/websocket/authClient.ts b/lib/websocket/authClient.ts new file mode 100644 index 0000000..2c1d339 --- /dev/null +++ b/lib/websocket/authClient.ts @@ -0,0 +1,59 @@ +import CryptoJS from "crypto-js"; +import { WSClientBase } from "./clientBase"; + +export class AuthClient extends WSClientBase { + private apiKey: string; + private apiSecret: string; + private window: number | null; + + constructor( + url: string, + apiKey: string, + apiSecret: string, + window: number | null = null, + subscriptionKeys = {} + ) { + super(url, subscriptionKeys); + this.apiKey = apiKey; + this.apiSecret = apiSecret; + this.window = window; + } + + async connect(): Promise { + await super.connect(); + await this.authenticate(); + } + + /** + * Authenticates the websocket + * + * https://api.exchange.cryptomkt.com/#socket-session-authentication + * + * @param {function} [callback] Optional. A function to call with the result data. It takes two arguments, err and result. err is None for successful calls, result is None for calls with error: callback(err, result) + * + * @return The transaction status as result argument for the callback. + */ + authenticate() { + const timestamp = Math.floor(Date.now()); + const params = { + type: "HS256", + api_Key: this.apiKey, + timestamp: timestamp, + }; + let toSign = timestamp.toString(); + if (this.window) { + params["window"] = this.window; + toSign += this.window.toString(); + } + const signature = CryptoJS.HmacSHA256(toSign, this.apiSecret).toString(); + params["signature"] = signature; + return this.sendById({ + method: "login", + params, + }); + } + + async makeRequest(params: { method: string; params?: any }): Promise { + return (await this.sendById(params)) as T; + } +} diff --git a/lib/websocket/clientBase.js b/lib/websocket/clientBase.js deleted file mode 100644 index 1ce8ae2..0000000 --- a/lib/websocket/clientBase.js +++ /dev/null @@ -1,113 +0,0 @@ -const WebSocket = require('ws'); -const { EventEmitter } = require('events'); - -const { ArgumentFormatException, CryptomarketAPIException, CryptomarketSDKException } = require('../exceptions') - -class WSClientBase { - constructor(uri, subscriptionKeys={}) { - this.uri = uri; - this.subscriptionKeys = subscriptionKeys; - this.emitter = new EventEmitter(); - this.nextId = 1 - } - - getNextId() { - if (this.nextId < 1) this.nextId = 1 - let next = this.nextId - this.nextId++ - return next - } - - checkDefined(options) { - for (let key in options) { - if (options[key] === undefined) { - throw new ArgumentFormatException(`undefined argument, "${key}" is required`) - } - } - } - - heartbeat() { - clearTimeout(this.pingTimeout); - this.pingTimeout = setTimeout(() => { - this.ws.terminate(); - }, 30000 + 1000); - } - - async connect() { - this.ws = new WebSocket(this.uri) - this.ws.on('message', (msg) => { - this.handle(msg) - }) - this.ws.on('ping', () => {this.heartbeat()}); - this.ws.on('close', () => {clearTimeout(this.pingTimeout)}) - return new Promise(resolve => { this.ws.once('open', () => { - resolve() - this.heartbeat - })}) - } - - close() { - this.ws.close() - } - - sendSubscription(method, callback, params = {}) { - let key = this.buildKey(method, params) - if (this.emitter.listenerCount(key) == 1){ - throw new CryptomarketSDKException("Already subscripted. Unsubscribe first") - } - this.emitter.on(key, callback) - return this.sendById(method, params) - } - - sendUnsubscription(method, params = {}) { - let key = this.buildKey(method, params) - this.emitter.removeAllListeners(key) - return this.sendById(method, params) - } - - sendById(method, params = {}) { - let id = this.getNextId() - let emitter = this.emitter - let promise = new Promise(function(resolve, reject) { - emitter.once(id, function(response) { - if ('error' in response) { - reject(new CryptomarketAPIException(response)) - } - resolve(response.result) - }) - }) - let payload = {method, params, id} - this.ws.send(JSON.stringify(payload)) - return promise - } - - handle(msg_json) { - let message = JSON.parse(msg_json) - if ('method' in message) { - this.handleNotification(message) - - } else if ('id' in message) { - this.handleResponse(message) - } - } - - handleNotification(notification) { - let key = this.buildKey(notification.method) - this.emitter.emit(key, notification.params) - } - - handleResponse(response) { - let id = response['id'] - if (id === null) return - this.emitter.emit(id, response) - } - - buildKey(method, params) { - if (method in this.subscriptionKeys) this.subscriptionKeys[method]; - return "" - } -} - -module.exports = { - WSClientBase -} \ No newline at end of file diff --git a/lib/websocket/clientBase.ts b/lib/websocket/clientBase.ts new file mode 100644 index 0000000..f5afa7f --- /dev/null +++ b/lib/websocket/clientBase.ts @@ -0,0 +1,144 @@ +import WebSocket from "ws"; +import { EventEmitter } from "events"; +import { + CryptomarketAPIException, + //@ts-ignore +} from "../exceptions"; + +export class WSClientBase { + private uri: string; + private subscriptionKeys: { [x: string]: any }; + private nextId: number; + private pingTimeout: NodeJS.Timeout; + protected ws: WebSocket; + protected emitter: EventEmitter; + + constructor(uri: string, subscriptionKeys = {}) { + this.uri = uri; + this.subscriptionKeys = subscriptionKeys; + this.emitter = new EventEmitter(); + this.nextId = 1; + this.ws = new WebSocket(null); + this.pingTimeout = setTimeout(() => {}); + } + + protected getNextId(): number { + if (this.nextId < 1) this.nextId = 1; + const next = this.nextId; + this.nextId++; + return next; + } + + protected heartbeat() { + clearTimeout(this.pingTimeout); + this.pingTimeout = setTimeout(() => { + this.ws.terminate(); + }, 30_000 + 1_000); + } + + public async connect(): Promise { + return new Promise((resolve) => { + if (this.ws.readyState === this.ws.OPEN) this.ws.close(); + this.ws = new WebSocket(this.uri) + .on("close", () => { + clearTimeout(this.pingTimeout); + }) + .on("ping", () => { + this.heartbeat(); + }) + .on("message", (msg: any) => { + this.handle({ msgJson: msg }); + }) + .once("open", () => { + this.heartbeat; + resolve(); + }); + }); + } + + public close(): Promise { + const promise = new Promise((resolve) => { + this.ws.once("close", () => { + resolve(); + }); + }); + this.ws.close(); + return promise; + } + + protected sendSubscription({ + method, + callback, + params = {}, + }: { + method: string; + callback: (...args: any[]) => void; + params?: {}; + }): Promise { + const { key } = this.subscriptionKeys[method]; + this.emitter.on(key, callback); + return this.sendById({ method, params }); + } + + protected async sendUnsubscription({ + method, + params = {}, + }: { + method: any; + params?: {}; + }): Promise { + const { key } = this.subscriptionKeys[method]; + this.emitter.removeAllListeners(key); + return ((await this.sendById({ method, params })) as { result: Boolean }) + .result; + } + + protected sendById({ + method, + params = {}, + }: { + method: any; + params?: {}; + }): Promise { + const id = this.getNextId(); + const emitter = this.emitter; + const promise = new Promise(function (resolve, reject) { + emitter.once(id.toString(), function (response) { + if ("error" in response) { + reject(new CryptomarketAPIException(response)); + return; + } + resolve(response.result); + }); + }); + const payload = { method, params, id }; + this.ws.send(JSON.stringify(payload)); + return promise; + } + + protected handle({ msgJson }: { msgJson: string }): void { + const message = JSON.parse(msgJson); + if ("method" in message) { + this.handleNotification(message); + } else if ("id" in message) { + this.handleResponse(message); + } + } + + protected handleNotification({ + method, + params, + }: { + method: string; + params: any; + }): void { + const { key, type } = this.subscriptionKeys[method]; + this.emitter.emit(key, params, type); + } + + protected handleResponse(response: { [x: string]: any }): void { + const id = response["id"]; + if (id === null) return; + this.emitter.emit(id, response); + } +} diff --git a/lib/websocket/marketDataClient.ts b/lib/websocket/marketDataClient.ts new file mode 100644 index 0000000..f4d6c2c --- /dev/null +++ b/lib/websocket/marketDataClient.ts @@ -0,0 +1,495 @@ +//@ts-ignore +import { + DEPTH, + NOTIFICATION_TYPE, + ORDER_BOOK_SPEED, + PERIOD, + TICKER_SPEED, +} from "../constants"; +import { CryptomarketAPIException } from "../exceptions"; +import { + Candle, + MiniTicker, + OrderBookTop, + Ticker, + Trade, + WSOrderBook, +} from "../models"; +import { WSClientBase } from "./clientBase"; + +/** + * MarketDataClient connects via websocket to cryptomarket to get market information of the exchange. + */ +export class MarketDataClient extends WSClientBase { + constructor() { + super("wss://api.exchange.cryptomkt.com/api/3/ws/public"); + } + + protected override handle({ msgJson }: { msgJson: string }): void { + const message = JSON.parse(msgJson); + if ("ch" in message) { + this.handleChanneledNotification(message); + } else if ("id" in message) { + this.handleResponse(message); + } + } + + private handleChanneledNotification(notification: { + ch: any; + update: any; + snapshot: any; + data: any; + }): void { + const { ch: channel, update, snapshot, data } = notification; + if (update) { + this.emitter.emit(channel, update, NOTIFICATION_TYPE.UPDATE); + } else if (snapshot) { + this.emitter.emit(channel, snapshot, NOTIFICATION_TYPE.SNAPSHOT); + } else { + this.emitter.emit(channel, data, NOTIFICATION_TYPE.DATA); + } + } + + private sendChanneledSubscription({ + channel, + callback, + params, + }: { + channel: string; + callback: (...args: any[]) => void; + params: any; + }): Promise { + this.emitter.on(channel, callback); + const ID = this.getNextId(); + const emitter = this.emitter; + const promise = new Promise(function (resolve, reject) { + emitter.once(ID.toString(), function (response) { + if ("error" in response) { + reject(new CryptomarketAPIException(response)); + return; + } + resolve(response.result); + }); + }); + let payload = { + id: ID, + method: "subscribe", + ch: channel, + params, + }; + this.ws.send(JSON.stringify(payload)); + return promise; + } + + private async makeSubscription({ + channel, + callback, + params, + withDefaultSymbols = false, + }: { + channel: string; + callback: any; + params: any; + withDefaultSymbols?: boolean; + }): Promise { + if (withDefaultSymbols && !params.symbols) { + params.symbols = ["*"]; + } + const { subscriptions } = (await this.sendChanneledSubscription({ + channel, + callback, + params, + })) as { + subscriptions: string[]; + }; + return subscriptions; + } + + /** + * subscribe to a feed of trades + * + * subscription is for the specified symbols + * + * normal subscriptions have one update message per symbol + * + * the first notification contains the last n trades, with n defined by the + * limit argument, the next notifications are updates and correspond to new + * trades + * + * Requires no API key Access Rights + * + * https://api.exchange.cryptomkt.com/#subscribe-to-trades + * + * @param {function} callback a function that recieves notifications as a dict of trades indexed by symbol, and the type of the notification (either SNAPSHOT or UPDATE) + * @param {string[]} params.symbols A list of symbol ids to recieve notifications + * @param {number} params.limit Number of historical entries returned in the first feed. Min is 0. Max is 1000. Default is 0 + * @returns a promise that resolves when subscribed with a list of the successfully subscribed symbols + */ + async subscribeToTrades({ + callback, + params, + }: { + callback: ( + notification: { [x: string]: Trade[] }, + type: NOTIFICATION_TYPE + ) => any; + params: { symbols: string[]; limit?: number }; + }): Promise { + return await this.makeSubscription({ + channel: "trades", + callback, + params, + }); + } + + /** + * subscribe to a feed of candles + * + * subscription is for all symbols or for the specified symbols + * + * normal subscriptions have one update message per symbol + * + * the first notification are n candles, with n defined by the limit argument, + * the next notification are updates, with one candle at a time + * + * Requires no API key Access Rights + * + * https://api.exchange.cryptomkt.com/#subscribe-to-candles + * + * @param {function} callback a function that recieves notifications as a dict of candles indexed by symbol, and the type of notification (either SNAPSHOT or UPDATE) + * @param {PERIOD} [params.period] Optional. A valid tick interval. 'M1' (one minute), 'M3', 'M5', 'M15', 'M30', 'H1' (one hour), 'H4', 'D1' (one day), 'D7', '1M' (one month). Default is 'M30' + * @param {string[]} [params.symbols] Optional. A list of symbol ids + * @param {number} params.limit Number of historical entries returned in the first feed. Min is 0. Max is 1000. Default is 0 + * @return A promise that resolves when subscribed with a list of the successfully subscribed symbols + */ + async subscribeToCandles({ + callback, + params: { period, ...params }, + }: { + callback: ( + notification: { [x: string]: Candle[] }, + type: NOTIFICATION_TYPE + ) => any; + params: { period: PERIOD; symbols: string[]; limit?: number }; + }): Promise { + return await this.makeSubscription({ + channel: `candles/${period}`, + callback, + params, + }); + } + + /** + * subscribe to a feed of mini tickers + * + * subscription is for all symbols or for the specified symbols + * + * normal subscriptions have one update message per symbol + * + * Requires no API key Access Rights + * + * https://api.exchange.cryptomkt.com/#subscribe-to-mini-ticker + * + * @param {function} callback a function that recieves notifications as a dict of mini tickers indexed by symbol id, and the type of notification (only DATA) + * @param {TICKER_SPEED} params.speed The speed of the feed. '1s' or '3s' + * @param {string[]} [params.symbols] Optional. A list of symbol ids + * @return A promise that resolves when subscribed with a list of the successfully subscribed symbols + */ + async subscribeToMiniTicker({ + callback, + params: { speed, ...params }, + }: { + callback: ( + notification: { [x: string]: MiniTicker[] }, + type: NOTIFICATION_TYPE + ) => any; + params: { speed: TICKER_SPEED; symbols?: string[] }; + }): Promise { + return await this.makeSubscription({ + channel: `ticker/price/${speed}`, + callback, + params, + withDefaultSymbols: true, + }); + } + + /** + * subscribe to a feed of mini tickers + * + * subscription is for all symbols or for the specified symbols + * + * batch subscriptions have a joined update for all symbols + * + * Requires no API key Access Rights + * + * https://api.exchange.cryptomkt.com/#subscribe-to-mini-ticker-in-batches + * + * @param {function} callback a function that recieves notifications as a dict of mini tickers indexed by symbol id, and the type of notification (only DATA) + * @param {TICKER_SPEED} params.speed The speed of the feed. '1s' or '3s' + * @param {string[]} [params.symbols] Optional. A list of symbol ids + * @return A promise that resolves when subscribed with a list of the successfully subscribed symbols + */ + async subscribeToMiniTickerInBatches({ + callback, + params: { speed, ...params }, + }: { + callback: ( + notification: { [x: string]: MiniTicker[] }, + type: NOTIFICATION_TYPE + ) => any; + params: { speed: TICKER_SPEED; symbols?: string[] }; + }): Promise { + return await this.makeSubscription({ + channel: `ticker/price/${speed}/batch`, + callback, + params, + withDefaultSymbols: true, + }); + } + + /** + * subscribe to a feed of tickers + * + * subscription is for all symbols or for the specified symbols + * + * normal subscriptions have one update message per symbol + * + * Requires no API key Access Rights + * + * https://api.exchange.cryptomkt.com/#subscribe-to-ticker + * + * @param {function} callback a function that recieves notifications as a dict of tickers indexed by symbol id, and the type of notification (only DATA) + * @param {TICKER_SPEED} params.speed The speed of the feed. '1s' or '3s' + * @param {string[]} [param.ssymbols] Optional. A list of symbol ids + * @return A promise that resolves when subscribed with a list of the successfully subscribed symbols + */ + async subscribeToTicker({ + callback, + params: { speed, ...params }, + }: { + callback: ( + notification: { [x: string]: Ticker[] }, + type: NOTIFICATION_TYPE + ) => any; + params: { speed: TICKER_SPEED; symbols?: string[] }; + }): Promise { + return await this.makeSubscription({ + channel: `ticker/${speed}`, + callback, + params, + withDefaultSymbols: true, + }); + } + + /** + * subscribe to a feed of tickers + * + * subscription is for all symbols or for the specified symbols + * + * batch subscriptions have a joined update for all symbols + * + * Requires no API key Access Rights + * + * https://api.exchange.cryptomkt.com/#subscribe-to-ticker-in-batches + * + * @param {function} callback a function that recieves notifications as a dict of tickers indexed by symbol id, and the type of notification (only DATA) + * @param {TICKER_SPEED} params.speed The speed of the feed. '1s' or '3s' + * @param {string[]} [params.symbols] Optional. A list of symbol ids + * @return A promise that resolves when subscribed with a list of the successfully subscribed symbols + */ + async subscribeToTickerInBatches({ + callback, + params: { speed, ...params }, + }: { + callback: ( + notification: { [x: string]: Ticker[] }, + type: NOTIFICATION_TYPE + ) => any; + params: { speed: TICKER_SPEED; symbols?: string[] }; + }): Promise { + return await this.makeSubscription({ + channel: `ticker/${speed}/batch`, + callback, + params, + withDefaultSymbols: true, + }); + } + + /** + * subscribe to a feed of a full orderbook + * + * subscription is for all symbols or for the specified symbols + * + * normal subscriptions have one update message per symbol + * + * Requires no API key Access Rights + * + * https://api.exchange.cryptomkt.com/#subscribe-to-full-order-book + * + * @param {function} callback a function that recieves notifications as a dict of full orderbooks indexed by symbol id, and the type of notification (either SNAPSHOT or UPDATE) + * @param {string[]} [params.symbols] Optional. A list of symbol ids + * @return A promise that resolves when subscribed with a list of the successfully subscribed symbols + */ + async subscribeToFullOrderBook({ + callback, + params, + }: { + callback: ( + notification: { [x: string]: WSOrderBook[] }, + type: NOTIFICATION_TYPE + ) => any; + params: { symbols?: string[] }; + }): Promise { + return await this.makeSubscription({ + channel: "orderbook/full", + callback, + params, + }); + } + + /** + * subscribe to a feed of a partial orderbook + * + * subscription is for all symbols or for the specified symbols + * + * normal subscriptions have one update message per symbol + * + * Requires no API key Access Rights + * + * https://api.exchange.cryptomkt.com/#subscribe-to-partial-order-book + * + * @param {function} callback a function that recieves notifications as a dict of partial orderbooks indexed by symbol id, and the type of notification (only DATA) + * @param {ORDER_BOOK_SPEED} params.speed The speed of the feed. '100ms', '500ms' or '1000ms' + * @param {DEPTH} params.depth The depth of the partial orderbook, 'D5', 'D10' or 'D20' + * @param {string[]} [params.symbols] Optional. A list of symbol ids + * @return A promise that resolves when subscribed with a list of the successfully subscribed symbols + */ + async subscribeToPartialOrderBook({ + callback, + params: { speed, depth, ...params }, + }: { + callback: ( + notification: { [x: string]: WSOrderBook[] }, + type: NOTIFICATION_TYPE + ) => any; + params: { + speed: ORDER_BOOK_SPEED; + depth: DEPTH; + symbols?: string[]; + }; + }): Promise { + return await this.makeSubscription({ + channel: `orderbook/${depth}/${speed}`, + callback, + params, + withDefaultSymbols: true, + }); + } + + /** + * subscribe to a feed of a partial orderbook in batches + * + * subscription is for all symbols or for the specified symbols + * + * batch subscriptions have a joined update for all symbols + * + * https://api.exchange.cryptomkt.com/#subscribe-to-partial-order-book-in-batches + * + * @param {function} callback a function that recieves notifications as a dict of partial orderbooks indexed by symbol id, and the type of notification (only DATA) + * @param {ORDER_BOOK_SPEED} params.speed The speed of the feed. '100ms', '500ms' or '1000ms' + * @param {DEPTH} params.depth The depth of the partial orderbook. 'D5', 'D10' or 'D20' + * @param {string[]} [params.symbols] Optional. A list of symbol ids + * @return A promise that resolves when subscribed with a list of the successfully subscribed symbols + */ + async subscribeToPartialOrderBookInBatches({ + callback, + params: { speed, depth, ...params }, + }: { + callback: ( + notification: { [x: string]: WSOrderBook[] }, + type: NOTIFICATION_TYPE + ) => any; + params: { + speed: ORDER_BOOK_SPEED; + depth: DEPTH; + symbols?: string[]; + }; + }): Promise { + return await this.makeSubscription({ + channel: `orderbook/${depth}/${speed}/batch`, + callback, + params, + withDefaultSymbols: true, + }); + } + + /** + * subscribe to a feed of the top of the orderbook + * + * subscription is for all symbols or for the specified symbols + * + * normal subscriptions have one update message per symbol + * + * https://api.exchange.cryptomkt.com/#subscribe-to-top-of-book + * + * @param {function} callback a function that recieves notifications as a dict of top of orderbooks indexed by symbol id, and the type of notification (only DATA) + * @param {ORDER_BOOK_SPEED} params.speed The speed of the feed. '100ms', '500ms' or '1000ms' + * @param {string[]} [params.symbols] Optional. A list of symbol ids + * @return A promise that resolves when subscribed with a list of the successfully subscribed symbols + */ + async subscribeToTopOfOrderBook({ + callback, + params: { speed, ...params }, + }: { + callback: ( + notification: { [x: string]: OrderBookTop[] }, + type: NOTIFICATION_TYPE + ) => any; + params: { + speed: ORDER_BOOK_SPEED; + symbols?: string[]; + }; + }): Promise { + return await this.makeSubscription({ + channel: `orderbook/top/${speed}`, + callback, + params, + withDefaultSymbols: true, + }); + } + + /** + * subscribe to a feed of the top of the orderbook + * + * subscription is for all symbols or for the specified symbols + * + * batch subscriptions have a joined update for all symbols + * + * https://api.exchange.cryptomkt.com/#subscribe-to-top-of-book-in-batches + * + * @param {function} callback a function that recieves notifications as a dict of top of orderbooks indexed by symbol id, and the type of notification (only DATA) + * @param {ORDER_BOOK_SPEED} params.speed The speed of the feed. '100ms', '500ms' or '1000ms' + * @param {string[]} [params.symbols] Optional. A list of symbol ids + * @return A promise that resolves when subscribed with a list of the successfully subscribed symbols + */ + async subscribeToTopOfOrderBookInBatches({ + callback, + params: { speed, ...params }, + }: { + callback: ( + notification: { [x: string]: OrderBookTop[] }, + type: NOTIFICATION_TYPE + ) => any; + params: { + speed: ORDER_BOOK_SPEED; + symbols?: string[]; + }; + }): Promise { + return await this.makeSubscription({ + channel: `orderbook/top/${speed}/batch`, + callback, + params, + withDefaultSymbols: true, + }); + } +} diff --git a/lib/websocket/orderbook_cache.js b/lib/websocket/orderbook_cache.js deleted file mode 100644 index eb46d0c..0000000 --- a/lib/websocket/orderbook_cache.js +++ /dev/null @@ -1,114 +0,0 @@ -var bigdecimal = require("bigdecimal"); - -// orderbook states -const UPDATING = 0 -const WAITING = 1 -const BROKEN = 2 - -// ordering of side of orderbook -const ASCENDING = 3 -const DESCENDING = 4 - -class OrderbookCache { - - constructor() { - this.orderbooks = new Object() - this.orderbookStates = new Object() // one of the three: UPDATING, WAITING, BROKEN - } - - update(method, key, updateData) { - switch (method) { - case 'snapshotOrderbook': - this.orderbookStates[key] = UPDATING - this.orderbooks[key] = updateData - break - case 'updateOrderbook': - if (this.orderbookStates[key] !== UPDATING) return - let oldOrderbook = this.orderbooks[key] - if (updateData['sequence'] - oldOrderbook['sequence'] != 1) { - this.orderbookStates[key] = BROKEN - return - } - oldOrderbook['sequence'] = updateData['sequence'] - oldOrderbook['timestamp'] = updateData['timestamp'] - if ('ask' in updateData) { - oldOrderbook['ask'] = this.updateBookSide(oldOrderbook['ask'], updateData['ask'], ASCENDING) - } - if ('bid' in updateData) { - oldOrderbook['bid'] = this.updateBookSide(oldOrderbook['bid'], updateData['bid'], DESCENDING) - } - break - } - } - - updateBookSide(oldList, updateList, sortDirection) { - let newList = [] - let oldEntry - let oldIdx = 0 - let updateEntry - let updateIdx = 0 - let order - while (oldIdx < oldList.length && updateIdx < updateList.length) { - updateEntry = updateList[updateIdx] - oldEntry = oldList[oldIdx] - order = this.priceOrder(oldEntry, updateEntry, sortDirection) - if (order == 0) { - if (!this.zeroSize(updateEntry)) newList.push(updateEntry) - updateIdx++ - oldIdx++ - } else if (order == 1) { - newList.push(oldEntry) - oldIdx++ - } else { - newList.push(updateEntry) - updateIdx++ - } - } - if (updateIdx == updateList.length) { - for (let idx = oldIdx; idx < oldList.length; idx++) { - oldEntry = oldList[idx] - newList.push(oldEntry) - } - } - if (oldIdx == oldList.length) { - for (let idx = updateIdx; idx < updateList.length; idx++) { - updateEntry = updateList[idx] - if (!this.zeroSize(updateEntry)) newList.push(updateEntry) - } - } - return newList - } - - zeroSize(entry) { - let size = new bigdecimal.BigDecimal(entry['size']) - return size.compareTo(new bigdecimal.BigDecimal('0.00')) == 0 - } - - priceOrder(oldEntry, updateEntry, sortDirection) { - let oldPrice = new bigdecimal.BigDecimal(oldEntry['price']) - let updatePrice = new bigdecimal.BigDecimal(updateEntry['price']) - let direction = oldPrice.compareTo(updatePrice) - if (sortDirection === ASCENDING) return -direction - return direction - } - - getOrderbook(key) { - return Object.assign({}, this.orderbooks[key]) // a copy - } - - orderbookWaiting(key) { - return this.orderbookStates[key] === WAITING - } - - orderbookBroken(key) { - return this.orderbookStates[key] === BROKEN - } - - waitOrderbookSnapshot(key) { - this.orderbookStates[key] = WAITING - } -} - -module.exports = { - OrderbookCache -} \ No newline at end of file diff --git a/lib/websocket/publicClient.js b/lib/websocket/publicClient.js deleted file mode 100644 index fb9367f..0000000 --- a/lib/websocket/publicClient.js +++ /dev/null @@ -1,299 +0,0 @@ -const {WSClientBase} = require('./clientBase') -const { OrderbookCache } = require('./orderbook_cache') - -/** - * PublicClient connects via websocket to cryptomarket to get market information of the exchange. - */ -class PublicClient extends WSClientBase { - constructor() { - super( - "wss://api.exchange.cryptomkt.com/api/2/ws/public", - { - // tickers - "subscribeTicker":"tickers", - "unsubscribeTicker":"tickers", - "ticker":"tickers", - // orderbook - "subscribeOrderbook":"orderbooks", - "unsubscribeOrderbook":"orderbooks", - "snapshotOrderbook":"orderbooks", - "updateOrderbook":"orderbooks", - // trades - "subscribeTrades":"trades", - "unsubscribeTrades":"trades", - "snapshotTrades":"trades", - "updateTrades":"trades", - // candles - "subscribeCandles":"candles", - "unsubscribeCandles":"candles", - "snapshotCandles":"candles", - "updateCandles":"candles", - } - ) - this.obCache = new OrderbookCache() - } - - handleNotification(notification) { - let method = notification['method'] - let params = notification['params'] - let feedData = null - let key = this.buildKey(method, params) - if (this.isOrderbookFeed(method)) { - this.obCache.update(method, key, params) - if (this.obCache.orderbookBroken(key)) { - this.obCache.waitOrderbookSnapshot(key) - this.sendById('subscribeOrderbook', {'symbol':params['symbol']}) - } - if (this.obCache.orderbookWaiting(key)) return - feedData = this.obCache.getOrderbook(key) - } else if (this.isCandlesFeed(method) || this.isTradesFeed(method)) { - feedData = params.data - } else { - feedData = params - } - this.emitter.emit(key, feedData) - } - - isOrderbookFeed(method) { - return this.subscriptionKeys[method] === "orderbooks" - } - - isTradesFeed(method) { - return this.subscriptionKeys[method] === "trades" - } - - isCandlesFeed(method) { - return this.subscriptionKeys[method] === "candles" - } - - buildKey(method, params) { - let methodKey = this.subscriptionKeys[method] - let symbol = ('symbol' in params) ? params['symbol'] : '' - let period = ('period' in params) ? params['period'] : '' - let key = methodKey + ':' + symbol + ':' + period - return key.toUpperCase() - } - - /** - * Get a list all available currencies on the exchange - * - * https://api.exchange.cryptomkt.com/#get-currencies - * - * @return {Promise} A promise of the list of all available currencies. - */ - getCurrencies() { - return this.sendById('getCurrencies') - } - - /** - * Get the data of a currency - * - * https://api.exchange.cryptomkt.com/#get-currencies - * - * @param {string} currency A currency id - * - * @return {Promise} A promise of the currency - */ - getCurrency(currency) { - this.checkDefined({currency}) - return this.sendById('getCurrency', {currency}) - } - - /** - * Get a list of the specified symbols or all of them if no symbols are specified - * - * A symbol is the combination of the base currency (first one) and quote currency (second one) - * - * https://api.exchange.cryptomkt.com/#get-symbols - * - * @return {Promise} A promise of the list of symbols traded on the exchange - */ - getSymbols() { - return this.sendById('getSymbols') - } - - /** - * Get a symbol by its id - * - * A symbol is the combination of the base currency (first one) and quote currency (second one) - * - * https://api.exchange.cryptomkt.com/#get-symbols - * - * - * @param {string} symbol A symbol id - * - * @return {Promise} A promise of the symbol traded on the exchange - */ - getSymbol(symbol) { - this.checkDefined({symbol}) - return this.sendById('getSymbol', {symbol}) - } - - /** - * Get trades of the specified symbol - * - * https://api.exchange.cryptomkt.com/#get-trades - * - * @param {string} symbol The symbol to get the trades - * @param {string} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC' - * @param {string} [params.from] Optional. Initial value of the queried interval. - * @param {string} [params.till] Optional. Last value of the queried interval. - * @param {number} [params.limit] Optional. Trades per query. Defaul is 100. Max is 1000 - * @param {number} [params.offset] Optional. Default is 0. Max is 100000 - - * - * @return Trades information of the symbol as result argument for the callback - */ - getTrades(symbol, params) { - if (!params) params = {} - this.checkDefined({symbol}) - params['symbol'] = symbol - return this.sendById('getTrades', params) - } - - /////////////////// - // subscriptions // - /////////////////// - - /** - * Subscribe to a ticker of a symbol. - * - * the feed is a ticker - * - * https://api.exchange.cryptomkt.com/#subscribe-to-ticker - * - * @param {string} symbol A symbol to subscribe - * @param {function} callback A function to call with the result data. It takes one argument. The ticker feed - * - * @return {Promise} A Promise of the subscription result. True if success - */ - subscribeToTicker(symbol, callback) { - this.checkDefined({symbol, callback}) - return this.sendSubscription('subscribeTicker', callback, {symbol}) - } - - /** - * Unsubscribe to a ticker of a symbol - * - * https://api.exchange.cryptomkt.com/#subscribe-to-ticker - * - * - * @param {string} symbol The symbol to stop the ticker subscribption - * - * @return {Promise} A Promise of the unsubscription result. True if success - */ - unsubscribeToTicker(symbol) { - this.checkDefined({symbol}) - return this.sendUnsubscription('unsubscribeTicker', {symbol}) - } - - /** - * Subscribe to the order book of a symbol - * - * the feed is an orderbook - * - * An Order Book is an electronic list of buy and sell orders for a specific symbol, structured by price level - * - * https://api.exchange.cryptomkt.com/#subscribe-to-order-book - * - * @param {string} symbol The symbol of the orderbook - * @param {function} callback A function to call with the result data. It takes one argument. the order book feed - * - * @return {Promise} A Promise of the subscription result. True if success - */ - subscribeToOrderbook(symbol, callback) { - this.checkDefined({symbol, callback}) - return this.sendSubscription('subscribeOrderbook', callback, {symbol}) - } - - /** - * Unsubscribe to an order book of a symbol - * - * An Order Book is an electronic list of buy and sell orders for a specific symbol, structured by price level - * - * https://api.exchange.cryptomkt.com/#subscribe-to-order-book - * - * @param {string} symbol The symbol of the orderbook - * - * @return {Promise} A Promise of the unsubscription result. True if success - */ - unsubscribeToOrderbook(symbol) { - this.checkDefined({symbol}) - return this.sendUnsubscription('unsubscribeOrderbook', {symbol}) - } - - /** - * Subscribe to the trades of a symbol - * - * the feed is a list of trades - * - * https://api.exchange.cryptomkt.com/#subscribe-to-trades - * - * @param {string} symbol The symbol of the trades - * @param {number} [limit] Optional. Maximum number of trades in the first feed, the nexts feeds have one trade - * @param {function} callback A function to call with the result data. It takes one argument. the trades feed - * - * @return {Promise} A Promise of the subscription result. True if success - */ - subscribeToTrades(symbol, limit, callback) { - this.checkDefined({symbol, callback}) - return this.sendSubscription('subscribeTrades', callback, {symbol, limit}) - } - - - /** - * Unsubscribe to a trades of a symbol - * - * https://api.exchange.cryptomkt.com/#subscribe-to-trades - * - * @param {string} symbol The symbol of the trades - * - * @return {Promise} A Promise of the unsubscription result. True if success - */ - unsubscribeToTrades(symbol) { - this.checkDefined({symbol}) - return this.sendUnsubscription('unsubscribeTrades', {symbol}) - } - - /** - * Subscribe to the candles of a symbol, at the given period - * - * the feed is a list of candles - * - * Candels are used for OHLC representation - * - * https://api.exchange.cryptomkt.com/#subscribe-to-candles - * - * @param {string} symbol A symbol to recieve a candle feed - * @param {string} period A valid tick interval. 'M1' (one minute), 'M3', 'M5', 'M15', 'M30', 'H1' (one hour), 'H4', 'D1' (one day), 'D7', '1M' (one month) - * @param {number} [limit] Optional. Maximum number of candles in the first feed. The rest of the feeds have one candle - * @param {function} callback A function to call with the result data. It takes one argument. recieves the candle feed - * - * @return {Promise} A Promise of the subscription result. True if success - */ - subscribeToCandles(symbol, period, limit, callback) { - this.checkDefined({symbol, period, callback}) - let params = {symbol, period} - if (limit) params["limit"] = limit - return this.sendSubscription('subscribeCandles', callback, params) - } - - /** - * Unsubscribe to the candles of a symbol at a given period - * - * https://api.exchange.cryptomkt.com/#subscribe-to-candles - * - * @param {string} symbol The symbol of the candles - * @param {string} period 'M1' (one minute), 'M3', 'M5', 'M15', 'M30', 'H1' (one hour), 'H4', 'D1' (one day), 'D7', '1M' (one month) - * - * @return {Promise} A Promise of the unsubscription result. True if success - */ - unsubscribeToCandles(symbol, period) { - this.checkDefined({symbol, period}) - return this.sendUnsubscription('unsubscribeCandles', {symbol, period}) - } -} - -module.exports = { - PublicClient -} \ No newline at end of file diff --git a/lib/websocket/tradingClient.js b/lib/websocket/tradingClient.js deleted file mode 100644 index f6b8836..0000000 --- a/lib/websocket/tradingClient.js +++ /dev/null @@ -1,132 +0,0 @@ -const { AuthClient } = require("./authClient") - -/** - * TradingClient connects via websocket to cryptomarket to enable the user to manage orders. uses SHA256 as auth method and authenticates automatically. - */ -class TradingClient extends AuthClient { - constructor(apiKey, apiSecret) { - super( - "wss://api.exchange.cryptomkt.com/api/2/ws/trading", - apiKey, - apiSecret, - { - // reports - "subscribeReports":"reports", - "unsubscribeReports":"reports", - "activeOrders":"reports", - "report":"reports", - } - ) - } - - /** - * Create a new order - * - * https://api.exchange.cryptomkt.com/#place-new-order - * - * @param {string} params.clientOrderId If given must be unique within the trading day, including all active orders. If not given, is generated by the server - * @param {string} params.symbol Trading symbol - * @param {string} params.side 'buy' or 'sell' - * @param {string} params.quantity Order quantity - * @param {string} [params.type] Optional. 'limit', 'market', 'stopLimit' or 'stopMarket'. Default is 'limit' - * @param {string} [params.timeInForce] Optional. 'GTC', 'IOC', 'FOK', 'Day', 'GTD' - * @param {string} [params.price] Required for 'limit' and 'stopLimit'. limit price of the order - * @param {string} [params.stopPrice] Required for 'stopLimit' and 'stopMarket' orders. stop price of the order - * @param {string} [params.expireTime] Required for orders with timeInForce = 'GDT' - * @param {boolean} [params.strictValidate] Optional. If False, the server rounds half down for tickerSize and quantityIncrement. Example of ETHBTC: tickSize = '0.000001', then price '0.046016' is valid, '0.0460165' is invalid - * @param {boolean} [params.postOnly] Optional. If True, your postOnly order causes a match with a pre-existing order as a taker, then the order will be cancelled - * - * @return {Promise} A promise of the report of the newly created order - */ - createOrder(params= {}) { - this.checkDefined({'clientOrderId':params['clientOrderId'], 'symbol':params['symbol'], 'side':params['side'], 'quantity':params['quantity']}) - return this.sendById('newOrder', params) - } - - /** - * Cancel the order with ClientOrderId - * - * https://api.exchange.cryptomkt.com/#cancel-order - * - * @param {string} clientOrderId The client order id of the order to cancel - * - * @return {Promise} A promise of the report of the canceled order - */ - cancelOrder(clientOrderId) { - this.checkDefined({clientOrderId}) - return this.sendById('cancelOrder', {clientOrderId}) - } - - /** - * Rewrites an order, canceling it or replacing it - * - * The Cancel/Replace request is used to change the parameters of an existing order and to change the quantity or price attribute of an open order - * - * Do not use this request to cancel the quantity remaining in an outstanding order. Use the cancel_order for this purpose - * - * It is stipulated that a newly entered order cancels a prior order that has been entered, but not yet executed - * - * https://api.exchange.cryptomkt.com/#cancel-replace-order - * - * @param {string} clientOrderId The client id of the order to modify - * @param {string} requestClientId The new id for the modified order - * @param {string} quantity The new quantity of the order - * @param {string} price The new price of the order - * @param {boolean} [strictValidate] Optional. If False, the server rounds half down for tickerSize and quantityIncrement. Example of ETHBTC: tickSize = '0.000001', then price '0.046016' is valid, '0.0460165' is invalid - * - * @return {Promise} A promise of the report of the modified order - */ - replaceOrder(clientOrderId, requestClientId, quantity, price, strictValidate) { - let params = {clientOrderId, requestClientId, quantity, price} - this.checkDefined(params) - if (strictValidate !== undefined && strictValidate !== null) params['strictValidate'] = strictValidate - return this.sendById('cancelReplaceOrder', params) - } - - /** - * Get the account active orders - * - * https://api.exchange.cryptomkt.com/#get-active-orders-2 - * - * @return {Promise} A promise of the list of reports of the active orders - */ - getActiveOrders() { - return this.sendById('getOrders') - } - - /** - * Get the user trading balance - * - * https://api.exchange.cryptomkt.com/#get-trading-balance - * - * @return {Promise} A promise of the list of the user trading balance - */ - getTradingBalance() { - return this.sendById('getTradingBalance') - } - - /////////////////// - // subscriptions // - /////////////////// - - /** - * Subscribe to a feed of trading events of the account. - * - * the first feed is a list of reporst of the active orders. the rest of the feeds are one report at a time - * - * https://api.exchange.cryptomkt.com/#subscribe-to-reports - * - * @param {function} callback A function to call with the result data. It takes one argument. a feed of reports - * - * @return {Promise} A Promise of the reports of trading events of the account as feed for the callback - */ - subscribeToReports(callback) { - this.checkDefined({callback}) - return this.sendSubscription('subscribeReports', callback) - } - -} - -module.exports = { - TradingClient -} \ No newline at end of file diff --git a/lib/websocket/tradingClient.ts b/lib/websocket/tradingClient.ts new file mode 100644 index 0000000..bc0f66b --- /dev/null +++ b/lib/websocket/tradingClient.ts @@ -0,0 +1,293 @@ +import { AuthClient } from "./authClient"; +import { Balance, Commission, Order, OrderRequest, Report } from "../models"; +import { + CONTINGENCY, + NOTIFICATION_TYPE, + ORDER_TYPE, + TIME_IN_FORCE, +} from "../constants"; + +/** + * TradingClient connects via websocket to cryptomarket to enable the user to manage orders. uses SHA256 as auth method and authenticates on connection. + */ +export class TradingClient extends AuthClient { + /** + * + * @param apiKey public API key + * @param apiSecret secret API key + * @param window Maximum difference between the send of the request and the moment of request processing in milliseconds. + */ + constructor(apiKey: string, apiSecret: string, window: number | null = null) { + super( + "wss://api.exchange.cryptomkt.com/api/3/ws/trading", + apiKey, + apiSecret, + window, + { + // reports + spot_subscribe: { + key: "reports", + type: NOTIFICATION_TYPE.COMMAND, + }, + spot_unsubscribe: { + key: "reports", + type: NOTIFICATION_TYPE.COMMAND, + }, + spot_order: { key: "reports", type: NOTIFICATION_TYPE.UPDATE }, + spot_orders: { key: "reports", type: NOTIFICATION_TYPE.SNAPSHOT }, + } + ); + } + + /** + * Get all active spot orders + * + * Orders without executions are deleted after 24 hours + * + * https://api.exchange.cryptomkt.com/#get-active-spot-orders + * + * @return A promise that resolves with all the spot orders + * + */ + async getActiveSpotOrders(): Promise { + return this.makeRequest({ method: "spot_get_orders" }); + } + + /** + * Creates a new spot order + * + * For fee, for price accuracy and quantity, and for order status information see the api docs at + * https://api.exchange.cryptomkt.com/#create-new-spot-order + * + * https://api.exchange.cryptomkt.com/#place-new-spot-order + * + * @param {string} params.symbol Trading symbol + * @param {string} params.side Either 'buy' or 'sell' + * @param {string} params.quantity Order quantity + * @param {string} [params.client_order_id] Optional. If given must be unique within the trading day, including all active orders. If not given, is generated by the server + * @param {ORDER_TYPE} [params.type] Optional. 'limit', 'market', 'stopLimit', 'stopMarket', 'takeProfitLimit' or 'takeProfitMarket'. Default is 'limit' + * @param {string} [params.price] Optional. Required for 'limit' and 'stopLimit'. limit price of the order + * @param {string} [params.stop_price] Optional. Required for 'stopLimit' and 'stopMarket' orders. stop price of the order + * @param {TIME_IN_FORCE} [params.time_in_force] Optional. 'GTC', 'IOC', 'FOK', 'Day', 'GTD'. Default to 'GTC' + * @param {string} [params.expire_time] Optional. Required for orders with timeInForce = GDT + * @param {boolean} [params.strict_validate] Optional. If False, the server rounds half down for tickerSize and quantityIncrement. Example of ETHBTC: tickSize = '0.000001', then price '0.046016' is valid, '0.0460165' is invalid + * @param {boolean} [params.post_only] Optional. If True, your post_only order causes a match with a pre-existing order as a taker, then the order will be cancelled + * @param {string} [params.take_rate] Optional. Liquidity taker fee, a fraction of order volume, such as 0.001 (for 0.1% fee). Can only increase the fee. Used for fee markup. + * @param {string} [params.make_rate] Optional. Liquidity provider fee, a fraction of order volume, such as 0.001 (for 0.1% fee). Can only increase the fee. Used for fee markup. + * @return A promise that resolves with a report of the new order + */ + async createSpotOrder(params: { + symbol: string; + side: string; + quantity: string; + client_order_id?: string; + type?: ORDER_TYPE; + price?: string; + stop_price?: string; + time_in_Force?: TIME_IN_FORCE; + expire_time?: string; + strict_Validate?: boolean; + postOnly?: boolean; + takeRate?: string; + makeRate?: string; + }): Promise { + return this.makeRequest({ + method: "spot_new_order", + params, + }); + } + + /** + * creates a list of spot orders + * + * Types or contingency: + * + * - CONTINGENCY.ALL_OR_NONE (CONTINGENCY.AON) + * - CONTINGENCY.ONE_CANCEL_OTHER (CONTINGENCY.OCO) + * - CONTINGENCY.ONE_TRIGGER_ONE_CANCEL_OTHER (CONTINGENCY.OTOCO) + * + * Restriction in the number of orders: + * + * - An AON list must have 2 or 3 orders + * - An OCO list must have 2 or 3 orders + * - An OTOCO must have 3 or 4 orders + * + * Symbol restrictions: + * + * - For an AON order list, the symbol code of orders must be unique for each order in the list. + * - For an OCO order list, there are no symbol code restrictions. + * - For an OTOCO order list, the symbol code of orders must be the same for all orders in the list (placing orders in different order books is not supported). + * + * ORDER_TYPE restrictions: + * - For an AON order list, orders must be ORDER_TYPE.LIMIT or ORDER_TYPE.Market + * - For an OCO order list, orders must be ORDER_TYPE.LIMIT, ORDER_TYPE.STOP_LIMIT, ORDER_TYPE.STOP_MARKET, ORDER_TYPE.TAKE_PROFIT_LIMIT or ORDER_TYPE.TAKE_PROFIT_MARKET. + * - An OCO order list cannot include more than one limit order (the same + * applies to secondary orders in an OTOCO order list). + * - For an OTOCO order list, the first order must be ORDER_TYPE.LIMIT, ORDER_TYPE.MARKET, ORDER_TYPE.STOP_LIMIT, ORDER_TYPE.STOP_MARKET, ORDER_TYPE.TAKE_PROFIT_LIMIT or ORDER_TYPE.TAKE_PROFIT_MARKET. + * - For an OTOCO order list, the secondary orders have the same restrictions as an OCO order + * - Default is ORDER_TYPE.Limit + * + * https://api.exchange.cryptomkt.com/#create-new-spot-order-list-2 + * + * @param {string} params.order_list_id Order list identifier. If omitted, it will be generated by the system upon order list creation. Must be equal to client_order_id of the first order in the request. + * @param {string} params.contingency_type Order list type. + * @param {OrderRequest[]} params.orders Orders in the list. + * @return A promise that resolves with a list of reports of the created orders + */ + async createNewSpotOrderList(params: { + oder_list_id: string; + contingency_type: CONTINGENCY; + orders: OrderRequest[]; + }): Promise { + return this.makeRequest({ + method: "spot_new_order_list", + params, + }); + } + + /** + * cancels a spot order + * + * https://api.exchange.cryptomkt.com/#cancel-spot-order-2 + * + * @param {string} client_order_id the client order id of the order to cancel + * @return A promise that resolves with a report of the canceled order + */ + async cancelSpotOrder(client_order_id: string): Promise { + return this.makeRequest({ + method: "spot_cancel_order", + params: { client_order_id }, + }); + } + + /** + * changes the parameters of an existing order, quantity or price + * + * https://api.exchange.cryptomkt.com/#cancel-replace-spot-order + * + * @param {string} params.client_order_id the client order id of the order to change + * @param {string} params.new_client_order_id the new client order id for the modified order. must be unique within the trading day + * @param {string} params.quantity new order quantity + * @param {string} params.price new order price + * @param {boolean} [params.strictValidate] price and quantity will be checked for the incrementation with tick size and quantity step. See symbol's tick_size and quantity_increment + * @return A promise that resolves with a report of the modified order + */ + async replaceSpotOrder(params: { + client_order_id: string; + new_client_order_id: string; + quantity: string; + price: string; + strictValidate?: Boolean; + }): Promise { + return this.makeRequest({ + method: "spot_replace_order", + params, + }); + } + + /** + * cancel all active spot orders and returns the ones that could not be canceled + * + * https://api.exchange.cryptomkt.com/#cancel-spot-orders + * + * @return A promise that resolves with a list of report of the canceled orders + */ + async cancelSpotOrders(): Promise { + return this.makeRequest({ method: "spot_cancel_orders" }); + } + + /** + * Get the user's spot trading balance for all currencies with balance + * + * https://api.exchange.cryptomkt.com/#get-spot-trading-balances + * + * @return A promise that resolves with a list of spot trading balances + */ + async getSpotTradingBalances(): Promise { + return this.makeRequest({ method: "spot_balances" }); + } + + /** + * Get the user spot trading balance of a currency + * + * https://api.exchange.cryptomkt.com/#get-spot-trading-balance-2 + * + * @param {string} params.currency The currency code to query the balance + * @return A promise that resolves with the spot trading balance of a currency + */ + async getSpotTradingBalanceOfCurrency(params: { + currency: string; + }): Promise { + return this.makeRequest({ method: "spot_balance", params }); + } + + /** + * Get the personal trading commission rates for all symbols + * + * https://api.exchange.cryptomkt.com/#get-spot-fees + * + * @return A promise that resolves with a list of commission rates + */ + async getSpotCommissions(): Promise { + return this.makeRequest({ method: "spot_fees" }); + } + + /** + * Get the personal trading commission rate of a symbol + * + * https://api.exchange.cryptomkt.com/#get-spot-fee + * + * @param {string} params.symbol The symbol of the commission rate + * @return A promise that resolves with the commission rate of a symbol + */ + async getSpotCommissionOfSymbol(params: { + symbol: string; + }): Promise { + return this.makeRequest({ method: "spot_fee", params }); + } + + /////////////////// + // subscriptions // + /////////////////// + + /** + * subscribe to a feed of execution reports of the user's orders + * + * the first notification is a snapshot of the current orders, further + * notifications are updates of the user orders + * + * https://api.exchange.cryptomkt.com/#subscribe-to-reports + * + * @param {function} callback a function that recieves a list of reports, and the type of notification (either SNAPSHOT or UPDATE) + * @return {Promise} A Promise of the subscription result. True if subscribed + */ + async subscribeToReports( + callback: (notification: Report[], type: NOTIFICATION_TYPE) => any + ): Promise { + return ( + (await this.sendSubscription({ + method: "spot_subscribe", + callback: (notification: any, type: NOTIFICATION_TYPE) => { + if (type === NOTIFICATION_TYPE.SNAPSHOT) { + callback(notification as Report[], type); + } else { + callback([notification as Report], type); + } + }, + })) as { + result: boolean; + } + ).result; + } + + /** + * stop recieveing the report feed subscription + * + * https://api.exchange.cryptomkt.com/#subscribe-to-reports + * + * @return {Promise} A Promise of the unsubscription result. True if unsubscribed + */ + async unsubscribeToReports(): Promise { + return this.sendUnsubscription({ method: "spot_unsubscribe" }); + } +} diff --git a/lib/websocket/walletClient.ts b/lib/websocket/walletClient.ts new file mode 100644 index 0000000..06806a3 --- /dev/null +++ b/lib/websocket/walletClient.ts @@ -0,0 +1,221 @@ +import { AuthClient } from "./authClient"; +import { Balance, Transaction } from "../models"; +import { + NOTIFICATION_TYPE, + SORT, + SORT_BY, + TRANSACTION_STATUS, + TRANSACTION_SUBTYPE, + TRANSACTION_TYPE, +} from "../constants"; + +/** + * WalletClient connects via websocket to cryptomarket to get wallet information of the user. uses SHA256 as auth method and authenticates on connection. + */ +export class WalletClient extends AuthClient { + constructor(apiKey: string, apiSecret: string, window: number | null = null) { + const transactionKey = "transaction"; + const balanceKey = "balance"; + super( + "wss://api.exchange.cryptomkt.com/api/3/ws/wallet", + apiKey, + apiSecret, + window, + { + // transaction + subscribe_transactions: { + key: transactionKey, + type: NOTIFICATION_TYPE.COMMAND, + }, + unsubscribe_transactions: { + key: transactionKey, + type: NOTIFICATION_TYPE.COMMAND, + }, + transaction_update: { + method: transactionKey, + type: NOTIFICATION_TYPE.UPDATE, + }, + // balance + subscribe_wallet_balances: { + key: balanceKey, + type: NOTIFICATION_TYPE.COMMAND, + }, + unsubscribe_wallet_balances: { + key: balanceKey, + type: NOTIFICATION_TYPE.COMMAND, + }, + wallet_balances: { + key: balanceKey, + type: NOTIFICATION_TYPE.SNAPSHOT, + }, + wallet_balance_update: { + key: balanceKey, + type: NOTIFICATION_TYPE.UPDATE, + }, + } + ); + } + + /** + * Get the user's wallet balance for all currencies with balance + * + * Requires the "Payment information" API key Access Right + * + * https://api.exchange.cryptomkt.com/#wallet-balance + * + * @return A promise that resolves with a list of wallet balances + */ + getWalletBalances(): Promise { + return this.makeRequest({ method: "wallet_balances" }); + } + + /** + * Get the user's wallet balance of a currency + * + * Requires the "Payment information" API key Access Right + * + * https://api.exchange.cryptomkt.com/#wallet-balance + * + * @param {string} currency The currency code to query the balance + * @return A promise that resolves with the wallet balance of the currency + */ + getWalletBalanceOfCurrency(params: { currency: string }): Promise { + return this.makeRequest({ + method: "wallet_balance", + params, + }); + } + + /** + * Get the transaction history of the account + * + * Important: + * + * - The list of supported transaction types may be expanded in future versions + * + * - Some transaction subtypes are reserved for future use and do not purport to provide any functionality on the platform + * + * - The list of supported transaction subtypes may be expanded in future versions + * + * Requires the "Payment information" API key Access Right + * + * https://api.exchange.cryptomkt.com/#get-transactions-history + * + * @param {string[]} [params.tx_ids] Optional. List of transaction identifiers to query + * @param {TRANSACTION_TYPE[]} [params.transaction_types] Optional. List of types to query. valid types are: 'DEPOSIT', 'WITHDRAW', 'TRANSFER' and 'SWAP' + * @param {TRANSACTION_SUBTYPE[]} [params.transaction_subtyes] Optional. List of subtypes to query. valid subtypes are: 'UNCLASSIFIED', 'BLOCKCHAIN', 'AIRDROP', 'AFFILIATE', 'STAKING', 'BUY_CRYPTO', 'OFFCHAIN', 'FIAT', 'SUB_ACCOUNT', 'WALLET_TO_SPOT', 'SPOT_TO_WALLET', 'WALLET_TO_DERIVATIVES', 'DERIVATIVES_TO_WALLET', 'CHAIN_SWITCH_FROM', 'CHAIN_SWITCH_TO' and 'INSTANT_EXCHANGE' + * @param {TRANSACTION_STATUS[]} [params.transaction_statuses] Optional. List of statuses to query. valid subtypes are: 'CREATED', 'PENDING', 'FAILED', 'SUCCESS' and 'ROLLED_BACK' + * @param {string} [params.from] Optional. Interval initial value when ordering by 'created_at'. As Datetime + * @param {string} [params.till] Optional. Interval end value when ordering by 'created_at'. As Datetime + * @param {string} [params.id_from] Optional. Interval initial value when ordering by id. Min is 0 + * @param {string} [params.id_till] Optional. Interval end value when ordering by id. Min is 0 + * @param {SORT_BY} [params.order_by] Optional. sorting parameter.'created_at' or 'id'. Default is 'created_at' + * @param {SORT} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC' + * @param {number} [params.limit] Optional. Transactions per query. Defaul is 100. Max is 1000 + * @param {number} [params.offset] Optional. Default is 0. Max is 100000 + * @return A promise that resolves with a list of transactions + */ + getTransactions(params: { + tx_ids?: number[]; + types?: TRANSACTION_TYPE[]; + subtypes?: TRANSACTION_SUBTYPE[]; + statuses?: TRANSACTION_STATUS[]; + currencies?: string[]; + from?: string; + till?: string; + id_from?: number; + id_till?: number; + order_by?: SORT_BY; + sort?: SORT; + limit?: number; + offset?: number; + }): Promise { + return this.makeRequest({ + method: "get_transactions", + params, + }); + } + + /////////////////// + // subscriptions // + /////////////////// + + /** + * A transaction notification occurs each time a transaction has been changed, such as creating a transaction, updating the pending state (e.g., the hash assigned) or completing a transaction + * + * https://api.exchange.cryptomkt.com/#subscribe-to-transactions + * + * @param {function} callback a function that recieves notifications with a list of transactions, and the type of notification (only UPDATE) + * @return {Promise} A Promise of the subscription result. True if subscribed + */ + async subscribeToTransactions( + callback: (notification: Transaction, type: NOTIFICATION_TYPE) => any + ): Promise { + return ( + (await this.sendSubscription({ + method: "subscribe_transactions", + callback: (notification: any, type: NOTIFICATION_TYPE) => { + callback(notification as Transaction, type); + }, + })) as { + result: boolean; + } + ).result; + } + + /** + * unsubscribe to the transaction feed. + * + * https://api.exchange.cryptomkt.com/#subscription-to-the-transactions + * + * @return {Promise} A Promise of the unsubscription result. True if unsubscribed + */ + unsubscribeToTransactions(): Promise { + return this.sendUnsubscription({ + method: "unsubscribe_transactions", + }); + } + + /** + * Subscribe to a feed of the balances of the account + * + * the first notification has a snapshot of the wallet. further notifications + * are updates of the wallet + * + * https://api.exchange.cryptomkt.com/#subscription-to-the-balance + * + * @param {function} callback A function that recieves notifications with a list of balances, and the type of notification (either SNAPSHOT or UPDATE) + * @return {Promise} A Promise of the subscription result. True if subscribed + */ + async subscribeToBalance( + callback: (notification: Balance[], type: NOTIFICATION_TYPE) => any + ): Promise { + return ( + (await this.sendSubscription({ + method: "subscribe_wallet_balances", + callback: (notification: any, type) => { + if (type === NOTIFICATION_TYPE.SNAPSHOT) { + callback(notification as Balance[], type); + } else { + callback([notification as Balance], type); + } + }, + })) as { + result: boolean; + } + ).result; + } + + /** + * unsubscribe to the balance feed. + * + * https://api.exchange.cryptomkt.com/#subscription-to-the-balance + * + * @return {Promise} A Promise of the unsubscription result. True if unsubscribed + */ + unsubscribeToBalance(): Promise { + return this.sendUnsubscription({ + method: "ununsubscribe_wallet_balances", + }); + } +} diff --git a/package-lock.json b/package-lock.json index 40ce553..e8ac349 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,1051 +1,125 @@ { "name": "cryptomarket", - "version": "1.0.5", - "lockfileVersion": 2, + "version": "1.0.6", + "lockfileVersion": 1, "requires": true, - "packages": { - "": { - "name": "cryptomarket", - "version": "1.0.5", - "license": "Apache-2.0", - "dependencies": { - "bigdecimal": "^0.6.1", - "crypto-js": "^4.0.0", - "node-fetch": "^2.6.1", - "rxjs": "^6.6.3", - "ws": "^7.4.0" - }, - "devDependencies": { - "mocha": "^8.4.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/bigdecimal": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/bigdecimal/-/bigdecimal-0.6.1.tgz", - "integrity": "sha1-GFiNS08ia3cxDtBFdIWMA2pUSFs=", - "engines": [ - "node" - ], - "bin": { - "bigdecimal.js": "repl.js" - } - }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.1" - } - }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - } - }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "node_modules/crypto-js": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz", - "integrity": "sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg==" - }, - "node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true, - "engines": { - "node": ">=4.x" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true, - "bin": { - "he": "bin/he" - } - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/js-yaml": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", - "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mocha": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", - "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", - "dev": true, - "dependencies": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.1", - "debug": "4.3.1", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.0.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", - "ms": "2.1.3", - "nanoid": "3.1.20", - "serialize-javascript": "5.0.1", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.1.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha" - }, - "engines": { - "node": ">= 10.12.0" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/nanoid": { - "version": "3.1.20", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", - "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", - "dev": true, - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", - "engines": { - "node": "4.x || >=6.0.0" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", - "dev": true, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/rxjs": { - "version": "6.6.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", - "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "npm": ">=2.0.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, - "node_modules/serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "dependencies": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "dependencies": { - "ansi-regex": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dependencies": { + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", "dev": true, - "dependencies": { - "string-width": "^1.0.2 || 2" + "requires": { + "@jridgewell/trace-mapping": "0.3.9" } }, - "node_modules/workerpool": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", - "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", + "@jridgewell/resolve-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz", + "integrity": "sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w==", "dev": true }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", "dev": true }, - "node_modules/ws": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.0.tgz", - "integrity": "sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ==", - "engines": { - "node": ">=8.3.0" - } + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } + "@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true }, - "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "engines": { - "node": ">=10" - } + "@types/chai": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.1.tgz", + "integrity": "sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==", + "dev": true }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } + "@types/crypto-js": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz", + "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==", + "dev": true }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true, - "engines": { - "node": ">=8" - } + "@types/mocha": { + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", + "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", + "dev": true }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } + "@types/node": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.32.tgz", + "integrity": "sha512-eAIcfAvhf/BkHcf4pkLJ7ECpBAhh9kcxRBpip9cTiO+hf+aJrsxYxBeS6OXvOd9WqNAJmavXVpZvY1rBjNsXmw==", + "dev": true }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "@types/node-fetch": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", + "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==", "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" } }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "@types/ws": { + "version": "8.5.3", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", + "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" + "requires": { + "@types/node": "*" } }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - } - } - }, - "dependencies": { "@ungap/promise-all-settled": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", "dev": true }, + "acorn": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "dev": true + }, + "acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true + }, "ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", @@ -1053,9 +127,9 @@ "dev": true }, "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", "dev": true }, "ansi-styles": { @@ -1077,12 +151,30 @@ "picomatch": "^2.0.4" } }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -1126,15 +218,30 @@ "dev": true }, "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", "dev": true }, + "chai": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", + "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, "chalk": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", - "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -1152,6 +259,12 @@ } } }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "dev": true + }, "chokidar": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", @@ -1180,9 +293,9 @@ }, "dependencies": { "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "is-fullwidth-code-point": { @@ -1192,23 +305,23 @@ "dev": true }, "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "strip-ansi": "^6.0.1" } }, "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" } } } @@ -1228,12 +341,27 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "crypto-js": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz", @@ -1262,6 +390,21 @@ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", "dev": true }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true + }, "diff": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", @@ -1311,6 +454,17 @@ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", "dev": true }, + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1330,6 +484,12 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", "dev": true }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "dev": true + }, "glob": { "version": "7.1.6", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", @@ -1409,9 +569,9 @@ "dev": true }, "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "requires": { "is-extglob": "^2.1.1" @@ -1462,6 +622,36 @@ "chalk": "^4.0.0" } }, + "loupe": { + "version": "2.3.4", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", + "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", + "dev": true, + "requires": { + "get-func-name": "^2.0.0" + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "requires": { + "mime-db": "1.52.0" + } + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -1566,10 +756,16 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true + }, "picomatch": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, "randombytes": { @@ -1662,11 +858,58 @@ "is-number": "^7.0.0" } }, + "ts-node": { + "version": "10.8.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.1.tgz", + "integrity": "sha512-Wwsnao4DQoJsN034wePSg5nZiw4YKXf56mPIAeD6wVmiv+RytNSWqc2f3fKvcUoV+Yn2+yocD71VOfQHbmVX4g==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + } + } + }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "typescript": { + "version": "4.7.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", + "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", + "dev": true + }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1703,9 +946,9 @@ }, "dependencies": { "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "is-fullwidth-code-point": { @@ -1715,23 +958,23 @@ "dev": true }, "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "strip-ansi": "^6.0.1" } }, "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" } } } @@ -1769,9 +1012,9 @@ }, "dependencies": { "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "is-fullwidth-code-point": { @@ -1781,23 +1024,23 @@ "dev": true }, "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "strip-ansi": "^6.0.1" } }, "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "requires": { - "ansi-regex": "^5.0.0" + "ansi-regex": "^5.0.1" } } } @@ -1820,6 +1063,12 @@ "is-plain-obj": "^2.1.0" } }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 2075d2a..125d36f 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "main": "lib/index.js", "scripts": { - "test": "mocha" + "test": "env TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha -r ts-node/register 'test/**/*.test.ts'" }, "author": "Ismael Verdugo Zambra", "directories": { @@ -43,6 +43,14 @@ "ws": "^7.4.0" }, "devDependencies": { - "mocha": "^8.4.0" + "@types/chai": "^4.3.1", + "@types/crypto-js": "^4.1.1", + "@types/mocha": "^9.1.1", + "@types/node-fetch": "^2.6.2", + "@types/ws": "^8.5.3", + "chai": "^4.3.6", + "mocha": "^8.4.0", + "ts-node": "^10.8.1", + "typescript": "^4.7.4" } } diff --git a/test/rest/market_data.test.ts b/test/rest/market_data.test.ts new file mode 100644 index 0000000..82af0c6 --- /dev/null +++ b/test/rest/market_data.test.ts @@ -0,0 +1,254 @@ +import assert from "assert"; +import { expect } from "chai"; +import "mocha"; +import { Client } from "../../lib"; +import { PERIOD } from "../../lib/constants"; +import { + dictSize, + emptyDict, + emptyList, + goodCandle, + goodCurrency, + goodDict, + goodList, + goodOrderbook, + goodPrice, + goodPriceHistory, + goodPublicTrade, + goodSymbol, + goodTicker, + goodTickerPrice, + listSize, +} from "../test_helpers"; + +describe("Rest client test", () => { + let client = new Client("", ""); + function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + const second = 1000; + beforeEach(async function () { + await sleep(second / 20); // make around 20 calls per second at most, to not pass the rate limiting of 30 calls per second, by a big margin. + }); + describe("Get Currencies", () => { + it("All currencies", async function () { + this.timeout(0); + let currencies = await client.getCurrencies(); + assert(!emptyDict(currencies), "empty currency dict"); + assert(goodDict(goodCurrency, currencies), "not good currencies"); + }); + it("two currencies", async function () { + this.timeout(0); + let currencies = await client.getCurrencies(["eos", "BTC"]); + assert(dictSize(currencies, 2), "wrong number of currencies"); + assert(goodDict(goodCurrency, currencies), "not good currencies"); + }); + }); + describe("Get Symbol", () => { + it("", async function () { + this.timeout(0); + let symbol = await client.getSymbol("ETHBTC"); + assert(goodSymbol(symbol), "not a good symbol"); + }); + }); + describe("Get Symbols", () => { + it("All symbols", async function () { + this.timeout(0); + let symbols = await client.getSymbols(); + assert(!emptyDict(symbols), "empty dict of symbols"); + assert(goodDict(goodSymbol, symbols), "not good symbols"); + }); + it("two symbols", async function () { + this.timeout(0); + let symbols = await client.getSymbols(["EOSETH", "PAXGBTC"]); + assert(dictSize(symbols, 2), "wrong number of symbols"); + assert(goodDict(goodSymbol, symbols), "not good symbols"); + }); + }); + describe("Get Tickers", () => { + it("All tickers", async function () { + this.timeout(0); + let tickers = await client.getTickers(); + assert(!emptyDict(tickers), "empty dict of tickers"); + assert(goodDict(goodTicker, tickers), "not good tickers"); + }); + it("A dict with two tickers", async function () { + this.timeout(0); + let tickers = await client.getTickers(["EOSETH", "PAXGBTC"]); + assert(dictSize(tickers, 2), "wrong number of tickers"); + assert(goodDict(goodTicker, tickers), "not good tickers"); + }); + }); + describe("Get Ticker", () => { + it("", async function () { + this.timeout(0); + let ticker = await client.getTicker("EOSETH"); + assert(goodTicker(ticker), "not good ticker"); + }); + }); + describe("Get Prices", () => { + it("for all currencies as origin", async function () { + this.timeout(0); + let prices = await client.getPrices({ + to: "XLM", + }); + assert(!dictSize(prices, 1), "wrong number of prices"); + assert(goodDict(goodPrice, prices), "not good prices"); + }); + it("for one currency pair", async function () { + this.timeout(0); + let prices = await client.getPrices({ + to: "XLM", + from: "CRO", + }); + assert(dictSize(prices, 1), "wrong number of prices"); + assert(goodDict(goodPrice, prices), "not good prices"); + }); + }); + describe("Get Prices History", () => { + it("for all currencies as origin", async function () { + this.timeout(0); + let pricesHistory = await client.getPricesHistory({ + to: "ETH", + period: PERIOD._15_MINUTES, + }); + assert(!dictSize(pricesHistory, 1), "wrong number of prices histories"); + assert( + goodDict(goodPriceHistory, pricesHistory), + "not good price history" + ); + }); + it("for one currency pair", async function () { + this.timeout(0); + let pricesHistory = await client.getPricesHistory({ + to: "ETH", + from: "BTC", + period: PERIOD._15_MINUTES, + }); + assert(dictSize(pricesHistory, 1), "wrong number of prices histories"); + assert( + goodDict(goodPriceHistory, pricesHistory), + "not good price history" + ); + }); + }); + describe("Get Ticker Prices", () => { + it("for all symbols", async function () { + this.timeout(0); + let prices = await client.getTickerLastPrices(); + assert(!emptyDict(prices), "wrong number of ticker prices"); + assert(goodDict(goodTickerPrice, prices), "not good ticker prices"); + }); + it("for two symbols", async function () { + this.timeout(0); + let prices = await client.getTickerLastPrices(["EOSETH", "XLMETH"]); + assert(dictSize(prices, 2), "wrong number of ticker prices"); + assert(goodDict(goodTickerPrice, prices), "not good ticker prices"); + }); + }); + describe("Get Ticker Price Of Symbol", () => { + it("", async function () { + this.timeout(0); + let price = await client.getTickerLastPriceOfSymbol("EOSETH"); + assert(goodTickerPrice(price), "not a good ticker price"); + }); + }); + describe("Get Trades", () => { + it("for all symbols", async function () { + this.timeout(0); + let trades = await client.getTrades(); + assert(!emptyDict(trades), "empty dict of trades"); + assert( + goodDict((trade) => goodList(goodPublicTrade, trade), trades), + "not good trades" + ); + }); + it("for two symbols", async function () { + this.timeout(0); + let trades = await client.getTrades({ + symbols: ["EOSETH", "PAXGBTC"], + limit: 2, + }); + assert(dictSize(trades, 2), "wrong number of trades"); + assert( + goodDict((trade) => goodList(goodPublicTrade, trade), trades), + "not good trades" + ); + }); + }); + describe("Get Trades of symbol", () => { + it("", async function () { + this.timeout(0); + let trades = await client.getTradesOfSymbol("EOSETH"); + assert(!emptyList(trades), "empty dict of trades"); + assert(goodList(goodPublicTrade, trades), "not good trades"); + }); + }); + describe("Get order books", () => { + it("for all symbols", async function () { + this.timeout(0); + let orderBooks = await client.getOrderBooks(); + assert(!emptyDict(orderBooks), "empty dict of order books"); + assert(goodDict(goodOrderbook, orderBooks), "not good orderbooks"); + }); + it("for 2 symbols", async function () { + this.timeout(0); + let orderBooks = await client.getOrderBooks({ + symbols: ["EOSETH", "PAXGBTC"], + }); + expect(Object.keys(orderBooks).length).to.equal(2); + }); + }); + describe("Get Order book of symbol", () => { + it("", async function () { + this.timeout(0); + let orderBook = await client.getOrderBookOfSymbol("EOSETH"); + assert(goodOrderbook(orderBook), "not good orderbook"); + }); + }); + describe("Get Order book of symbol, with volume", () => { + it("", async function () { + this.timeout(0); + let orderBook = await client.getOrderBookVolume({ + symbol: "EOSETH", + volume: 1000, + }); + assert(goodOrderbook(orderBook), "not good orderbook"); + }); + }); + describe("Get candles", () => { + it("for all symbols", async function () { + this.timeout(0); + let candles = await client.getCandles(); + assert(!emptyDict(candles), "empty list of candles"); + assert( + goodDict((candleList) => goodList(goodCandle, candleList), candles), + "not good candles" + ); + }); + it("for 2 symbols", async function () { + this.timeout(0); + let candles = await client.getCandles({ + symbols: ["EOSETH", "PAXGBTC"], + period: PERIOD._1_HOUR, + limit: 2, + }); + assert(dictSize(candles, 2), "wrong number of symbols"); + assert( + goodDict((candleList) => goodList(goodCandle, candleList), candles), + "not good candles" + ); + }); + }); + describe("Get candles Of Symbol", () => { + it("with period and limit", async function () { + this.timeout(0); + let candles = await client.getCandlesOfSymbol("ADAETH", { + period: PERIOD._30_MINUTES, + limit: 2, + }); + assert(listSize(candles, 2), "wrong number of candles"); + assert(goodList(goodCandle, candles), "not good candles"); + }); + }); +}); diff --git a/test/rest/spot_trading.test.ts b/test/rest/spot_trading.test.ts new file mode 100644 index 0000000..6f141aa --- /dev/null +++ b/test/rest/spot_trading.test.ts @@ -0,0 +1,151 @@ +import assert from "assert"; +import "mocha"; +import { Client } from "../../lib/client"; +import { SIDE } from "../../lib/constants"; +import { + emptyList, + goodBalance, + goodList, + goodOrder, + goodTradingCommission, + listSize, +} from "../test_helpers"; +const keys = require("/home/ismael/cryptomarket/keys-v3.json"); + +describe("spot trading", () => { + let client = new Client(keys.apiKey, keys.apiSecret); + function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + const second = 1000; + beforeEach(async function () { + await sleep(second / 20); // make around 20 calls per second at most, to not pass the rate limiting of 30 calls per second, by a big margin. + }); + describe("get spot trading balance", () => { + it("spot balance", async function () { + this.timeout(0); + let balances = await client.getSpotTradingBalances(); + assert(goodList(goodBalance, balances), "not good balances"); + }); + }); + describe("get spot trading balance of currency", () => { + it("", async function () { + this.timeout(0); + let balance = await client.getSpotTradingBalanceOfCurrency("ADA"); + assert(goodBalance(balance), "not good balance"); + }); + }); + describe("get all active spot orders", () => { + it("", async function () { + this.timeout(0); + let orders = await client.getAllActiveSpotOrders(); + assert(goodList(goodOrder, orders), "not good orders"); + }); + it("filtering by symbol", async function () { + let orders = await client.getAllActiveSpotOrders("CROETH"); + if (!goodList(goodOrder, orders)) assert(false, "not good orders"); + }); + }); + describe("spot order life cycle: createNewSpotOrder, getActiveOrder, replaceSpotOrder and cancelOrder", () => { + it("with client order id", async function () { + this.timeout(0); + // creation + let timestamp = Date.now().toString(); + let order = await client.createNewSpotOrder({ + symbol: "EOSETH", + side: SIDE.SELL, + quantity: "0.01", + price: "1000", + client_order_id: timestamp, + }); + assert(goodOrder(order), "not good order after creation"); + + // querying + order = await client.getActiveSpotOrder(timestamp); + assert(goodOrder(order), "not good order after query"); + + // replacing + let newOrderID = Date.now().toString() + "1"; + order = await client.replaceSpotOrder(order.client_order_id, { + new_client_order_id: newOrderID, + quantity: "0.02", + price: "1000", + }); + // cancelation + order = await client.cancelSpotOrder(newOrderID); + assert(goodOrder(order), "not good order after cancelation"); + assert(order.status == "canceled"); + }); + it("with no client order id", async function () { + this.timeout(0); + // creation + let order = await client.createNewSpotOrder({ + symbol: "EOSETH", + side: SIDE.SELL, + quantity: "0.01", + price: "1001", + }); + assert(goodOrder(order), "not good order after creation"); + + // cancelation + order = await client.cancelSpotOrder(order.client_order_id); + assert(goodOrder(order), "not good order after cancellation"); + }); + }); + describe("cancel all orders", () => { + it("", async function () { + this.timeout(0); + + // cleaning the order list + await client.cancelAllSpotOrders(); + + // filling the list + await client.createNewSpotOrder({ + symbol: "EOSETH", + side: SIDE.SELL, + quantity: "0.01", + price: "1001", + }); + await client.createNewSpotOrder({ + symbol: "EOSBTC", + side: SIDE.SELL, + quantity: "0.01", + price: "1001", + }); + + // checking list size + let orders = await client.getAllActiveSpotOrders(); + assert(listSize(orders, 2), "wrong number of active orders"); + assert(goodList(goodOrder, orders), "not good active order"); + + // cancelling + let canceledOrders = await client.cancelAllSpotOrders(); + assert(listSize(canceledOrders, 2), "wrong number of canceled orders"); + assert(goodList(goodOrder, canceledOrders), "not good canceled"); + + // checking empty list size of active orders + orders = await client.getAllActiveSpotOrders(); + assert(emptyList(orders), "wrong number of active orders"); + }); + }); + describe("get all trading commissions", () => { + it("", async function () { + this.timeout(0); + let tradingCommissions = await client.getAllTradingCommissions(); + assert( + goodList(goodTradingCommission, tradingCommissions), + "not good trading commission" + ); + }); + }); + describe("get trading commission", () => { + it("should succeed", async function () { + this.timeout(0); + let tradingCommission = await client.getTradingCommission("EOSETH"); + assert( + goodTradingCommission(tradingCommission), + "not good trading commision" + ); + }); + }); +}); diff --git a/test/rest/spot_trading_history.test.ts b/test/rest/spot_trading_history.test.ts new file mode 100644 index 0000000..f4d0119 --- /dev/null +++ b/test/rest/spot_trading_history.test.ts @@ -0,0 +1,31 @@ +import assert from "assert"; +import { Client } from "../../lib"; +import { goodList, goodOrder, goodTrade } from "../test_helpers"; +const keys = require("/home/ismael/cryptomarket/keys-v3.json"); + +import "mocha"; + +describe("spot trading history", () => { + function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + const second = 1000; + beforeEach(async function () { + await sleep(second / 20); // make around 20 calls per second at most, to not pass the rate limiting of 30 calls per second, by a big margin. + }); + let client = new Client(keys.apiKey, keys.apiSecret); + describe("Get spot order history", () => { + it("", async function () { + this.timeout(0); + let orderHistory = await client.getSpotOrdersHistory(); + assert(goodList(goodOrder, orderHistory), "not good order history"); + }); + }); + describe("Get spot Trades history", () => { + it("", async function () { + this.timeout(0); + let tradesHistory = await client.getSpotTradesHistory(); + assert(goodList(goodTrade, tradesHistory), "not good trade history"); + }); + }); +}); diff --git a/test/rest/wallet_management.test.ts b/test/rest/wallet_management.test.ts new file mode 100644 index 0000000..acbd9e2 --- /dev/null +++ b/test/rest/wallet_management.test.ts @@ -0,0 +1,229 @@ +import assert from "assert"; +import "mocha"; +import { CryptomarketAPIException, CryptomarketSDKException } from "../../lib"; +import { Client } from "../../lib/client"; +import { ACCOUNT } from "../../lib/constants"; +import { + goodAddress, + goodAmountLock, + goodBalance, + goodList, + goodTransaction, +} from "../test_helpers"; +const keys = require("/home/ismael/cryptomarket/keys-v3.json"); + +describe("wallet management", () => { + let client = new Client(keys.apiKey, keys.apiSecret); + function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); + } + const second = 1000; + beforeEach(async function () { + await sleep(second / 20); // make around 20 calls per second at most, to not pass the rate limiting of 30 calls per second, by a big margin. + }); + describe("Get wallet balance", () => { + it("", async function () { + this.timeout(0); + let balances = await client.getWalletBalance(); + assert(goodList(goodBalance, balances), "not good balance"); + }); + }); + describe("get wallet balance of currency", () => { + it("", async function () { + this.timeout(0); + let balance = await client.getWalletBalanceOfCurrency("ADA"); + assert(goodBalance(balance), "not good balance"); + }); + }); + describe("Get deposit crypto addresses", () => { + it("", async function () { + this.timeout(0); + let addressList = await client.getDepositCryptoAddresses(); + assert(goodList(goodAddress, addressList), "not good address"); + }); + }); + describe("Get deposit crypto address of symbol", () => { + it("", async function () { + this.timeout(0); + let address = await client.getDepositCryptoAddressOfCurrency("EOS"); + assert(goodAddress(address), "not good address"); + }); + }); + describe("create deposit crypto address", () => { + it("", async function () { + this.timeout(0); + let oldAddress = await client.getDepositCryptoAddressOfCurrency("EOS"); + let newAddres = await client.createDepositCryptoAddress("EOS"); + assert(oldAddress.address !== newAddres.address, "not a new address"); + assert(goodAddress(newAddres), "not good address"); + }); + }); + describe("get last 10 deposit crypto addresses", () => { + it("", async function () { + this.timeout(0); + let lastAddresses = await client.getLast10DepositCryptoAddresses("EOS"); + assert(goodList(goodAddress, lastAddresses), "not good address"); + }); + }); + describe("get last 10 withdrawal crypto addresses", () => { + it("", async function () { + this.timeout(0); + let lastAddresses = await client.getLast10WithdrawalCryptoAddresses( + "EOS" + ); + assert(goodList(goodAddress, lastAddresses), "not good address"); + }); + }); + describe("withdraw crypto and commitment/rollback", () => { + it("exception amount too low", async function () { + this.timeout(0); + let adaAddress = await client.getDepositCryptoAddressOfCurrency("ADA"); + try { + let transactionID = await client.withdrawCrypto({ + currency: "ADA", + amount: "0.01", + address: adaAddress.address, + }); + assert(transactionID !== "", "no transaction id"); + } catch (e) { + if (e instanceof CryptomarketAPIException) { + assert(e.code == 10001); + } + } + }); + it("with auto commit (default mode)", async function () { + this.timeout(0); + let adaAddress = await client.getDepositCryptoAddressOfCurrency("ADA"); + let transactionID = await client.withdrawCrypto({ + currency: "ADA", + amount: "0.1", + address: adaAddress.address, + }); + assert(transactionID !== "", "no transaction id"); + }); + it("with commit", async function () { + this.timeout(0); + let adaAddress = await client.getDepositCryptoAddressOfCurrency("ADA"); + let transactionID = await client.withdrawCrypto({ + currency: "ADA", + amount: "0.1", + address: adaAddress.address, + auto_commit: false, + }); + assert(transactionID !== "", "no transaction id"); + let commitResult = await client.withdrawCryptoCommit(transactionID); + assert(commitResult, "failed to withdraw crypto"); + }); + it("with rollback", async function () { + this.timeout(0); + let adaAddress = await client.getDepositCryptoAddressOfCurrency("ADA"); + let transactionID = await client.withdrawCrypto({ + currency: "ADA", + amount: "0.1", + address: adaAddress.address, + auto_commit: false, + }); + assert(transactionID !== "", "no transaction id"); + let rollbackResult = await client.withdrawCryptoRollback(transactionID); + assert(rollbackResult, "failed to rollback withdraw crypto transaction"); + }); + }); + describe("get estimate withdrawal fee", () => { + it("", async function () { + this.timeout(0); + let fee = await client.getEstimateWithdrawFee({ + currency: "CRO", + amount: "100", + }); + assert(fee !== "", "not a good fee"); + }); + }); + describe("check if crypto address belongs to current account", () => { + it("cro belongs", async function () { + this.timeout(0); + let croAddress = await client.getDepositCryptoAddressOfCurrency("CRO"); + let result = await client.checkIfCryptoAddressBelongsToCurrentAccount( + croAddress.address + ); + assert(result == true, "does not belong"); + }); + it.skip("eos belongs", async function () { + this.timeout(0); + let eosAddress = await client.getDepositCryptoAddressOfCurrency("EOS"); + let result = await client.checkIfCryptoAddressBelongsToCurrentAccount( + eosAddress.address + ); + assert(result == true, "does not belong"); + }); + it("does not belong", async function () { + this.timeout(0); + const result = await client.checkIfCryptoAddressBelongsToCurrentAccount( + "abc" + ); + assert(result == false, "belong"); + }); + }); + describe("transfer between wallet and exchange", () => { + it("", async function () { + this.timeout(0); + // original wallet amount + let startingADAInWallet = await client.getWalletBalanceOfCurrency("ADA"); + // transfer to spot + let transactionID = await client.transferBetweenWalletAndExchange({ + source: ACCOUNT.WALLET, + destination: ACCOUNT.SPOT, + currency: "ADA", + amount: 1, + }); + assert(transactionID !== "", "not good identifier of transfer to spot"); + + // transfering back + transactionID = await client.transferBetweenWalletAndExchange({ + source: ACCOUNT.SPOT, + destination: ACCOUNT.WALLET, + currency: "ADA", + amount: 1, + }); + assert(transactionID !== "", "not good identifier of transfer to wallet"); + + // end wallet amount + let endADAInWallet = await client.getWalletBalanceOfCurrency("ADA"); + assert( + startingADAInWallet.available === endADAInWallet.available, + "not good tranfer" + ); + }); + }); + describe("get transaction history", () => { + it("", async function () { + this.timeout(0); + let transactions = await client.getTransactionHistory(); + assert(goodList(goodTransaction, transactions), "not good transaction"); + }); + }); + describe("get transaction by identifier", () => { + it("", async function () { + this.timeout(0); + // For more information see the ruby sdk test + }); + }); + describe("check if offchain is available", () => { + it("", async function () { + this.timeout(0); + let myEOSAddress = await client.getDepositCryptoAddressOfCurrency("EOS"); + let result = await client.checkIfOffchainIsAvailable({ + currency: "EOS", + address: myEOSAddress.address, + }); + // we do not care if its available or not, only if it runs properly, + assert(result !== null, "not good result"); + }); + }); + describe.skip("get amount locks", () => { + it("", async function () { + this.timeout(0); + let amountLocks = await client.getAmountLocks(); + assert(goodList(goodAmountLock, amountLocks), "not good amount locks"); + }); + }); +}); diff --git a/test/test_helpers.js b/test/test_helpers.js deleted file mode 100644 index ede4db7..0000000 --- a/test/test_helpers.js +++ /dev/null @@ -1,270 +0,0 @@ -// defined checks if a key is present in a dict, and if its value is str, checks if its defined. -// return false when the key is not present or when the value is an empty string, return true otherwise. -function defined(anObject, key) { - if (!(key in anObject)) return false - val = anObject[key] - if ((typeof val === 'string' || val instanceof String) && val === "") return false - return true -} - -// goodObject checks all of the values in the fields list to be present in the dict, and if they are -// present, check the defined() condition to be true. if any of the fields fails to be defined(), then -// this function returns false -function goodObject(anObject, fields) { - if (!(anObject)) return false - if (!(typeof anObject === 'object' || anObject instanceof Object)) return false - for (field of fields) { - if (!defined(anObject, field)) return false - } - return true -} - - -// goodCurrency checks the precence of every field in the currency dict -function goodCurrency(currency) { - return goodObject(currency, - [ - "id", - "fullName", - "crypto", - "payinEnabled", - "payinPaymentId", - "payinConfirmations", - "payoutEnabled", - "payoutIsPaymentId", - "transferEnabled", - "delisted", - // "precisionPayout", - // "precisionTransfer", - ] - ) -} - -// goodSymbol check the precence of every field in the symbol dict -function goodSymbol(symbol) { - return goodObject(symbol, - [ - 'id', - 'baseCurrency', - 'quoteCurrency', - 'quantityIncrement', - 'tickSize', - 'takeLiquidityRate', - 'provideLiquidityRate', - // 'feeCurrency' - ] - ) -} - - -// goodTicker check the precence of every field in the ticker dict -function goodTicker(ticker) { - return goodObject(ticker, - [ - "symbol", - "ask", - "bid", - "last", - "low", - "high", - "open", - "volume", - "volumeQuote", - "timestamp", - ] - ) -} - - -// goodPublicTrade check the precence of every field in the trade dict -function goodPublicTrade(trade) { - return goodObject(trade, - [ - "id", - "price", - "quantity", - "side", - "timestamp", - ] - ) -} - -// goodOrderbookLevel check the precence of every field in the level dict -function goodOrderbookLevel(level) { - return goodObject(level, - [ - "price", - "size", - ] - ) -} - -// goodOrderbook check the precence of every field in the orderbook dict -// and the fields of each level in each side of the orderbook -function goodOrderbook(orderbook) { - goodOrderbook = goodObject(orderbook, - [ - "symbol", - "timestamp", - // "batchingTime", - "ask", - "bid", - ] - ) - if (!goodOrderbook) return false - - for (level of orderbook["ask"]) { - if (!goodOrderbookLevel(level)) return false - } - for (level of orderbook["bid"]) { - if (!goodOrderbookLevel(level)) return false - } - return true -} - - -// goodCandle check the precence of every field in the candle dict -function goodCandle(candle) { - return goodObject(candle, - [ - "timestamp", - "open", - "close", - "min", - "max", - "volume", - "volumeQuote", - ] - ) -} - - -// goodCandleList check the precence of every field of the candle dict in every candle of the candle list. -function goodCandleList(candles) { - for (candle of candles) { - if (!goodCandle(candle)) return false - } - return true -} - - -// goodBalances check the precence of every field on every balance dict -function goodBalances(balances) { - for (balance of balances) { - goodBalance = goodObject(balance, - [ - "currency", - "available", - "reserved", - ] - ) - if (!goodBalance) return false - } - return true -} - - -// goodOrder check the precence of every field in the order dict -function goodOrder(order) { - return goodObject(order, - [ - "id", - "clientOrderId", - "symbol", - "side", - "status", - "type", - "timeInForce", - "quantity", - "price", - "cumQuantity", - // "postOnly", // does not appears in the orders in orders history - "createdAt", - "updatedAt", - ] - ) -} - - -// goodOrderList check the precence of every field of the order dict in every order of the order list. -function goodOrderList(orders) { - for (order of orders){ - if (!goodOrder(order)) return false - } - return true -} - -// goodTrade check the precence of every field in the trade dict -function goodTrade(trade) { - return goodObject(trade, - [ - "id", - "orderId", - "clientOrderId", - "symbol", - "side", - "quantity", - "price", - "fee", - "timestamp", - ] - ) -} - - - -// goodTransaction check the precence of every field in the transaction dict -function goodTransaction(transaction) { - return goodObject(transaction, - [ - "id", - "index", - "currency", - "amount", - // "fee", - // "address", - // "hash", - "status", - "type", - "createdAt", - "updatedAt", - ] - ) -} - -function goodReport(report) { - return goodObject(report, - [ - "id", - "clientOrderId", - "symbol", - "side", - "status", - "type", - "timeInForce", - "quantity", - "price", - "cumQuantity", - // "postOnly", // does not appears in the orders in orders history - "createdAt", - "updatedAt", - "reportType", - ] - ) -} - -module.exports = { - goodBalances, - goodCandle, - goodCandleList, - goodCurrency, - goodOrder, - goodOrderList, - goodOrderbook, - goodPublicTrade, - goodSymbol, - goodTicker, - goodTrade, - goodTransaction, - goodReport -} \ No newline at end of file diff --git a/test/test_helpers.ts b/test/test_helpers.ts new file mode 100644 index 0000000..6c81ad3 --- /dev/null +++ b/test/test_helpers.ts @@ -0,0 +1,359 @@ +export const timeout = (ms: number) => { + return new Promise((resolve) => setTimeout(resolve, ms)); +}; +export const SECOND = 1000; + +// defined checks if a key is present in a dict, and if its value is str, checks if its defined. +// return false when the key is not present or when the value is an empty string, return true otherwise. +export function defined(anObject: { [x: string]: any }, key: string) { + if (!(key in anObject)) return false; + const val = anObject[key]; + if ((typeof val === "string" || val instanceof String) && val === "") + return false; + return true; +} + +// goodObject checks all of the values in the fields list to be present in the dict, and if they are +// present, check the defined() condition to be true. if any of the fields fails to be defined(), then +// this function returns false +export function goodObject(anObject: any, fields: string[]) { + if (!anObject) return false; + if (!(typeof anObject === "object" || anObject instanceof Object)) + return false; + for (const field of fields) { + if (!defined(anObject, field)) return false; + } + return true; +} + +export function emptyList(list: any[]) { + return list.length == 0; +} + +export function listSize(list: string | any[], size: any) { + return list.length == size; +} + +export function goodList( + checkFn: { (trade: any): boolean; (arg0: any): any }, + list: any +) { + for (const elem of list) { + if (!checkFn(elem)) return false; + } + return true; +} + +export function emptyDict(dict: {}) { + return Object.keys(dict).length == 0; +} + +export function dictSize(dict: {}, size: number) { + return Object.keys(dict).length == size; +} + +export function goodDict(checkFn: (arg0: any) => any, dict: { [x: string]: any }) { + for (const key in dict) { + if (!checkFn(dict[key])) return false; + } + return true; +} + +// goodCurrency checks the precence of every field in the currency dict +export function goodCurrency(currency: { [x: string]: any }) { + let good = goodObject(currency, [ + "full_name", + "payin_enabled", + "payout_enabled", + "transfer_enabled", + "precision_transfer", + "networks", + ]); + if (!good) { + return false; + } + for (const network of currency["networks"]) { + if (!goodNetwork(network)) { + return false; + } + } + return true; +} + +export function goodNetwork(network: any) { + return goodObject(network, [ + "network", + "default", + "payin_enabled", + "payout_enabled", + "precision_payout", + "payout_fee", + "payout_is_payment_id", + "payin_payment_id", + "payin_confirmations", + ]); +} +// goodSymbol check the precence of every field in the symbol dict +export function goodSymbol(symbol: any) { + return goodObject(symbol, [ + "type", + "base_currency", + "quote_currency", + "status", + "quantity_increment", + "tick_size", + "take_rate", + "make_rate", + "fee_currency", + // "margin_trading", + // "max_initial_leverage", + ]); +} + +// goodTicker check the precence of every field in the ticker dict +export function goodTicker(ticker: any) { + return goodObject(ticker, [ + "ask", + "bid", + "last", + "low", + "high", + "open", + "volume", + "volume_quote", + "timestamp", + ]); +} + +export function goodPrice(price: any) { + return goodObject(price, ["currency", "price", "timestamp"]); +} + +export function goodTickerPrice(price: any) { + return goodObject(price, ["price", "timestamp"]); +} + +export function goodPriceHistory(price_history: { [x: string]: any }) { + let good = goodObject(price_history, ["currency", "history"]); + if (!good) return false; + for (const point of price_history["history"]) { + if (!goodHistoryPoint(point)) return false; + } + return true; +} + +export function goodHistoryPoint(point: any) { + return goodObject(point, ["timestamp", "open", "close", "min", "max"]); +} + +// goodPublicTrade check the precence of every field in the trade dict +export function goodPublicTrade(trade: any) { + return goodObject(trade, ["id", "price", "qty", "side", "timestamp"]); +} + +// goodOrderbookLevel check the precence of every field in the level dict +export function goodOrderbookLevel(level: string | any[]) { + return level.length == 2; +} + +// goodOrderbook check the precence of every field in the orderbook dict +// and the fields of each level in each side of the orderbook +export function goodOrderbook(orderbook: { [x: string]: any }) { + const goodOrderbook = goodObject(orderbook, ["timestamp", "ask", "bid"]); + if (!goodOrderbook) return false; + + for (const level of orderbook["ask"]) { + if (!goodOrderbookLevel(level)) return false; + } + for (const level of orderbook["bid"]) { + if (!goodOrderbookLevel(level)) return false; + } + return true; +} + +// goodCandle check the precence of every field in the candle dict +export function goodCandle(candle: any) { + return goodObject(candle, [ + "timestamp", + "open", + "close", + "min", + "max", + "volume", + "volume_quote", + ]); +} + +// goodBalance check the precence of every field on every balance dict +export function goodBalance(balance: any) { + return goodObject(balance, [ + "currency", + "available", + "reserved", + // "reserved_margin" optional. + ]); +} + +// goodOrder check the precence of every field in the order dict +export function goodOrder(order: { [x: string]: any }) { + let good = goodObject(order, [ + "id", + "client_order_id", + "symbol", + "side", + "status", + "type", + "time_in_force", + "quantity", + "quantity_cumulative", + // "price", // optional + // "stop_price", // optional + // "expire_time", // optional + // "original_client_order_id", // optional + "created_at", + "updated_at", + // "trades", // optional. List of trades + ]); + if (!good) return false; + if ("trades" in order) { + return goodList(goodTradeOfOrder, order["trades"]); + } + return true; +} + +// goodTradeOfOrder checks orders that are part of an order model. +export function goodTradeOfOrder(trade: any) { + return goodObject(trade, [ + "id", + "quantity", + "price", + "fee", + "timestamp", + "taker", + ]); +} + +// goodTrade check the precence of every field in the trade dict. trades of this type are present in trade history. +export function goodTrade(trade: any) { + return goodObject(trade, [ + "id", + "order_id", + "client_order_id", + "symbol", + "side", + "quantity", + "price", + "fee", + "timestamp", + "taker", + ]); +} + +export function goodTradingCommission(transaction: any) { + return goodObject(transaction, ["symbol", "take_rate", "make_rate"]); +} + +// goodTransaction check the precence of every field in the transaction dict +export function goodAddress(transaction: any) { + return goodObject(transaction, [ + "currency", + "address", + // "payment_id", // optional + // "public_key", // optional + ]); +} + +// goodTransaction check the precence of every field in the transaction dict +export function goodTransaction(transaction: { native: any }) { + let good = goodObject(transaction, [ + "id", + "status", + "type", + "subtype", + "created_at", + "updated_at", + // "native", // optional + // "primetrust", // optional + // "meta" // optional + ]); + if (!good) return false; + if ("native" in transaction && !goodNativeTransaction(transaction.native)) { + return false; + } + if ("meta" in transaction && !goodMetaTransaction(transaction.native)) { + return false; + } + return true; +} + +export function goodNativeTransaction(transaction: any) { + return goodObject(transaction, [ + "tx_id", + "index", + "currency", + "amount", + // "fee", // optional + // "address", // optional + // "payment_id", // optional + // "hash", // optional + // "offchain_id", // optional + // "confirmations", // optional + // "public_comment", // optional + // "error_code", // optional + // "senders" // optional + ]); +} + +export function goodMetaTransaction(transaction: any) { + return goodObject(transaction, [ + "fiat_to_crypto", + "id", + "provider_name", + "order_type", + "order_type", + "source_currency", + "target_currency", + "wallet_address", + "tx_hash", + "target_amount", + "source_amount", + "status", + "created_at", + "updated_at", + "deleted_at", + "payment_method_type", + ]); +} + +export function goodReport(report: any) { + return goodObject(report, [ + "id", + "clientOrderId", + "symbol", + "side", + "status", + "type", + "timeInForce", + "quantity", + "price", + "cumQuantity", + // "postOnly", // does not appears in the orders in orders history + "createdAt", + "updatedAt", + "reportType", + ]); +} + +export function goodAmountLock(report: any) { + return goodObject(report, [ + "id", + "currency", + "amount", + "date_end", + "description", + "cancelled", + "cancelled_at", + "cancel_description", + "created_at", + ]); +} \ No newline at end of file diff --git a/test/test_rest_account_management.js b/test/test_rest_account_management.js deleted file mode 100644 index 4fa38c1..0000000 --- a/test/test_rest_account_management.js +++ /dev/null @@ -1,61 +0,0 @@ -const { Client } = require("../lib/client") -const keys = require("../../keys.json") -const check = require("./test_helpers") -const { fail } = require("assert") - -describe('Get deposit crypto address', () => { - let client = new Client(keys.apiKey,keys.apiSecret); - it('should succeed', async function() { - address = await client.getDepositCryptoAddress('EOS') - if (!(typeof address === 'object' || address instanceof Object)) fail("should be an object") - if (!('address' in address)) fail("should have address") - if (address['address'] === "") fail("should have address") - }) -}) - -describe('Transfer between trading and account balances', () => { - let client = new Client(keys.apiKey,keys.apiSecret); - it('should succeed', async function() { - transaction = await client.transferMoneyFromAccountBalanceToTradingBalance("EOS", "0.1") - if ((typeof transaction === 'string' || transaction instanceof String) && transaction === "") fail("transcation failed") - transaction = await client.transferMoneyFromTradingBalanceToAccountBalance("EOS", "0.1") - if ((typeof transaction === 'string' || transaction instanceof String) && transaction === "") fail("transcation failed") - - }) -}) - -describe('get transaction history', () => { - let client = new Client(keys.apiKey,keys.apiSecret); - it('should succeed', async function() { - result = await client.getTransactionHistory("EOS") - for (transaction of result) { - if (!check.goodTransaction(transaction)) fail("should be good") - } - }) -}) - -describe('check crypto address is mine', () => { - let client = new Client(keys.apiKey,keys.apiSecret); - it('should succeed', async function() { - result = await client.checkIfCryptoAddressIsMine("words") - if (!("result" in result)) fail("should have result") - if (result["result"]) fail("is not mine") - }) -}) - -describe('estimate withdraw fee', () => { - let client = new Client(keys.apiKey,keys.apiSecret); - it('should succeed', async function() { - result = await client.getEstimatesWithdrawFee("EOS", "100") - if (!("fee" in result)) fail("should have fee") - if (result["fee"] == "") fail("no fee") - }) -}) - -describe('account balance', () => { - let client = new Client(keys.apiKey,keys.apiSecret); - it('should succeed', async function() { - result = await client.getAccountBalance() - if (!check.goodBalances(result)) fail("should be good") - }) -}) diff --git a/test/test_rest_public.js b/test/test_rest_public.js deleted file mode 100644 index 7da0f3d..0000000 --- a/test/test_rest_public.js +++ /dev/null @@ -1,249 +0,0 @@ -const { fail } = require("assert"); -const assert = require("assert") -const { Client } = require("../lib/client") -const check = require("./test_helpers") - -describe('Get Currencies', () => { - let client = new Client("",""); - - context('Get all currencies', () => { - it('should be the full list of currencies', async function() { - currencies = await client.getCurrencies() - if (currencies.length == 0) fail("should have currencies") - for (currency of currencies) { - if (!check.goodCurrency(currency)) fail("should be good") - } - }) - }) - - context('Get some currencies', () => { - it('should be a list of two currencies', async function() { - currencies = await client.getCurrencies(['eos', 'BTC']) - assert(currencies.length == 2, "wrong number of currencies") - for (currency of currencies) { - if (!check.goodCurrency(currency)) fail("should be good") - } - }) - }) - - context('Get one currency', () => { - it('should be a list of one currency', async function() { - currencies = await client.getCurrencies(['eth']) - assert(currencies.length == 1, "wrong number of currencies") - for (currency of currencies) { - if (!check.goodCurrency(currency)) fail("should be good") - } - }) - }) -}) - -describe('Get Symbol', () => { - let client = new Client("",""); - it('should succeed', async function() { - let result = await client.getSymbol('ETHBTC') - if (!check.goodSymbol(result)) fail("should be good") - assert(result.id === 'ETHBTC', "failed request") - }) -}) - -describe('Get Symbols', () => { - let client = new Client("",""); - - context('Get all symbols', () => { - it('should be the full list of symbols', async function() { - symbols = await client.getSymbols() - if (symbols.length == 0) fail("should have symbols") - for (symbol of symbols) { - if (!check.goodSymbol(symbol)) fail("should be good") - } - }) - }) - - context('Get some symbols', () => { - it('should be a list of two symbols', async function() { - symbols = await client.getSymbols(['EOSETH', 'PAXGBTC']) - for (symbol of symbols) { - if (!check.goodSymbol(symbol)) fail("should be good") - } - assert(symbols.length == 2, "wrong number of symbols") - }) - }) - - context('Get one symbol', () => { - it('should be a list of one symbol', async function() { - symbols = await client.getSymbols(['EOSETH']) - for (symbol of symbols) { - if (!check.goodSymbol(symbol)) fail("should be good") - } - assert(symbols.length == 1, "wrong number of symbols") - }) - }) -}) - - -describe('Get Ticker', () => { - let client = new Client("",""); - - context('Get tickers of all symbols', () => { - it('should be the full list of tickers', async function() { - tickers = await client.getTickers() - if (tickers.length == 0) fail("should have tickers") - for (key in tickers) { - if (!check.goodTicker(tickers[key])) fail("should be good") - } - }) - }) - - context('Get tickers of some symbols', () => { - it('should be a list of two tickers', async function() { - tickers = await client.getTickers(['EOSETH', 'PAXGBTC']) - for (key in tickers) { - if (!check.goodTicker(tickers[key])) fail("should be good") - } - assert(tickers.length == 2, "wrong number of tickers") - }) - }) - - context('Get ticker of one symbol', () => { - it('should be a list of one ticker', async function() { - tickers = await client.getTickers(['EOSETH']) - for (key in tickers) { - if (!check.goodTicker(tickers[key])) fail("should be good") - } - assert(tickers.length == 1, "wrong number of tickers") - }) - }) -}) - -describe('Get Ticker', () => { - let client = new Client("",""); - it('should succeed', async function() { - ticker = await client.getTicker('EOSETH') - if (!check.goodTicker(ticker)) fail("should be good") - assert(ticker.symbol === 'EOSETH', "failed request") - }) -}) - - -describe('Get Trades', () => { - let client = new Client("",""); - - context('Get trades of all symbols', () => { - it('should be the full list of trades', async function() { - trades = await client.getTrades() - if (trades.length ==0) fail("should have trades") - for (key in trades) { - for (trade of trades[key]) { - if (!check.goodPublicTrade(trade)) fail("should be good") - } - } - }) - }) - - context('Get trades of some symbols', () => { - it('should be trades of two symbols', async function() { - trades = await client.getTrades({'symbols':['EOSETH', 'PAXGBTC'], 'limit':2}) - for (key in trades) { - for (trade of trades[key]) { - if (!check.goodPublicTrade(trade)) fail("should be good") - } - } - assert(Object.keys(trades).length == 2, "wrong number of tickers") - }) - }) - - context('Get trades of one symbol', () => { - it('should be the trades of one symbol', async function() { - trades = await client.getTrades({"symbols":['EOSETH']}) - for (key in trades) { - for (trade of trades[key]) { - if (!check.goodPublicTrade(trade)) fail("should be good") - } - } - assert(Object.keys(trades).length == 1, "wrong number of tickers") - }) - }) -}) - -describe('Get order books', () => { - let client = new Client("",""); - - context('Get order books of all symbols', () => { - it('should be the full list of order books', async function() { - orderBooks = await client.getOrderBooks() - if (orderBooks.length == 0) fail("should have orderbooks") - for (key in orderBooks) { - if (!check.goodOrderbook(orderBooks[key])) fail("should be good") - } - }) - }) - - context('Get order books of some symbols', () => { - it('should be order books of two symbols', async function() { - orderBooks = await client.getOrderBooks(['EOSETH', 'PAXGBTC'], 2) - for (key in orderBooks) { - if (!check.goodOrderbook(orderBooks[key])) fail("should be good") - } - assert(Object.keys(orderBooks).length == 2, "wrong number of books") - }) - }) - - context('Get order books of one symbol', () => { - it('should be the order books of one symbol', async function() { - orderBooks = await client.getOrderBooks(['EOSETH']) - for (key in orderBooks) { - if (!check.goodOrderbook(orderBooks[key])) fail("should be good") - } - assert(Object.keys(orderBooks).length == 1, "wrong number of books") - }) - }) -}) - - -describe('Get Order book', () => { - let client = new Client("",""); - it('should succeed', async function() { - orderBook = await client.getOrderBook('EOSETH') - if (!check.goodOrderbook(orderBook)) fail("should be good") - assert(orderBook.symbol === 'EOSETH', "failed request") - }) -}) - -describe('Get candles', () => { - let client = new Client("",""); - - context('Get candles of all symbols', () => { - it('should be the full list of candles', async function() { - candles = await client.getCandles() - if (candles.length ==0) fail("should have candles") - for (key in candles) { - if (!check.goodCandleList(candles[key])) fail("should be good") - } - }) - }) - - context('Get candles of some symbols', () => { - it('should be order candles of two symbols', async function() { - candles = await client.getCandles({ - 'symbols':['EOSETH', 'PAXGBTC'], - 'period':'M1', - 'limit':2 - }) - for (key in candles) { - if (!check.goodCandleList(candles[key])) fail("should be good") - } - assert(Object.keys(candles).length == 2, "wrong number of candles") - }) - }) - - context('Get candles of one symbol', () => { - it('should be the candles of one symbol', async function() { - candles = await client.getCandles({'symbols':['EOSETH']}) - for (key in candles) { - if (!check.goodCandleList(candles[key])) fail("should be good") - } - assert(Object.keys(candles).length == 1, "wrong number of candles") - }) - }) -}) - diff --git a/test/test_rest_trading.js b/test/test_rest_trading.js deleted file mode 100644 index c1899f4..0000000 --- a/test/test_rest_trading.js +++ /dev/null @@ -1,85 +0,0 @@ -const { Client } = require("../lib/client") -const keys = require("/home/ismael/cryptomarket/apis/keys.json"); -const check = require("./test_helpers") -const { fail } = require("assert"); - -function timeout(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} - - -describe('Get account trading balance', () => { - let client = new Client(keys.apiKey,keys.apiSecret); - it('should succeed', async function() { - balances = await client.getTradingBalance() - if (!check.goodBalances(balances)) fail("should be good") - }) -}) - -describe('create order with client order id', () => { - let client = new Client(keys.apiKey, keys.apiSecret); - it('should succeed', async function() { - let timestamp = Math.floor(Date.now() / 1000).toString() - order = await client.createOrder({ - 'symbol':'EOSETH', - 'side':'sell', - 'quantity':'0.01', - 'price':'1000', - 'clientOrderId':timestamp - }) - if (!check.goodOrder(order)) fail("should be good") - order = await client.getActiveOrder(timestamp) - if (!check.goodOrder(order)) fail("should be good") - order = await client.cancelOrder(timestamp) - if (!check.goodOrder(order)) fail("should be good") - }) -}) - - -describe('create order with no client order id', () => { - let client = new Client(keys.apiKey,keys.apiSecret); - it('should succeed', async function() { - order = await client.createOrder({ - symbol: 'EOSETH', - side: 'sell', - quantity: '0.01', - price: '1001', - }) - if (!check.goodOrder(order)) fail("should be good") - order = await client.cancelOrder(order.clientOrderId) - if (!check.goodOrder(order)) fail("should be good") - }) -}) - -describe('cancel all orders', () => { - let client = new Client(keys.apiKey,keys.apiSecret); - it('should succeed', async function() { - this.timeout(0) - client.cancelAllOrders() - order = await client.createOrder({ - symbol: 'EOSETH', - side: 'sell', - quantity: '0.01', - price: '1001', - }) - order = await client.createOrder({ - symbol: 'EOSBTC', - side: 'sell', - quantity: '0.01', - price: '1001', - }) - orders = await client.getActiveOrders('EOSETH') - response = await client.cancelAllOrders('EOSETH') - - if (response.length == orders.length) fail("it does not cancel all orders order, filter does not work") - client.cancelAllOrders() - }) -}) - -describe('get trading fee', () => { - let client = new Client(keys.apiKey,keys.apiSecret); - it('should succeed', async function() { - response = await client.tradingFee('EOSETH') - if (!('takeLiquidityRate' in response) || !("provideLiquidityRate" in response)) fail("not a trading fee") - }) -}) \ No newline at end of file diff --git a/test/test_rest_trading_history.js b/test/test_rest_trading_history.js deleted file mode 100644 index b61e49d..0000000 --- a/test/test_rest_trading_history.js +++ /dev/null @@ -1,43 +0,0 @@ -const { Client } = require("../lib/client") -const keys = require("../../keys.json") -const check = require("./test_helpers") -const { fail } = require("assert") - -describe('Get Orders', () => { - let client = new Client(keys.apiKey,keys.apiSecret); - it('should succeed', async function() { - orders = await client.getOrders('825d1fc9ac2b24ef7027400d3a05480b') - if (!check.goodOrderList(orders)) fail("should be good") - }) -}) - - -describe('Get Order history', () => { - let client = new Client(keys.apiKey,keys.apiSecret); - it('should succeed', async function() { - orders = await client.getOrdersHistory() - if (!check.goodOrderList(orders)) fail("should be good") - }) -}) - - -describe('Get Trades history', () => { - let client = new Client(keys.apiKey,keys.apiSecret); - it('should succeed', async function() { - trades = await client.getTradesHistory() - for (trade of trades) { - if (!check.goodTrade(trade)) fail("should be good") - } - }) -}) - - -describe('Get trades by order', () => { - let client = new Client(keys.apiKey,keys.apiSecret); - it('should succeed', async function() { - trades = await client.getTradesByOrder(337789478188) - for (trade of trades) { - if (!check.goodTrade(trade)) fail("should be good") - } - }) -}) diff --git a/test/test_websocket_account.js b/test/test_websocket_account.js deleted file mode 100644 index 5f2a810..0000000 --- a/test/test_websocket_account.js +++ /dev/null @@ -1,55 +0,0 @@ -const { WSAccountClient } = require("../lib/index") -const keys = require("../../keys.json"); -const { fail } = require("assert"); -const check = require("./test_helpers"); - -describe('get account balance', function() { - it('should succeed', async function() { - this.timeout(0) - let wsclient = new WSAccountClient(keys.apiKey, keys.apiSecret) - try { - await wsclient.connect() - balance = await wsclient.getAccountBalance() - if (!check.goodBalances(balance)) fail("sould be good") - } catch (err) { - fail("should not fail. " + err) - } - wsclient.close() - }) -}) - - -describe('find transactions', function() { - it('should succeed', async function() { - this.timeout(0) - let wsclient = new WSAccountClient(keys.apiKey, keys.apiSecret) - try { - await wsclient.connect() - transactions = await wsclient.findTransactions({currency:"EOS", limit:3}) - for (let transaction of transactions) { - if (!check.goodTransaction(transaction)) fail("sould be good") - } - } catch (err) { - fail("should not fail. " + err) - } - wsclient.close() - }) -}) - - -describe('load transactions', function() { - it('should succeed', async function() { - this.timeout(0) - let wsclient = new WSAccountClient(keys.apiKey, keys.apiSecret) - try { - await wsclient.connect() - transactions = await wsclient.loadTransactions({currency:"EOS", limit:3, showSenders:true}) - for (let transaction of transactions) { - if (!check.goodTransaction(transaction)) fail("sould be good") - } - } catch (err) { - fail("should not fail. " + err) - } - wsclient.close() - }) -}) \ No newline at end of file diff --git a/test/test_websocket_public.js b/test/test_websocket_public.js deleted file mode 100644 index ba46e33..0000000 --- a/test/test_websocket_public.js +++ /dev/null @@ -1,114 +0,0 @@ -const { PublicClient } = require("../lib/websocket/publicClient") -const { fail } = require("assert"); -const check = require("./test_helpers"); - - -describe('get an exchange error', function() { - it('should succeed', async function() { - this.timeout(0) - let wsclient = new PublicClient() - try { - await wsclient.connect() - await wsclient.getTrades("abcde") // not a real symbol - fail("shoul rise error") - } catch (err) { - } - wsclient.close() - }) -}) - - -describe('Get Currencies', function() { - it('should succeed', async function() { - this.timeout(0) - let wsclient = new PublicClient() - try { - await wsclient.connect() - currencies = await wsclient.getCurrencies() - for (currency of currencies) { - if (!check.goodCurrency(currency)) fail("should be good") - } - } catch (err) { - fail("should not fail" + err) - } - wsclient.close() - }) -}) - -describe('Get Currency', function() { - it('should succeed', async function() { - this.timeout(0) - let wsclient = new PublicClient() - try { - await wsclient.connect() - currency = await wsclient.getCurrency("EOS") - if (!check.goodCurrency(currency)) fail("should be good") - } catch (err) { - fail("should not fail" + err) - } - wsclient.close() - }) -}) - -describe('Get symbols', function() { - it('should succeed', async function() { - this.timeout(0) - let wsclient = new PublicClient() - try { - await wsclient.connect() - symbols = await wsclient.getSymbols() - for (symbol of symbols) { - if (!check.goodSymbol(symbol)) fail("should be good") - } - } catch (err) { - fail("should not fail" + err) - } - wsclient.close() - }) -}) - -describe('Get symbol', function() { - it('should succeed', async function() { - this.timeout(0) - let wsclient = new PublicClient() - try { - await wsclient.connect() - symbol = await wsclient.getSymbol("EOSETH") - if (!check.goodSymbol(symbol)) fail("should be good") - - } catch (err) { - fail("should not fail" + err) - } - wsclient.close() - }) -}) - -describe('get trades', function() { - it('should succeed', async function() { - this.timeout(0) - let wsclient = new PublicClient() - try { - await wsclient.connect() - trades = await wsclient.getTrades("ETHBTC") - for (trade of trades.data) { - if (!check.goodPublicTrade(trade)) fail("should be good") - } - } catch (err) { - fail("should not fail" + err) - } - wsclient.close() - }) - it('not a real symbol', async function() { - this.timeout(0) - let wsclient = new PublicClient() - - try { - await wsclient.connect() - await wsclient.getTrades("ETHBTCCCC") - fail("should fail") - } catch (err) { - // good, is failing - } - wsclient.close() - }) -}) \ No newline at end of file diff --git a/test/test_websocket_subscriptions_account.js b/test/test_websocket_subscriptions_account.js deleted file mode 100644 index 179f980..0000000 --- a/test/test_websocket_subscriptions_account.js +++ /dev/null @@ -1,49 +0,0 @@ -const { WSAccountClient, Client } = require("../lib/index") -const keys = require("/home/ismael/cryptomarket/apis/keys.json"); -const { fail } = require("assert"); -const check = require("./test_helpers") - -function timeout(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} -const second = 1000 - -describe('Subscribe to transactions', function() { - it('should succeed', async function() { - let wsclient = new WSAccountClient(keys.apiKey, keys.apiSecret) - let restClient = new Client(keys.apiKey, keys.apiSecret) - try { - await wsclient.connect() - await wsclient.subscribeToTransactions((feed) => { - if (!check.goodTransaction(feed)) fail("not a good transaction") - }) - await timeout(3 * second) - await restClient.transferMoneyFromAccountBalanceToTradingBalance("EOS", "0.01") - await timeout(3 * second) - await restClient.transferMoneyFromTradingBalanceToAccountBalance("EOS", "0.01") - await timeout(3 * second) - } catch (err) { - fail("should not fail. " + err) - } - wsclient.close() - }) -}) - - - -describe('Subscribe to balance', function() { - it('should succeed', async function() { - this.timeout(0) - let wsclient = new WSAccountClient(keys.apiKey, keys.apiSecret) - try { - await wsclient.connect() - await wsclient.subscribeToBalance((feed) => { - if (!check.goodBalances(feed)) fail("not a good balance") - }) - await timeout(3 * second) - } catch (err) { - fail("should not fail. " + err) - } - wsclient.close() - }) -}) \ No newline at end of file diff --git a/test/test_websocket_subscriptions_public.js b/test/test_websocket_subscriptions_public.js deleted file mode 100644 index f62f079..0000000 --- a/test/test_websocket_subscriptions_public.js +++ /dev/null @@ -1,175 +0,0 @@ -const { PublicClient } = require("../lib/websocket/publicClient") -const { fail } = require("assert"); -const check = require("./test_helpers") -const { ArgumentFormatException, CryptomarketAPIException, CryptomarketSDKException } = require('../lib/exceptions') - -function timeout(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} -const second = 1000 -const minute = 60 * second - -class TimeFlow { - constructor() { - this.oldTimestamp = null - } - - checkNextTimestamp(timestamp) { - let dtimestamp = new Date(timestamp) - let goodFlow = true - if (this.oldTimestamp && this.oldTimestamp.getTime() > dtimestamp.getTime()) { - goodFlow = false - } - this.oldTimestamp = dtimestamp - return goodFlow - } -} - -class SequenceFlow { - constructor() { - this.lastSequence = null - } - - checkNextSequence(sequence) { - let goodFlow = true - if (this.sequence && sequence - this.lastSequence != 1) { - console.log(`failing time: ${Date.now()}`) - console.log(`last: ${this.lastSequence}\tnew: ${sequence}`) - goodFlow = false - } - this.lastSequence = sequence - return goodFlow - } -} - -function handleErr(err) { - if (err instanceof CryptomarketAPIException) { - fail("api exception. " + err) - } else if (err instanceof ArgumentFormatException) { - fail("argument format exception." + err) - } else if (err instanceof CryptomarketSDKException) { - fail("sdk exception. " + err) - } else { - fail("exception. " + err) - } -} - - -describe('missing callback', function() { - it('should succeed', async function() { - this.timeout(0) - let wsclient = new PublicClient() - try { - await wsclient.connect() - await wsclient.subscribeToOrderbook('ETHBTC') // needs a callback - fail("missing callback") - } catch (error) { - } - wsclient.close() - }) -}) - - -describe('Subscribe to ticker', function() { - it('should succeed', async function() { - this.timeout(0) - let wsclient = new PublicClient() - let checker = new TimeFlow() - try { - await wsclient.connect() - await wsclient.subscribeToTicker('EOSETH', (ticker) => { - if (!checker.checkNextTimestamp(ticker.timestamp)) fail() - console.log("ticker: " + Date.now()) - console.log(ticker) - }) - await timeout(2 * minute) - - console.log('unsubcribing') - await wsclient.unsubscribeToTicker('EOSETH') - console.log('unsubscribed') - await timeout(5 * second) - } catch (err) { - handleErr(err) - } - wsclient.close() - }) -}) - -describe('Subscribe to orderbook', function() { - it('should succeed', async function() { - this.timeout(0) - checker = new SequenceFlow() - let wsclient = new PublicClient() - try { - await wsclient.connect() - console.log("connected") - } catch (err) { - console.log("failed to connect") - handleErr(err) - } try { - console.log(`start time: ${Date.now()}`) - await wsclient.subscribeToOrderbook('EOSETH', (ob) => { - checker.checkNextSequence(ob.sequence) - for (let side of [ob.bid, ob.ask]) { - for ({price, size} of side) { - if (size === '0.00') fail('bad book') - } - } - if (!check.goodOrderbook(ob)) fail( "should be a good orderbook") - }) - await timeout(5 * minute) - console.log('unsubcribing') - await wsclient.unsubscribeToOrderbook('EOSETH') - console.log("unsubscribed") - await timeout(5 * second) - } catch (err) { - handleErr(err) - } - wsclient.close() - }) -}) - -describe('Subscribe to trades', function() { - it('should succeed', async function() { - this.timeout(0) - let wsclient = new PublicClient() - try { - await wsclient.connect() - wsclient.subscribeToTrades('ETHBTC', 2, (trades) => { - console.log("trades: " + Date.now()) - console.log(trades) - for(trade of trades) if(!check.goodPublicTrade(trade)) fail("not a good trade") - }) - await timeout(2 * minute) - console.log("unsubcribing") - wsclient.unsubscribeToTrades('ETHBTC') - console.log("unsubscribed") - await timeout(5 * second) - } catch (err) { - handleErr(err) - } - wsclient.close() - }) -}) - -describe('Subscribe to candles', function() { - it('should succeed', async function() { - this.timeout(0) - let wsclient = new PublicClient() - try { - await wsclient.connect() - await wsclient.subscribeToCandles('ETHBTC', 'M1', null, (candles) => { - console.log("candles: " + Date.now()) - if (!check.goodCandleList(candles)) fail("not good") - }) - await timeout(2 * minute) - console.log('unsubcribing') - wsclient.unsubscribeToCandles('ETHBTC', 'M1') - console.log("unsubscribed") - await timeout(5 * second) - } catch (err) { - handleErr(err) - } - wsclient.close() - }) -}) \ No newline at end of file diff --git a/test/test_websocket_subscriptions_trading.js b/test/test_websocket_subscriptions_trading.js deleted file mode 100644 index 443ce4f..0000000 --- a/test/test_websocket_subscriptions_trading.js +++ /dev/null @@ -1,42 +0,0 @@ -const { TradingClient } = require("../lib/websocket/tradingClient") -const keys = require("/home/ismael/cryptomarket/apis/keys.json"); -const { fail } = require("assert"); -const check = require("./test_helpers") - -function timeout(ms) { - return new Promise(resolve => setTimeout(resolve, ms)); -} -const second = 1000 - -describe('Subscribe to reports', function() { - it('should succeed', async function() { - this.timeout(0) - let wsclient = new TradingClient(keys.apiKey, keys.apiSecret) - try { - await wsclient.connect() - await wsclient.subscribeToReports((feed) => { - if (Array.isArray(feed)) { - for (let report of feed) { - if (!check.goodReport(report)) {fail("not a good report in feed")} - } - } else if (!check.goodReport(feed)) {fail("not a good report in feed")} - - }) - await timeout(3 * second) - let clientOrderId = Math.floor(Date.now() / 1000).toString() - await wsclient.createOrder({ - clientOrderId, - symbol:'EOSETH', - side: 'sell', - quantity:'0.01', - price:'1000' - }) - await timeout(3 * second) - await wsclient.cancelOrder(clientOrderId) - await timeout(3 * second) - wsclient.close() - } catch (err) { - fail("should not fail. " + err) - } - }) -}) \ No newline at end of file diff --git a/test/test_websocket_trading.js b/test/test_websocket_trading.js deleted file mode 100644 index f8fb6e9..0000000 --- a/test/test_websocket_trading.js +++ /dev/null @@ -1,57 +0,0 @@ -const { TradingClient } = require("../lib/websocket/tradingClient") -const keys = require("/home/ismael/cryptomarket/apis/keys.json"); -const { fail } = require("assert"); -const check = require("./test_helpers"); - -describe('get trading balance', function() { - it('should succeed', async function() { - this.timeout(0) - let wsclient = new TradingClient(keys.apiKey, keys.apiSecret) - try { - await wsclient.connect() - balance = await wsclient.getTradingBalance() - if (!check.goodBalances(balance)) fail("sould be good") - } catch (err) { - fail("should not fail. " + err) - } - wsclient.close() - }) -}) - - -describe('order life cycle', function() { - it('should succeed', async function() { - this.timeout(0) - let wsclient = new TradingClient(keys.apiKey, keys.apiSecret) - try { - await wsclient.connect() - let clientOrderId = Math.floor(Date.now() / 1000).toString() - - let order = await wsclient.createOrder({ - clientOrderId:clientOrderId, - symbol:'EOSETH', - side:'sell', - quantity:'0.01', - price:'1000' - }) - if (!check.goodReport(order)) fail("not a good report") - - let activeOrders = await wsclient.getActiveOrders() - let present = false - for (order of activeOrders) { - if (order.clientOrderId === clientOrderId) present = true - } - if (!present) fail("order is not present") - - let newCOId = clientOrderId + "new" - order = await wsclient.replaceOrder(clientOrderId, newCOId, "0.02", "2000") - if (!check.goodReport(order)) fail("not a good report") - order = await wsclient.cancelOrder(newCOId) - if (!check.goodReport(order)) fail("not a good report") - - } catch (err) { - fail("should not fail. " + err) - } - wsclient.close() - }) -}) diff --git a/test/websocket/market_data.test.ts b/test/websocket/market_data.test.ts new file mode 100644 index 0000000..14b9d0b --- /dev/null +++ b/test/websocket/market_data.test.ts @@ -0,0 +1,234 @@ +import assert from "assert"; +import "mocha"; +import { expect } from "chai"; +import { WSMarketDataClient } from "../../lib"; +import { + DEPTH, + ORDER_BOOK_SPEED, + PERIOD, + TICKER_SPEED, +} from "../../lib/constants"; +import { SECOND, timeout } from "../test_helpers"; + +describe.only("websocket market data client", function () { + let wsclient: WSMarketDataClient; + + beforeEach(() => { + wsclient = new WSMarketDataClient(); + }); + + afterEach(() => { + wsclient.close(); + }); + + describe("subscribe to trades", function () { + it("should succeed", async function () { + this.timeout(0); + await wsclient.connect(); + try { + const symbols = await wsclient.subscribeToTrades({ + params: { + symbols: ["ETHBTC"], + limit: 4, + }, + callback: (trades, type): void => { + console.log(type); + for (const symbol in trades) { + console.log(""); + console.log(symbol); + console.log(trades[symbol]); + } + }, + }); + expect(symbols.length).equal(1); + await timeout(45 * SECOND); + } catch (err) { + assert.fail("should not fail: " + err); + } + }); + }); + + describe("subscribe to candles", function () { + it("should succeed", async function () { + this.timeout(0); + await wsclient.connect(); + try { + await wsclient.subscribeToCandles({ + callback: (candles, type) => { + for (const symbol in candles) { + console.log(symbol); + console.log(candles[symbol]); + } + }, + params: { + period: PERIOD._1_MINUTE, + symbols: ["EOSETH", "ETHBTC"], + limit: 3, + }, + }); + await timeout(3 * SECOND); + } catch (e) { + assert.fail("should not fail: " + e); + } + }); + }); + + describe("subscribe to mini ticker", function () { + it("should succeed", async function () { + this.timeout(0); + await wsclient.connect(); + try { + await wsclient.subscribeToMiniTicker({ + callback: (miniTickers, type) => { + for (const symbol in miniTickers) { + console.log(symbol); + console.log(miniTickers[symbol]); + } + }, + params: { + speed: TICKER_SPEED._1_S, + symbols: ["EOSETH", "ETHBTC"], + }, + }); + await timeout(3 * SECOND); + } catch (e) { + assert.fail("should not fail: " + e); + } + }); + }); + + describe("subscribe to ticker", function () { + it("should succeed", async function () { + this.timeout(0); + await wsclient.connect(); + try { + await wsclient.subscribeToTicker({ + callback: (tickers, type) => { + for (const symbol in tickers) { + console.log(symbol); + console.log(tickers[symbol]); + } + }, + params: { + speed: TICKER_SPEED._1_S, + symbols: ["EOSETH", "ETHBTC"], + }, + }); + await timeout(3 * SECOND); + } catch (e) { + assert.fail("should not fail: " + e); + } + }); + }); + + describe("subscribe to full orderbook", function () { + it("should succeed", async function () { + this.timeout(0); + await wsclient.connect(); + try { + await wsclient.subscribeToFullOrderBook({ + callback: (orderBooks, type) => { + for (const symbol in orderBooks) { + console.log(symbol); + console.log(orderBooks[symbol]); + } + }, + params: { + symbols: ["EOSETH", "ETHBTC"], + }, + }); + await timeout(3 * SECOND); + } catch (e) { + assert.fail("should not fail: " + e); + } + }); + }); + + describe("subscribe to partial orderbook", function () { + it("should succeed", async function () { + this.timeout(0); + await wsclient.connect(); + try { + await wsclient.subscribeToPartialOrderBook({ + callback: (orderBooks) => { + for (const symbol in orderBooks) { + console.log(symbol); + console.log(orderBooks[symbol]); + } + }, + params: { + speed: ORDER_BOOK_SPEED._100_MS, + depth: DEPTH._5, + symbols: ["EOSETH", "ETHBTC"], + }, + }); + await timeout(3 * SECOND); + } catch (e) { + assert.fail("should not fail: " + e); + } + }); + }); + + describe("subscribe to top of orderbook", function () { + it("should succeed", async function () { + this.timeout(0); + await wsclient.connect(); + try { + await wsclient.subscribeToTopOfOrderBook({ + callback: (orderBookTops) => { + for (const symbol in orderBookTops) { + console.log(symbol); + console.log(orderBookTops[symbol]); + } + }, + params: { + speed: ORDER_BOOK_SPEED._100_MS, + symbols: ["EOSETH", "ETHBTC"], + }, + }); + await timeout(3 * SECOND); + } catch (e) { + assert.fail("should not fail: " + e); + } + }); + }); + + describe("multiple subscriptions", function () { + it("override on second call on same channel", async function () { + this.timeout(0); + await wsclient.connect(); + const first_set = [ + "ALGOUSDT", + "ETHARS", + "USDTTUSD", + "ETHTUSD", + "THETAUSDT", + "ATOMBTC", + "NEOETH", + "AAVEUSDT", + ]; + const second_set = [ + "YFIBTC", + "ETHUSDC", + "SOLETH", + "UNIBTC", + "SOLUST", + "BUSDUSDT", + "XRPEURS", + "EURSDAI", + "BTCEURS", + "LUNAUSDT", + "MKRETH", + ]; + wsclient.subscribeToTicker({ + callback: () => console.log("first callbackk"), + params: { speed: TICKER_SPEED._1_S, symbols: first_set }, + }); + wsclient.subscribeToTicker({ + callback: () => console.log("second callback"), + params: { speed: TICKER_SPEED._1_S, symbols: second_set }, + }); + await timeout(20 * SECOND); + }); + }); +}); diff --git a/test/websocket/trading.test.ts b/test/websocket/trading.test.ts new file mode 100644 index 0000000..13585ea --- /dev/null +++ b/test/websocket/trading.test.ts @@ -0,0 +1,137 @@ +const keys = require("/home/ismael/cryptomarket/keys-v3.json"); +import { fail } from "assert"; +import "mocha"; +import { expect } from "chai"; +import { WSTradingClient } from "../../lib"; +import { SECOND, timeout } from "../test_helpers"; + +describe("TradingClient", () => { + let wsclient: WSTradingClient; + beforeEach(() => { + wsclient = new WSTradingClient(keys.apiKey, keys.apiSecret); + }); + + afterEach(() => { + wsclient.close(); + }); + + describe("get trading balance", function () { + it("list", async function () { + this.timeout(0); + try { + await wsclient.connect(); + const balances = await wsclient.getSpotTradingBalances(); + console.log(balances); + expect(balances.length).to.be.greaterThanOrEqual(1); + } catch (err) { + fail("should not fail. " + err); + } + }); + + it("only one", async function () { + this.timeout(0); + try { + await wsclient.connect(); + const balance = await wsclient.getSpotTradingBalanceOfCurrency({ + currency: "EOS", + }); + console.log(balance); + } catch (err) { + fail("should not fail. " + err); + } + }); + }); + + describe("order life cycle", function () { + it("should succeed", async function () { + this.timeout(0); + try { + await wsclient.connect(); + let clientOrderID = Math.floor(Date.now() / 1000).toString(); + + let orderReport = await wsclient.createSpotOrder({ + client_order_id: clientOrderID, + symbol: "EOSETH", + side: "sell", + quantity: "0.01", + price: "1000", + }); + console.log("after create order"); + console.log(orderReport); + let activeOrders = await wsclient.getActiveSpotOrders(); + let present = false; + for (orderReport of activeOrders) { + if (orderReport.client_order_id === clientOrderID) present = true; + } + if (!present) fail("order is not present"); + + let newClientOrderID = clientOrderID + "new"; + orderReport = await wsclient.replaceSpotOrder({ + client_order_id: clientOrderID, + new_client_order_id: newClientOrderID, + quantity: "0.01", + price: "2000", + }); + console.log("after replace"); + console.log(orderReport); + orderReport = await wsclient.cancelSpotOrder(newClientOrderID); + console.log("after cancel"); + console.log(orderReport); + } catch (err) { + console.log(err); + fail("err"); + } + }); + }); + describe("cancel all orders", function () { + it("should succeed", async function () { + this.timeout(0); + await wsclient.connect(); + try { + await wsclient.cancelSpotOrders(); + const list = [1, 2, 3, 4, 5]; + list.map(async () => { + await wsclient.createSpotOrder({ + symbol: "EOSETH", + side: "sell", + quantity: "0.01", + price: "1000", + }); + }); + await timeout(5 * SECOND); + const activeList = await wsclient.getActiveSpotOrders(); + const canceledOrders = await wsclient.cancelSpotOrders(); + expect(canceledOrders.length).to.equal(activeList.length); + } catch (err) { + fail("should not fail " + err); + } + }); + }); + + describe("get trading commission", function () { + it("list", async function () { + this.timeout(0); + try { + await wsclient.connect(); + const commissions = await wsclient.getSpotCommissions(); + console.log(commissions); + expect(commissions.length).to.be.greaterThanOrEqual(1); + } catch (err) { + fail("should not fail. " + err); + } + }); + + it("only one", async function () { + this.timeout(0); + try { + await wsclient.connect(); + const commission = await wsclient.getSpotCommissionOfSymbol({ + symbol: "EOSETH", + }); + console.log(commission); + } catch (err) { + fail("should not fail. " + err); + } + }); + }); +}); diff --git a/test/websocket/trading_subscriptions.test.ts b/test/websocket/trading_subscriptions.test.ts new file mode 100644 index 0000000..e0d0e5f --- /dev/null +++ b/test/websocket/trading_subscriptions.test.ts @@ -0,0 +1,43 @@ +const keys = require("/home/ismael/cryptomarket/keys-v3.json"); +import { fail } from "assert"; +import { expect } from "chai"; +import { WSTradingClient } from "../../lib"; +import { SECOND, timeout } from "../test_helpers"; + +describe("tradingClient subscriptions", function () { + let wsclient: WSTradingClient; + beforeEach(() => { + wsclient = new WSTradingClient(keys.apiKey, keys.apiSecret); + }); + + afterEach(() => { + wsclient.close(); + }); + + describe("Subscribe to reports", function () { + it("should succeed", async function () { + this.timeout(0); + await wsclient.connect(); + try { + await wsclient.subscribeToReports((notification, type) => { + console.log(notification); + }); + await timeout(3 * SECOND); + let clientOrderID = Math.floor(Date.now() / 1000).toString(); + await wsclient.createSpotOrder({ + client_order_id: clientOrderID, + symbol: "EOSETH", + side: "sell", + quantity: "0.01", + price: "1000", + }); + await timeout(3 * SECOND); + await wsclient.cancelSpotOrder(clientOrderID); + const result = await wsclient.unsubscribeToReports(); + expect(result).to.be.true; + } catch (err) { + fail("should not fail. " + err); + } + }); + }); +}); diff --git a/test/websocket/wallet.test.ts b/test/websocket/wallet.test.ts new file mode 100644 index 0000000..3b44f7a --- /dev/null +++ b/test/websocket/wallet.test.ts @@ -0,0 +1,59 @@ +const keys = require("/home/ismael/cryptomarket/keys-v3.json"); +import { fail } from "assert"; +import "mocha"; +import { expect } from "chai"; +import { WSWalletClient } from "../../lib"; + +describe("WalletClient", function () { + let wsclient: WSWalletClient; + beforeEach(() => { + wsclient = new WSWalletClient(keys.apiKey, keys.apiSecret); + }); + + afterEach(() => { + wsclient.close(); + }); + + describe("get account balance", function () { + it("list", async function () { + this.timeout(0); + await wsclient.connect(); + try { + const balances = await wsclient.getWalletBalances(); + console.log(balances); + expect(balances.length).to.be.greaterThan(1); + } catch (err) { + fail("should not fail. " + err); + } + }); + it("single", async function () { + this.timeout(0); + await wsclient.connect(); + try { + const balance = await wsclient.getWalletBalanceOfCurrency({ + currency: "EOS", + }); + console.log(balance); + } catch (err) { + fail("should not fail. " + err); + } + }); + }); + + describe("get transactions", function () { + it("should succeed", async function () { + this.timeout(0); + await wsclient.connect(); + try { + const transactions = await wsclient.getTransactions({ + currencies: ["EOS"], + limit: 3, + }); + console.log(transactions); + } catch (err) { + fail("should not fail. " + err); + } + wsclient.close(); + }); + }); +}); diff --git a/test/websocket/wallet_subscriptions.test.ts b/test/websocket/wallet_subscriptions.test.ts new file mode 100644 index 0000000..35622b8 --- /dev/null +++ b/test/websocket/wallet_subscriptions.test.ts @@ -0,0 +1,84 @@ +//@ts-ignore +import { fail } from "assert"; +import { NOTIFICATION_TYPE } from "../../lib/constants"; +import { Client, WSWalletClient } from "../../lib/index"; +import { Balance, Transaction } from "../../lib/models"; +import { SECOND, timeout } from "../test_helpers"; +const keys = require("/home/ismael/cryptomarket/keys-v3.json"); + +describe("wallet transactions", function () { + let wsclient: WSWalletClient; + let restClient: any; + beforeEach(() => { + wsclient = new WSWalletClient(keys.apiKey, keys.apiSecret); + restClient = new Client(keys.apiKey, keys.apiSecret); + }); + + afterEach(() => { + wsclient.close(); + }); + + describe("Subscribe to transactions", function () { + it("should succeed", async function () { + this.timeout(0); + try { + await wsclient.connect(); + await wsclient.subscribeToTransactions( + (notification: Transaction[], type: NOTIFICATION_TYPE) => { + console.log("transaction notification"); + console.log("type: " + type); + console.log(notification); + } + ); + await restClient.transferBetweenWalletAndExchange({ + source: "wallet", + destination: "spot", + currency: "ADA", + amount: 1, + }); + await timeout(3 * SECOND); + await restClient.transferBetweenWalletAndExchange({ + source: "spot", + destination: "wallet", + currency: "ADA", + amount: 1, + }); + await timeout(3 * SECOND); + } catch (err) { + fail("should not fail. " + err); + } + }); + }); + + describe("Subscribe to balance", function () { + it("should succeed", async function () { + this.timeout(0); + try { + await wsclient.connect(); + await wsclient.subscribeToBalance( + (notification: Balance[], type: NOTIFICATION_TYPE) => { + console.log("balance notification"); + console.log("type: " + type); + console.log(notification); + } + ); + await restClient.transferBetweenWalletAndExchange({ + source: "wallet", + destination: "spot", + currency: "ADA", + amount: 1, + }); + await timeout(3 * SECOND); + await restClient.transferBetweenWalletAndExchange({ + source: "spot", + destination: "wallet", + currency: "ADA", + amount: 1, + }); + await timeout(3 * SECOND); + } catch (err) { + fail("should not fail. " + err); + } + }); + }); +}); From 895ec438b3129f02a45529f0177383919e2cdc9a Mon Sep 17 00:00:00 2001 From: "T. Ismael Verdugo" Date: Tue, 13 Dec 2022 23:22:11 -0300 Subject: [PATCH 02/16] adds OTO order lists --- lib/client.ts | 10 +++++++--- lib/constants.ts | 2 ++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/client.ts b/lib/client.ts index 7b31eed..8446543 100644 --- a/lib/client.ts +++ b/lib/client.ts @@ -656,20 +656,23 @@ export class Client { * * Types or contingency: * - * - CONTINGENCY.ALL_OR_NONE (CONTINGENCY.AON) - * - CONTINGENCY.ONE_CANCEL_OTHER (CONTINGENCY.OCO) - * - CONTINGENCY.ONE_TRIGGER_ONE_CANCEL_OTHER (CONTINGENCY.OTOCO) + * - CONTINGENCY.ALL_OR_NONE (CONTINGENCY.AON) (AON) + * - CONTINGENCY.ONE_CANCEL_OTHER (CONTINGENCY.OCO) (OCO) + * - CONTINGENCY.ONE_TRIGGER_OTHER (CONTINGENCY.OTO) (OTO) + * - CONTINGENCY.ONE_TRIGGER_ONE_CANCEL_OTHER (CONTINGENCY.OTOCO) (OTOCO) * * Restriction in the number of orders: * * - An AON list must have 2 or 3 orders * - An OCO list must have 2 or 3 orders + * - An OTO list must have 2 or 3 orders * - An OTOCO must have 3 or 4 orders * * Symbol restrictions: * * - For an AON order list, the symbol code of orders must be unique for each order in the list. * - For an OCO order list, there are no symbol code restrictions. + * - For an OTO order list, there are no symbol code restrictions. * - For an OTOCO order list, the symbol code of orders must be the same for all orders in the list (placing orders in different order books is not supported). * * ORDER_TYPE restrictions: @@ -677,6 +680,7 @@ export class Client { * - For an OCO order list, orders must be ORDER_TYPE.LIMIT, ORDER_TYPE.STOP_LIMIT, ORDER_TYPE.STOP_MARKET, ORDER_TYPE.TAKE_PROFIT_LIMIT or ORDER_TYPE.TAKE_PROFIT_MARKET. * - An OCO order list cannot include more than one limit order (the same * applies to secondary orders in an OTOCO order list). + * - For an OTO order list, there are no order type restrictions. * - For an OTOCO order list, the first order must be ORDER_TYPE.LIMIT, ORDER_TYPE.MARKET, ORDER_TYPE.STOP_LIMIT, ORDER_TYPE.STOP_MARKET, ORDER_TYPE.TAKE_PROFIT_LIMIT or ORDER_TYPE.TAKE_PROFIT_MARKET. * - For an OTOCO order list, the secondary orders have the same restrictions as an OCO order * - Default is ORDER_TYPE.Limit diff --git a/lib/constants.ts b/lib/constants.ts index dc016f0..645f09f 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -114,9 +114,11 @@ export enum NOTIFICATION { export enum CONTINGENCY { AON = "allOrNone", OCO = "oneCancelOther", + OTO = "oneTriggerOther", OTOCO = "oneTriggerOneCancelOther", ALL_OR_NONE = AON, ONE_CANCEL_OTHER = OCO, + ONE_TRIGGER_OTHER = OTO, ONE_TRIGGER_ONE_CANCEL_OTHER = OTOCO, } From b81438b9d12b4f369611782662e2370c7f00802c Mon Sep 17 00:00:00 2001 From: "T. Ismael Verdugo" Date: Tue, 6 Feb 2024 23:39:07 -0300 Subject: [PATCH 03/16] refactors, fixes and updates websocket clients --- lib/client.ts | 142 +++------ lib/constants.ts | 12 + lib/exceptions.ts | 14 +- lib/hmac.ts | 58 ++++ lib/httpClient.ts | 105 +++++++ lib/httpMethods.ts | 8 + lib/models.ts | 4 +- lib/models/Balance.ts | 2 +- lib/models/Currency.ts | 6 + lib/models/Fee.ts | 6 + lib/models/FeeRequest.ts | 5 + lib/models/OrderBook.ts | 11 + lib/models/Transactions.ts | 1 + lib/websocket/ResponsePromiseFactory.ts | 75 +++++ lib/websocket/WSResponse.ts | 11 + lib/websocket/authClient.ts | 47 ++- lib/websocket/clientBase.ts | 104 +++---- lib/websocket/marketDataClient.ts | 147 ++++++++-- lib/websocket/tradingClient.ts | 80 ++++- lib/websocket/walletClient.ts | 70 ++--- test/rest/spot_trading.test.ts | 39 ++- test/rest/spot_trading_history.test.ts | 2 +- test/rest/wallet_management.test.ts | 19 +- test/test_helpers.ts | 72 ++++- test/websocket/market_data.test.ts | 273 +++++++----------- .../response_promise_factory.test.ts | 60 ++++ test/websocket/trading.test.ts | 210 ++++++++------ test/websocket/trading_subscriptions.test.ts | 72 +++-- test/websocket/wallet.test.ts | 51 ++-- test/websocket/wallet_subscriptions.test.ts | 108 +++---- 30 files changed, 1145 insertions(+), 669 deletions(-) create mode 100644 lib/hmac.ts create mode 100644 lib/httpClient.ts create mode 100644 lib/httpMethods.ts create mode 100644 lib/models/Fee.ts create mode 100644 lib/models/FeeRequest.ts create mode 100644 lib/websocket/ResponsePromiseFactory.ts create mode 100644 lib/websocket/WSResponse.ts create mode 100644 test/websocket/response_promise_factory.test.ts diff --git a/lib/client.ts b/lib/client.ts index 8446543..27e8cff 100644 --- a/lib/client.ts +++ b/lib/client.ts @@ -1,10 +1,3 @@ -import CryptoJS from "crypto-js"; -import fetch from "node-fetch"; -import { - CryptomarketSDKException, - CryptomarketAPIException, -} from "./exceptions"; -import { URL, URLSearchParams } from "url"; import { ACCOUNT, CONTINGENCY, @@ -21,6 +14,7 @@ import { TRANSFER_TYPE, USE_OFFCHAIN, } from "./constants"; +import { HttpClient } from "./httpClient"; import { ACLSettings, Address, @@ -29,6 +23,8 @@ import { Candle, Commission, Currency, + Fee, + FeeRequest, Order, OrderBook, OrderRequest, @@ -42,129 +38,39 @@ import { Transaction, } from "./models"; -const apiUrl = "https://api.exchange.cryptomkt.com"; -const apiVersion = "/api/3/"; -const methodGet = "GET"; -const methodPut = "PUT"; -const methodPatch = "PATCH"; -const methodPost = "POST"; -const methodDelete = "DELETE"; export class Client { - apiKey: string; - apiSecret: string; - window: number | null; + apiUrl = "https://api.exchange.cryptomkt.com"; + apiVersion = "3"; + httpClient: HttpClient constructor(apiKey: string, apiSecret: string, window: number | null = null) { - this.apiKey = apiKey; - this.apiSecret = apiSecret; - this.window = window; + this.httpClient = new HttpClient(this.apiUrl, this.apiVersion, apiKey, apiSecret, window) } async publicGet(endpoint: string, params: any) { - return this.makeRequest(methodGet, endpoint, params, true); + return this.httpClient.publicGet(endpoint, params); } async get(endpoint: string, params: any | null = null) { - return this.makeRequest(methodGet, endpoint, params); + return this.httpClient.get(endpoint, params); } async patch(endpoint: string, params: any) { - return this.makeRequest(methodPatch, endpoint, params); + return this.httpClient.patch(endpoint, params); } async post(endpoint: string, params: any) { - return this.makeRequest(methodPost, endpoint, params); + return this.httpClient.post(endpoint, params); } async delete(endpoint: string, params: any | null = null) { - return this.makeRequest(methodDelete, endpoint, params); + return this.httpClient.delete(endpoint, params); } async put(endpoint: string, params: any | null = null) { - return this.makeRequest(methodPut, endpoint, params); - } - - async makeRequest( - method: string, - endpoint: string, - params: any, - publc: boolean = false - ) { - let url = new URL(apiUrl + apiVersion + endpoint); - for (let key in params) { - if (params[key] == null) { - delete params[key]; - } - } - let rawQuery = new URLSearchParams(params); - rawQuery.sort(); - let query = rawQuery.toString(); - - // build fetch options - let opts: any = { - method: method, - headers: { - "User-Agent": "cryptomarket/node", - "Content-type": "application/x-www-form-urlencoded", - }, - }; - // add auth header if not public endpoint - if (!publc) - opts.headers["Authorization"] = this.buildCredential(method, url, query); - - // include query params to call - if (method === methodGet || method === methodPut) url.search = query; - else opts.body = query; - - // make request - let response: { json: () => any; ok: boolean; status: any }; - try { - response = await fetch(url, opts); - } catch (e) { - throw new CryptomarketSDKException("Failed request to server", e); - } - let jsonResponse: any; - try { - jsonResponse = await response.json(); - } catch (e) { - throw new CryptomarketSDKException( - `Failed to parse response: ${response}`, - e - ); - } - if (!response.ok) { - throw new CryptomarketAPIException( - jsonResponse["error"], - response.status - ); - } - return jsonResponse; - } - - /** - * - * @param {URL} url - * @returns - */ - buildCredential(httpMethod: string, url: URL, query: string) { - let timestamp = Math.floor(Date.now()).toString(); - let msg = httpMethod + url.pathname; - if (query) { - if (httpMethod === methodGet) msg += "?"; - msg += query; - } - msg += timestamp; - if (this.window) { - msg += this.window; - } - let signature = CryptoJS.HmacSHA256(msg, this.apiSecret).toString(); - let signed = this.apiKey + ":" + signature + ":" + timestamp; - if (this.window) { - signed += ":" + this.window; - } - return `HS256 ${Buffer.from(signed).toString("base64")}`; + return this.httpClient.put(endpoint, params); } ////////////////// @@ -179,10 +85,11 @@ export class Client { * https://api.exchange.cryptomkt.com/#currencies * * @param {string[]} [currencies] Optional. A list of currencies ids + * @param [preferred_network] Optional. Code of the default network for currencies. * * @return A list of available currencies */ - getCurrencies(currencies?: string[]): Promise<{ [key: string]: Currency[] }> { + getCurrencies(currencies?: string[], preferred_network?: string): Promise<{ [key: string]: Currency[] }> { return this.get("public/currency/", { currencies }); } @@ -693,7 +600,7 @@ export class Client { * @return A promise that resolves with a list of reports of the created orders */ async createNewSpotOrderList(params: { - order_list_id: string; + order_list_id?: string; contingency_type: CONTINGENCY; orders: OrderRequest[]; }): Promise { @@ -1040,6 +947,21 @@ Accepted values: never, optionally, required return response["result"]; } + /** + * Get estimates of withdrawal fees + * + * Requires the "Payment information" API key Access Right. + * + * https://api.exchange.cryptomkt.com/#get-spot-fees + * + * @param {FeeRequest[]} feeRequests A list of fee requests + * + * @return The list of requested fees + */ + async getEstimateWithdrawFees(feeRequests: FeeRequest[]): Promise { + return this.post("wallet/crypto/fees/estimate", feeRequests); + } + /** * Get an estimate of the withdrawal fee * @@ -1050,12 +972,14 @@ Accepted values: never, optionally, required * @param {object} params * @param {string} params.currency the currency code for withdraw * @param {string} params.amount the expected withdraw amount + * @param {string} [params.netwrok_code] Optional. Network code * * @return The expected fee */ async getEstimateWithdrawFee(params: { currency: string; amount: string; + network_code?: string; }): Promise { const response = await this.get("wallet/crypto/fee/estimate", params); return response["fee"]; diff --git a/lib/constants.ts b/lib/constants.ts index 645f09f..25dd74e 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -93,6 +93,11 @@ export enum TICKER_SPEED { _3_S = "3s", } +export enum PRICE_RATE_SPEED { + _1_S = "1s", + _3_S = "3s", +} + export enum ORDER_BOOK_SPEED { _100_MS = "100ms", _500_MS = "500ms", @@ -130,6 +135,7 @@ export enum ORDER_STATUS { CANCELED = "canceled", EXPIRED = "expired", } +export const REPORT_STATUS = ORDER_STATUS; export enum REPORT_TYPE { STATUS = "status", @@ -142,6 +148,7 @@ export enum REPORT_TYPE { REPLACED = "replaced", } + export enum SYMBOL_STATUS { WORKING = "working", SUSPENDED = "suspended", @@ -164,3 +171,8 @@ export enum NOTIFICATION_TYPE { DATA = "data", COMMAND = "COMMAND", } + +export enum SUBSCRIPTION_MODE { + UPDATES = "updates", + BATCHES = "batches", +} diff --git a/lib/exceptions.ts b/lib/exceptions.ts index d3a8cbf..bfe8b93 100644 --- a/lib/exceptions.ts +++ b/lib/exceptions.ts @@ -1,3 +1,5 @@ +import { WSResponse, WSResponseError } from "./websocket/WSResponse"; + export class CryptomarketSDKException extends Error { constructor(...args: any) { super(...args); @@ -7,15 +9,9 @@ export class CryptomarketSDKException extends Error { export class CryptomarketAPIException extends CryptomarketSDKException { status: any; code: number; - constructor( - { - code, - message, - description, - }: { code: any; message: string; description: any }, - status?: any - ) { - super(`(code=${code}) ${message}. ${description}`); + + constructor({ code, message, description, }: WSResponseError, status?: any) { + super(`(code=${code}) ${message}. ${description || ''}`); this.code = code; this.status = status; } diff --git a/lib/hmac.ts b/lib/hmac.ts new file mode 100644 index 0000000..a7886d7 --- /dev/null +++ b/lib/hmac.ts @@ -0,0 +1,58 @@ +import CryptoJS from "crypto-js"; +import { URL } from "url"; +import { HTTP_METHOD } from "./httpMethods"; + + +export class HMAC { + apiKey: string + apiSecret: string + window: number | null + + constructor(apiKey: string, + apiSecret: string, + window: number | null = null) { + this.apiKey = apiKey + this.apiSecret = apiSecret + this.window = window + } + + /**+ + * + * @param {URL} url + * @returns + */ + buildCredential(httpMethod: string, url: URL, query: string) { + const timestamp = Math.floor(Date.now()).toString(); + const messageToSign = this.getMessageToSign(httpMethod, url, query, timestamp); + const signedMessage = this.sign(messageToSign); + const fullMessage = this.getFullMessage(signedMessage, timestamp); + return `HS256 ${Buffer.from(fullMessage).toString("base64")}`; + } + + private getFullMessage(signedMessage: string, timestamp: string) { + let fullMessage = this.apiKey + ":" + signedMessage + ":" + timestamp; + if (this.window) { + fullMessage += ":" + this.window; + } + return fullMessage; + } + + private sign(messageToSign: string) { + return CryptoJS.HmacSHA256(messageToSign, this.apiSecret).toString(CryptoJS.enc.Hex); + } + + private getMessageToSign(httpMethod: string, url: URL, query: string, timestamp: string) { + let messageToSign = httpMethod + url.pathname; + if (query) { + if (httpMethod === HTTP_METHOD.GET) { + messageToSign += "?"; + } + messageToSign += query; + } + messageToSign += timestamp; + if (this.window) { + messageToSign += this.window; + } + return messageToSign; + } +} \ No newline at end of file diff --git a/lib/httpClient.ts b/lib/httpClient.ts new file mode 100644 index 0000000..0ea4a65 --- /dev/null +++ b/lib/httpClient.ts @@ -0,0 +1,105 @@ +import fetch from "node-fetch"; +import { URL, URLSearchParams } from "url"; +import { + CryptomarketAPIException, + CryptomarketSDKException, +} from "./exceptions"; +import { HMAC } from "./hmac"; +import { HTTP_METHOD } from "./httpMethods"; + + +export class HttpClient { + apiPath: string + hmac: HMAC + + constructor(apiUrl: string, apiVersion: string, apiKey: string, apiSecret: string, window: number | null = null) { + this.apiPath = apiUrl + `/api/${apiVersion}/` + this.hmac = new HMAC(apiKey, apiSecret, window) + } + + async makeRequest( + method: HTTP_METHOD, + endpoint: string, + params: any, + publc: boolean = false + ): Promise { + const { url, opts } = this.prepareRequest(params, method, publc, endpoint); + try { + return await this.makeFetch(url, opts); + } catch (e) { + throw new CryptomarketSDKException("Failed request to server. " + e, e); + } + } + + + private prepareRequest(params_raw: any, method: HTTP_METHOD, publicMethod: boolean, endpoint: string): { url: URL, opts: Map } { + let url = new URL(this.apiPath + endpoint); + const params: [string, string][] = Object.entries(params_raw) + .filter(([k, v]) => v !== null) + .map(([k, v]) => [k, String(v)]) + let rawQuery = new URLSearchParams(params); + rawQuery.sort(); + let query = rawQuery.toString(); + + // build fetch options + let opts: any = { + method: method, + headers: { + "User-Agent": "cryptomarket/node", + } + }; + let credentialParams = query + if (method === HTTP_METHOD.POST) { + opts.headers["Content-Type"] = "application/json"; + credentialParams = JSON.stringify(params_raw) + } + // add auth header if not public endpoint + if (!publicMethod) + opts.headers["Authorization"] = this.hmac.buildCredential(method, url, credentialParams); + // include query params to call + if (method === HTTP_METHOD.GET || method === HTTP_METHOD.PUT) + url.search = query; + else + opts.body = credentialParams; + return { url, opts }; + } + + private async makeFetch(url: URL, opts: any): Promise { + const response = await fetch(url, opts) + let jsonResponse: any + try { + jsonResponse = await response.json() + } catch (e) { + throw new CryptomarketSDKException(`Failed to parse response: ${response}`, e) + } + if (!response.ok) { + throw new CryptomarketAPIException(jsonResponse["error"], response.status) + } + return jsonResponse + } + + async publicGet(endpoint: string, params: any) { + return this.makeRequest(HTTP_METHOD.GET, endpoint, params, true); + } + + async get(endpoint: string, params: any | null = null) { + return this.makeRequest(HTTP_METHOD.GET, endpoint, params); + } + + async patch(endpoint: string, params: any) { + return this.makeRequest(HTTP_METHOD.PATCH, endpoint, params); + } + + async post(endpoint: string, params: any) { + return this.makeRequest(HTTP_METHOD.POST, endpoint, params); + } + + async delete(endpoint: string, params: any | null = null) { + return this.makeRequest(HTTP_METHOD.DELETE, endpoint, params); + } + + async put(endpoint: string, params: any | null = null) { + return this.makeRequest(HTTP_METHOD.PUT, endpoint, params); + } + +} \ No newline at end of file diff --git a/lib/httpMethods.ts b/lib/httpMethods.ts new file mode 100644 index 0000000..8a6b4a2 --- /dev/null +++ b/lib/httpMethods.ts @@ -0,0 +1,8 @@ + +export enum HTTP_METHOD { + GET = "GET", + PUT = "PUT", + PATCH = "PATCH", + POST = "POST", + DELETE = "DELETE" +} diff --git a/lib/models.ts b/lib/models.ts index 32ae30e..bd1dd75 100644 --- a/lib/models.ts +++ b/lib/models.ts @@ -21,4 +21,6 @@ export * from "./models/Trade" export * from "./models/Transactions"; export * from "./models/WSCandle"; export * from "./models/WSTicker"; -export * from "./models/WSTrades"; \ No newline at end of file +export * from "./models/WSTrades"; +export * from "./models/Fee"; +export * from "./models/FeeRequest"; \ No newline at end of file diff --git a/lib/models/Balance.ts b/lib/models/Balance.ts index 28e1337..112e515 100644 --- a/lib/models/Balance.ts +++ b/lib/models/Balance.ts @@ -2,5 +2,5 @@ export interface Balance { currency: string; available: string; reserved: string; - reserved_margin: string; + reserved_margin?: string; } diff --git a/lib/models/Currency.ts b/lib/models/Currency.ts index 911fb6c..8562ac4 100644 --- a/lib/models/Currency.ts +++ b/lib/models/Currency.ts @@ -7,6 +7,12 @@ export interface Currency { payout_enabled: boolean; transfer_enabled: boolean; precision_transfer: string; + sign: string; + crypto_payment_id_name: string; + crypto_explorer: string; + account_top_order: number; + qr_prefix: string; + delisted: boolean; networks: Network[]; } diff --git a/lib/models/Fee.ts b/lib/models/Fee.ts new file mode 100644 index 0000000..7945cf3 --- /dev/null +++ b/lib/models/Fee.ts @@ -0,0 +1,6 @@ +export interface Fee { + fee: string; + currency: string; + amount: string; + networkCode: string; +} diff --git a/lib/models/FeeRequest.ts b/lib/models/FeeRequest.ts new file mode 100644 index 0000000..07662fa --- /dev/null +++ b/lib/models/FeeRequest.ts @@ -0,0 +1,5 @@ +export interface FeeRequest { + currency: string; + amount: string; + network_code?: string; +} \ No newline at end of file diff --git a/lib/models/OrderBook.ts b/lib/models/OrderBook.ts index 85c3098..366c41e 100644 --- a/lib/models/OrderBook.ts +++ b/lib/models/OrderBook.ts @@ -15,6 +15,17 @@ export interface OrderBookTop { B: string; } +export interface PriceRate { + /** + * timestamp + */ + t: number, + /** + * rate + */ + r: string +} + export interface OrderBook { timestamp: string; ask: OrderBookLevel[]; diff --git a/lib/models/Transactions.ts b/lib/models/Transactions.ts index 33e608e..0770914 100644 --- a/lib/models/Transactions.ts +++ b/lib/models/Transactions.ts @@ -1,4 +1,5 @@ export interface Transaction { + operation_id?: string; // present in websocket transaction subscription notifications id: number; status: string; type: string; diff --git a/lib/websocket/ResponsePromiseFactory.ts b/lib/websocket/ResponsePromiseFactory.ts new file mode 100644 index 0000000..23ea239 --- /dev/null +++ b/lib/websocket/ResponsePromiseFactory.ts @@ -0,0 +1,75 @@ +import { EventEmitter } from "events"; +import { + CryptomarketAPIException, CryptomarketSDKException, +} from "../exceptions"; +import { WSResponse } from "./WSResponse"; + +export class ResponsePromiseFactory { + private nextId: number; + protected emitter: EventEmitter; + + constructor(emitter: EventEmitter) { + this.emitter = emitter; + this.nextId = 1; + } + + protected getNextId(): number { + if (this.nextId < 1) this.nextId = 1; + const next = this.nextId; + this.nextId++; + return next; + } + + emit(key: string, data: any) { + this.emitter.emit(key, data) + } + + newMultiResponsePromise(responseCount: number = 1): { id: number, promise: Promise } { + const id = this.getNextId(); + return { id, promise: this.listenNTimes(this.emitter, id, responseCount) } + } + + newOneResponsePromise(): { id: number, promise: Promise } { + const id = this.getNextId(); + const oneTimePromise = this.listenNTimes(this.emitter, id, 1) + const oneResultResponse = new Promise(async (resolve, reject) => { + const results = await oneTimePromise; + if (results.length < 1) { + reject(new CryptomarketSDKException("empty response")) + return + } + if (results.length > 1) { + reject(new CryptomarketSDKException("too many responses")) + return + } + return resolve(results[0]) + }) + return { id, promise: oneResultResponse } + } + + private listenNTimes(emitter: EventEmitter, id: number, responseCount: number): Promise { + return new Promise((resolve, reject) => { + const results: unknown[] = [] + emitter.on(id.toString(), (response: WSResponse) => { + if (response.error != undefined) { + emitter.removeAllListeners(id.toString()) + reject(new CryptomarketAPIException(response.error)) + return; + } + const currentLength = results.push(response.result) + if (currentLength < responseCount) { + return + } + emitter.removeAllListeners(id.toString()) + if (currentLength == responseCount) { + resolve(results); + return + } + reject(new CryptomarketSDKException("invalid response count, too many responses from api")) + return + }) + }) + } + + +} diff --git a/lib/websocket/WSResponse.ts b/lib/websocket/WSResponse.ts new file mode 100644 index 0000000..8321bf2 --- /dev/null +++ b/lib/websocket/WSResponse.ts @@ -0,0 +1,11 @@ +export interface WSResponse { + id: string + result: string; + error: WSResponseError; +} + +export interface WSResponseError { + code: any; + message: string; + description: any; +} \ No newline at end of file diff --git a/lib/websocket/authClient.ts b/lib/websocket/authClient.ts index 2c1d339..9b2cc00 100644 --- a/lib/websocket/authClient.ts +++ b/lib/websocket/authClient.ts @@ -1,24 +1,40 @@ import CryptoJS from "crypto-js"; import { WSClientBase } from "./clientBase"; +interface AuthPayload { + type: string + api_Key: string + timestamp: number + window?: number + signature: string +} + export class AuthClient extends WSClientBase { private apiKey: string; private apiSecret: string; - private window: number | null; + private window?: number | null; constructor( url: string, apiKey: string, apiSecret: string, - window: number | null = null, + window?: number, + requestTimeoutMs?: number, subscriptionKeys = {} ) { - super(url, subscriptionKeys); + super(url, subscriptionKeys, requestTimeoutMs); this.apiKey = apiKey; this.apiSecret = apiSecret; - this.window = window; + this.window = window || null; } + /** + * Connects the client with the server + * + * Initializes the internal websocket, and then authenticates the client to permit user related calls + * + * @returns A prommise that resolves after the authentication + */ async connect(): Promise { await super.connect(); await this.authenticate(); @@ -31,29 +47,34 @@ export class AuthClient extends WSClientBase { * * @param {function} [callback] Optional. A function to call with the result data. It takes two arguments, err and result. err is None for successful calls, result is None for calls with error: callback(err, result) * - * @return The transaction status as result argument for the callback. + * @returns The transaction status as result argument for the callback. */ - authenticate() { + private authenticate() { const timestamp = Math.floor(Date.now()); - const params = { + const payload: AuthPayload = { type: "HS256", api_Key: this.apiKey, timestamp: timestamp, + signature: "", }; let toSign = timestamp.toString(); if (this.window) { - params["window"] = this.window; + payload.window = this.window; toSign += this.window.toString(); } const signature = CryptoJS.HmacSHA256(toSign, this.apiSecret).toString(); - params["signature"] = signature; - return this.sendById({ + payload.signature = signature; + return this.request({ method: "login", - params, + params: payload, }); } - async makeRequest(params: { method: string; params?: any }): Promise { - return (await this.sendById(params)) as T; + async makeListRequest(requestParams: { method: string; params?: any, responseCount?: number }): Promise { + return (await this.requestList(requestParams)) as T[]; + } + + async makeRequest(requestParams: { method: string; params?: any }): Promise { + return (await this.request(requestParams)) as T; } } diff --git a/lib/websocket/clientBase.ts b/lib/websocket/clientBase.ts index f5afa7f..3fe2e31 100644 --- a/lib/websocket/clientBase.ts +++ b/lib/websocket/clientBase.ts @@ -1,25 +1,27 @@ -import WebSocket from "ws"; import { EventEmitter } from "events"; -import { - CryptomarketAPIException, - //@ts-ignore -} from "../exceptions"; +import WebSocket from "ws"; +import { ResponsePromiseFactory } from "./ResponsePromiseFactory"; +import { WSResponse } from "./WSResponse"; export class WSClientBase { private uri: string; private subscriptionKeys: { [x: string]: any }; + private multiResponsePromiseFactory: ResponsePromiseFactory; private nextId: number; private pingTimeout: NodeJS.Timeout; + private requestTimeoutMs?: number protected ws: WebSocket; protected emitter: EventEmitter; - constructor(uri: string, subscriptionKeys = {}) { + constructor(uri: string, subscriptionKeys: { [x: string]: any } = {}, requestTimeoutMs?: number) { this.uri = uri; + this.requestTimeoutMs = requestTimeoutMs; this.subscriptionKeys = subscriptionKeys; this.emitter = new EventEmitter(); this.nextId = 1; this.ws = new WebSocket(null); - this.pingTimeout = setTimeout(() => {}); + this.pingTimeout = setTimeout(() => { }); + this.multiResponsePromiseFactory = new ResponsePromiseFactory(this.emitter); } protected getNextId(): number { @@ -36,6 +38,13 @@ export class WSClientBase { }, 30_000 + 1_000); } + /** + * Connects the client with the server + * + * Initializes the internal websocket, this method does not authenticate the client so it does not permit user related calls + * + * @returns A prommise that resolves after the connection + */ public async connect(): Promise { return new Promise((resolve) => { if (this.ws.readyState === this.ws.OPEN) this.ws.close(); @@ -56,6 +65,11 @@ export class WSClientBase { }); } + /** + * Close the client connection with the server + * + * @returns A promise that resolve after the closure + */ public close(): Promise { const promise = new Promise((resolve) => { this.ws.once("close", () => { @@ -77,68 +91,60 @@ export class WSClientBase { }): Promise { const { key } = this.subscriptionKeys[method]; this.emitter.on(key, callback); - return this.sendById({ method, params }); + return this.request({ method, params }); } - protected async sendUnsubscription({ - method, - params = {}, - }: { - method: any; - params?: {}; - }): Promise { - const { key } = this.subscriptionKeys[method]; - this.emitter.removeAllListeners(key); - return ((await this.sendById({ method, params })) as { result: Boolean }) - .result; + protected async sendUnsubscription({ method, params = {}, }: { method: any; params?: {}; }): Promise { + const { key } = this.subscriptionKeys[method] + this.emitter.removeAllListeners(key) + const response = await this.request({ method, params }); + return response as Boolean; } - protected sendById({ - method, - params = {}, - }: { - method: any; - params?: {}; - }): Promise { - const id = this.getNextId(); - const emitter = this.emitter; - const promise = new Promise(function (resolve, reject) { - emitter.once(id.toString(), function (response) { - if ("error" in response) { - reject(new CryptomarketAPIException(response)); - return; - } - resolve(response.result); - }); - }); + protected request({ method, params = {} }: { method: any; params?: {}; }): Promise { + const { id, promise } = this.multiResponsePromiseFactory.newOneResponsePromise(); const payload = { method, params, id }; this.ws.send(JSON.stringify(payload)); - return promise; + return withTimeout(this.requestTimeoutMs, promise); + } + protected requestList({ method, params = {}, responseCount = 1 }: { method: any; params?: {}; responseCount?: number; }): Promise { + const { id, promise } = this.multiResponsePromiseFactory.newMultiResponsePromise(responseCount); + const payload = { method, params, id }; + this.ws.send(JSON.stringify(payload)); + return withTimeout(this.requestTimeoutMs, promise); } protected handle({ msgJson }: { msgJson: string }): void { const message = JSON.parse(msgJson); - if ("method" in message) { - this.handleNotification(message); - } else if ("id" in message) { + if ("id" in message) { this.handleResponse(message); + } else if ("method" in message) { + this.handleNotification(message); } } - protected handleNotification({ - method, - params, - }: { - method: string; - params: any; - }): void { + protected handleNotification({ method, params }: { method: string; params: any; }): void { const { key, type } = this.subscriptionKeys[method]; this.emitter.emit(key, params, type); } - protected handleResponse(response: { [x: string]: any }): void { + protected handleResponse(response: WSResponse): void { const id = response["id"]; if (id === null) return; this.emitter.emit(id, response); } } + +const withTimeout = (millis: number | undefined, promise: any) => { + if (millis === undefined || millis === null) { + return promise + } + let timeout: NodeJS.Timeout; + const timeoutPromise = new Promise((resolve, reject) => timeout = setTimeout(() => reject(new Error(`Timed out after ${millis} ms.`)), millis)) + return Promise.race([promise, timeoutPromise]) + .finally(() => { + if (timeout) { + clearTimeout(timeout) + } + }) +}; \ No newline at end of file diff --git a/lib/websocket/marketDataClient.ts b/lib/websocket/marketDataClient.ts index f4d6c2c..e1e2556 100644 --- a/lib/websocket/marketDataClient.ts +++ b/lib/websocket/marketDataClient.ts @@ -4,6 +4,7 @@ import { NOTIFICATION_TYPE, ORDER_BOOK_SPEED, PERIOD, + PRICE_RATE_SPEED, TICKER_SPEED, } from "../constants"; import { CryptomarketAPIException } from "../exceptions"; @@ -11,18 +12,23 @@ import { Candle, MiniTicker, OrderBookTop, + PriceRate, Ticker, Trade, + WSCandle, WSOrderBook, + WSTicker, + WSTrade, } from "../models"; import { WSClientBase } from "./clientBase"; /** * MarketDataClient connects via websocket to cryptomarket to get market information of the exchange. + * @param requestTimeoutMs Timeout time for subscription and unsubscription requests to the server. No timeout by default. */ export class MarketDataClient extends WSClientBase { - constructor() { - super("wss://api.exchange.cryptomkt.com/api/3/ws/public"); + constructor(requestTimeoutMs?: number) { + super("wss://api.exchange.cryptomkt.com/api/3/ws/public", {}, requestTimeoutMs); } protected override handle({ msgJson }: { msgJson: string }): void { @@ -86,15 +92,20 @@ export class MarketDataClient extends WSClientBase { callback, params, withDefaultSymbols = false, + withDefaultCurrencies = false, }: { channel: string; callback: any; params: any; withDefaultSymbols?: boolean; + withDefaultCurrencies?: boolean; }): Promise { if (withDefaultSymbols && !params.symbols) { params.symbols = ["*"]; } + if (withDefaultCurrencies && !params.currencies) { + params.currencies = ["*"]; + } const { subscriptions } = (await this.sendChanneledSubscription({ channel, callback, @@ -125,17 +136,17 @@ export class MarketDataClient extends WSClientBase { * @param {number} params.limit Number of historical entries returned in the first feed. Min is 0. Max is 1000. Default is 0 * @returns a promise that resolves when subscribed with a list of the successfully subscribed symbols */ - async subscribeToTrades({ + subscribeToTrades({ callback, params, }: { callback: ( - notification: { [x: string]: Trade[] }, + notification: { [x: string]: WSTrade[] }, type: NOTIFICATION_TYPE ) => any; params: { symbols: string[]; limit?: number }; }): Promise { - return await this.makeSubscription({ + return this.makeSubscription({ channel: "trades", callback, params, @@ -162,17 +173,17 @@ export class MarketDataClient extends WSClientBase { * @param {number} params.limit Number of historical entries returned in the first feed. Min is 0. Max is 1000. Default is 0 * @return A promise that resolves when subscribed with a list of the successfully subscribed symbols */ - async subscribeToCandles({ + subscribeToCandles({ callback, params: { period, ...params }, }: { callback: ( - notification: { [x: string]: Candle[] }, + notification: { [x: string]: WSCandle[] }, type: NOTIFICATION_TYPE ) => any; params: { period: PERIOD; symbols: string[]; limit?: number }; }): Promise { - return await this.makeSubscription({ + return this.makeSubscription({ channel: `candles/${period}`, callback, params, @@ -195,17 +206,17 @@ export class MarketDataClient extends WSClientBase { * @param {string[]} [params.symbols] Optional. A list of symbol ids * @return A promise that resolves when subscribed with a list of the successfully subscribed symbols */ - async subscribeToMiniTicker({ + subscribeToMiniTicker({ callback, params: { speed, ...params }, }: { callback: ( - notification: { [x: string]: MiniTicker[] }, + notification: { [x: string]: MiniTicker }, type: NOTIFICATION_TYPE ) => any; params: { speed: TICKER_SPEED; symbols?: string[] }; }): Promise { - return await this.makeSubscription({ + return this.makeSubscription({ channel: `ticker/price/${speed}`, callback, params, @@ -229,7 +240,7 @@ export class MarketDataClient extends WSClientBase { * @param {string[]} [params.symbols] Optional. A list of symbol ids * @return A promise that resolves when subscribed with a list of the successfully subscribed symbols */ - async subscribeToMiniTickerInBatches({ + subscribeToMiniTickerInBatches({ callback, params: { speed, ...params }, }: { @@ -239,7 +250,7 @@ export class MarketDataClient extends WSClientBase { ) => any; params: { speed: TICKER_SPEED; symbols?: string[] }; }): Promise { - return await this.makeSubscription({ + return this.makeSubscription({ channel: `ticker/price/${speed}/batch`, callback, params, @@ -260,20 +271,20 @@ export class MarketDataClient extends WSClientBase { * * @param {function} callback a function that recieves notifications as a dict of tickers indexed by symbol id, and the type of notification (only DATA) * @param {TICKER_SPEED} params.speed The speed of the feed. '1s' or '3s' - * @param {string[]} [param.ssymbols] Optional. A list of symbol ids + * @param {string[]} [param.symbols] Optional. A list of symbol ids * @return A promise that resolves when subscribed with a list of the successfully subscribed symbols */ - async subscribeToTicker({ + subscribeToTicker({ callback, params: { speed, ...params }, }: { callback: ( - notification: { [x: string]: Ticker[] }, + notification: { [x: string]: WSTicker }, type: NOTIFICATION_TYPE ) => any; params: { speed: TICKER_SPEED; symbols?: string[] }; }): Promise { - return await this.makeSubscription({ + return this.makeSubscription({ channel: `ticker/${speed}`, callback, params, @@ -297,7 +308,7 @@ export class MarketDataClient extends WSClientBase { * @param {string[]} [params.symbols] Optional. A list of symbol ids * @return A promise that resolves when subscribed with a list of the successfully subscribed symbols */ - async subscribeToTickerInBatches({ + subscribeToTickerInBatches({ callback, params: { speed, ...params }, }: { @@ -307,7 +318,7 @@ export class MarketDataClient extends WSClientBase { ) => any; params: { speed: TICKER_SPEED; symbols?: string[] }; }): Promise { - return await this.makeSubscription({ + return this.makeSubscription({ channel: `ticker/${speed}/batch`, callback, params, @@ -330,17 +341,17 @@ export class MarketDataClient extends WSClientBase { * @param {string[]} [params.symbols] Optional. A list of symbol ids * @return A promise that resolves when subscribed with a list of the successfully subscribed symbols */ - async subscribeToFullOrderBook({ + subscribeToFullOrderBook({ callback, params, }: { callback: ( - notification: { [x: string]: WSOrderBook[] }, + notification: { [x: string]: WSOrderBook }, type: NOTIFICATION_TYPE ) => any; params: { symbols?: string[] }; }): Promise { - return await this.makeSubscription({ + return this.makeSubscription({ channel: "orderbook/full", callback, params, @@ -364,12 +375,12 @@ export class MarketDataClient extends WSClientBase { * @param {string[]} [params.symbols] Optional. A list of symbol ids * @return A promise that resolves when subscribed with a list of the successfully subscribed symbols */ - async subscribeToPartialOrderBook({ + subscribeToPartialOrderBook({ callback, params: { speed, depth, ...params }, }: { callback: ( - notification: { [x: string]: WSOrderBook[] }, + notification: { [x: string]: WSOrderBook }, type: NOTIFICATION_TYPE ) => any; params: { @@ -378,7 +389,7 @@ export class MarketDataClient extends WSClientBase { symbols?: string[]; }; }): Promise { - return await this.makeSubscription({ + return this.makeSubscription({ channel: `orderbook/${depth}/${speed}`, callback, params, @@ -401,7 +412,7 @@ export class MarketDataClient extends WSClientBase { * @param {string[]} [params.symbols] Optional. A list of symbol ids * @return A promise that resolves when subscribed with a list of the successfully subscribed symbols */ - async subscribeToPartialOrderBookInBatches({ + subscribeToPartialOrderBookInBatches({ callback, params: { speed, depth, ...params }, }: { @@ -415,7 +426,7 @@ export class MarketDataClient extends WSClientBase { symbols?: string[]; }; }): Promise { - return await this.makeSubscription({ + return this.makeSubscription({ channel: `orderbook/${depth}/${speed}/batch`, callback, params, @@ -437,7 +448,7 @@ export class MarketDataClient extends WSClientBase { * @param {string[]} [params.symbols] Optional. A list of symbol ids * @return A promise that resolves when subscribed with a list of the successfully subscribed symbols */ - async subscribeToTopOfOrderBook({ + subscribeToTopOfOrderBook({ callback, params: { speed, ...params }, }: { @@ -450,7 +461,7 @@ export class MarketDataClient extends WSClientBase { symbols?: string[]; }; }): Promise { - return await this.makeSubscription({ + return this.makeSubscription({ channel: `orderbook/top/${speed}`, callback, params, @@ -472,7 +483,7 @@ export class MarketDataClient extends WSClientBase { * @param {string[]} [params.symbols] Optional. A list of symbol ids * @return A promise that resolves when subscribed with a list of the successfully subscribed symbols */ - async subscribeToTopOfOrderBookInBatches({ + subscribeToTopOfOrderBookInBatches({ callback, params: { speed, ...params }, }: { @@ -485,11 +496,85 @@ export class MarketDataClient extends WSClientBase { symbols?: string[]; }; }): Promise { - return await this.makeSubscription({ + return this.makeSubscription({ channel: `orderbook/top/${speed}/batch`, callback, params, withDefaultSymbols: true, }); } + + /** + * subscribe to a feed of price rates + * + * subscription is for all currencies or for the specified currencies + * + * normal subscription have one update message per currency + * + * https://api.exchange.cryptomkt.com/#subscribe-to-price-rates + * + * @param {function} callback recieves a feed of price rates as a map of them, indexed by currency id, and the type of notification, only DATA + * @param {PRICE_RATE_SPEED} speed The speed of the feed. '1s' or '3s' + * @param {string} target_currency quote currency of the rate + * @param {string} currencies Optional. a list of base currencies to get rates. If omitted, subscribe to all currencies + * @return A promise that resolves when subscribed with a list of the successfully subscribed currencies + */ + subscribeToPriceRates({ + callback, + params: { speed, ...params }, + }: { + callback: ( + notification: { [x: string]: PriceRate }, + type: NOTIFICATION_TYPE + ) => any; + params: { + speed: PRICE_RATE_SPEED; + target_currency: string, + currencies?: string[]; + }; + }): Promise { + return this.makeSubscription({ + channel: `price/rate/${speed}`, + callback, + params, + withDefaultCurrencies: true, + }); + } + + /** + * subscribe to a feed of price rates + * + * subscription is for all currencies or for the specified currencies + * + * batch subscriptions have a joined update for all currencies + * + * https://api.exchange.cryptomkt.com/#subscribe-to-price-rates + * + * @param {function} callback recieves a feed of price rates as a map of them, indexed by currency id, and the type of notification, only DATA + * @param {PRICE_RATE_SPEED} speed The speed of the feed. '1s' or '3s' + * @param {string} target_currency quote currency of the rate + * @param {string} currencies Optional. a list of base currencies to get rates. If omitted, subscribe to all currencies + * @return A promise that resolves when subscribed with a list of the successfully subscribed currencies + */ + subscribeToPriceRatesInBatches({ + callback, + params: { speed, ...params }, + }: { + callback: ( + notification: { [x: string]: PriceRate }, + type: NOTIFICATION_TYPE + ) => any; + params: { + speed: PRICE_RATE_SPEED; + target_currency: string, + currencies?: string[]; + }; + }): Promise { + return this.makeSubscription({ + channel: `price/rate/${speed}/batch`, + callback, + params, + withDefaultCurrencies: true, + }); + } } diff --git a/lib/websocket/tradingClient.ts b/lib/websocket/tradingClient.ts index bc0f66b..06326f1 100644 --- a/lib/websocket/tradingClient.ts +++ b/lib/websocket/tradingClient.ts @@ -4,37 +4,39 @@ import { CONTINGENCY, NOTIFICATION_TYPE, ORDER_TYPE, + SUBSCRIPTION_MODE, TIME_IN_FORCE, } from "../constants"; +const reportsKey = "reports"; +const balanceKey = "balance"; /** * TradingClient connects via websocket to cryptomarket to enable the user to manage orders. uses SHA256 as auth method and authenticates on connection. */ export class TradingClient extends AuthClient { /** - * + * Creates a new spot trading websocket client. It connects and authenticates with the client with the method {@link connect()}. * @param apiKey public API key * @param apiSecret secret API key * @param window Maximum difference between the send of the request and the moment of request processing in milliseconds. + * @param requestTimeoutMs Timeout time for requests to the server. No timeout by default */ - constructor(apiKey: string, apiSecret: string, window: number | null = null) { + constructor(apiKey: string, apiSecret: string, window?: number, requestTimeoutMs?: number) { super( "wss://api.exchange.cryptomkt.com/api/3/ws/trading", apiKey, apiSecret, window, + requestTimeoutMs, { // reports - spot_subscribe: { - key: "reports", - type: NOTIFICATION_TYPE.COMMAND, - }, - spot_unsubscribe: { - key: "reports", - type: NOTIFICATION_TYPE.COMMAND, - }, - spot_order: { key: "reports", type: NOTIFICATION_TYPE.UPDATE }, - spot_orders: { key: "reports", type: NOTIFICATION_TYPE.SNAPSHOT }, + spot_subscribe: { key: reportsKey, type: NOTIFICATION_TYPE.COMMAND }, + spot_unsubscribe: { key: reportsKey, type: NOTIFICATION_TYPE.COMMAND }, + spot_order: { key: reportsKey, type: NOTIFICATION_TYPE.UPDATE }, + spot_orders: { key: reportsKey, type: NOTIFICATION_TYPE.SNAPSHOT }, + spot_balance_subscribe: { key: balanceKey, type: NOTIFICATION_TYPE.COMMAND }, + spot_balance_unsubscribe: { key: balanceKey, type: NOTIFICATION_TYPE.COMMAND }, + spot_balance: { key: balanceKey, type: NOTIFICATION_TYPE.SNAPSHOT }, } ); } @@ -132,16 +134,17 @@ export class TradingClient extends AuthClient { * @param {string} params.order_list_id Order list identifier. If omitted, it will be generated by the system upon order list creation. Must be equal to client_order_id of the first order in the request. * @param {string} params.contingency_type Order list type. * @param {OrderRequest[]} params.orders Orders in the list. - * @return A promise that resolves with a list of reports of the created orders + * @return A promise that resolves with a list all reports created */ async createNewSpotOrderList(params: { - oder_list_id: string; + order_list_id: string; contingency_type: CONTINGENCY; orders: OrderRequest[]; - }): Promise { - return this.makeRequest({ + }): Promise { + return this.makeListRequest({ method: "spot_new_order_list", params, + responseCount: params.orders.length, }); } @@ -290,4 +293,49 @@ export class TradingClient extends AuthClient { async unsubscribeToReports(): Promise { return this.sendUnsubscription({ method: "spot_unsubscribe" }); } + + /** + * subscribe to a feed of the user's spot balances + * + * only non-zero values are present + * + * https://api.exchange.cryptomkt.com/#subscribe-to-spot-balances + * + * @param {function} callback A function that recieves notifications with a list of balances + * @param {string} mode Either 'updates' or 'batches'. Update messages arrive after an update. Batch messages arrive at equal intervals after a first update + * @return {Promise} A Promise of the subscription result. True if subscribed + */ + async subscribeToSpotBalance( + callback: (notification: Balance[]) => any, + mode: SUBSCRIPTION_MODE, + ): Promise { + return ( + (await this.sendSubscription({ + method: "spot_balance_subscribe", + callback: (notification: any, type) => { + callback(notification as Balance[]); + }, + params: { mode }, + })) as { + result: boolean; + } + ).result; + } + + + /** + * stop recieveing the feed of balances + * + * https://api.exchange.cryptomkt.com/#subscribe-to-spot-balances + * + * @return {Promise} A Promise of the unsubscription result. True if unsubscribed + */ + unsubscribeToSpotBalance(): Promise { + return this.sendUnsubscription({ + method: "spot_balance_unsubscribe", + params: { + mode: SUBSCRIPTION_MODE.UPDATES + } + }); + } } diff --git a/lib/websocket/walletClient.ts b/lib/websocket/walletClient.ts index 06806a3..4923470 100644 --- a/lib/websocket/walletClient.ts +++ b/lib/websocket/walletClient.ts @@ -11,9 +11,10 @@ import { /** * WalletClient connects via websocket to cryptomarket to get wallet information of the user. uses SHA256 as auth method and authenticates on connection. + * @param requestTimeoutMs Timeout time for requests to the server. No timeout by default */ export class WalletClient extends AuthClient { - constructor(apiKey: string, apiSecret: string, window: number | null = null) { + constructor(apiKey: string, apiSecret: string, window?: number, requestTimeoutMs?: number) { const transactionKey = "transaction"; const balanceKey = "balance"; super( @@ -21,37 +22,17 @@ export class WalletClient extends AuthClient { apiKey, apiSecret, window, + requestTimeoutMs, { // transaction - subscribe_transactions: { - key: transactionKey, - type: NOTIFICATION_TYPE.COMMAND, - }, - unsubscribe_transactions: { - key: transactionKey, - type: NOTIFICATION_TYPE.COMMAND, - }, - transaction_update: { - method: transactionKey, - type: NOTIFICATION_TYPE.UPDATE, - }, + subscribe_transactions: { key: transactionKey, type: NOTIFICATION_TYPE.COMMAND }, + unsubscribe_transactions: { key: transactionKey, type: NOTIFICATION_TYPE.COMMAND }, + transaction_update: { key: transactionKey, type: NOTIFICATION_TYPE.UPDATE }, // balance - subscribe_wallet_balances: { - key: balanceKey, - type: NOTIFICATION_TYPE.COMMAND, - }, - unsubscribe_wallet_balances: { - key: balanceKey, - type: NOTIFICATION_TYPE.COMMAND, - }, - wallet_balances: { - key: balanceKey, - type: NOTIFICATION_TYPE.SNAPSHOT, - }, - wallet_balance_update: { - key: balanceKey, - type: NOTIFICATION_TYPE.UPDATE, - }, + subscribe_wallet_balances: { key: balanceKey, type: NOTIFICATION_TYPE.COMMAND }, + unsubscribe_wallet_balances: { key: balanceKey, type: NOTIFICATION_TYPE.COMMAND }, + wallet_balances: { key: balanceKey, type: NOTIFICATION_TYPE.SNAPSHOT }, + wallet_balance_update: { key: balanceKey, type: NOTIFICATION_TYPE.UPDATE }, } ); } @@ -79,11 +60,9 @@ export class WalletClient extends AuthClient { * @param {string} currency The currency code to query the balance * @return A promise that resolves with the wallet balance of the currency */ - getWalletBalanceOfCurrency(params: { currency: string }): Promise { - return this.makeRequest({ - method: "wallet_balance", - params, - }); + async getWalletBalanceOfCurrency(currency: string): Promise { + const response = await this.makeRequest({ method: "wallet_balance", params: { currency } }); + return { available: response.available, reserved: response.reserved, currency: currency }; } /** @@ -130,9 +109,11 @@ export class WalletClient extends AuthClient { limit?: number; offset?: number; }): Promise { + const clean_params: any = { ...params } + clean_params.currencies = params.currencies?.join(", ") return this.makeRequest({ method: "get_transactions", - params, + params: clean_params }); } @@ -151,16 +132,11 @@ export class WalletClient extends AuthClient { async subscribeToTransactions( callback: (notification: Transaction, type: NOTIFICATION_TYPE) => any ): Promise { - return ( - (await this.sendSubscription({ - method: "subscribe_transactions", - callback: (notification: any, type: NOTIFICATION_TYPE) => { - callback(notification as Transaction, type); - }, - })) as { - result: boolean; - } - ).result; + const subscriptionResult = await this.sendSubscription({ + method: "subscribe_transactions", + callback: (notification: any, type: NOTIFICATION_TYPE) => callback(notification as Transaction, type), + }); + return (subscriptionResult as { result: boolean }).result; } /** @@ -177,7 +153,7 @@ export class WalletClient extends AuthClient { } /** - * Subscribe to a feed of the balances of the account + * Subscribe to a feed of the balances of the account balances * * the first notification has a snapshot of the wallet. further notifications * are updates of the wallet @@ -215,7 +191,7 @@ export class WalletClient extends AuthClient { */ unsubscribeToBalance(): Promise { return this.sendUnsubscription({ - method: "ununsubscribe_wallet_balances", + method: "unsubscribe_wallet_balances", }); } } diff --git a/test/rest/spot_trading.test.ts b/test/rest/spot_trading.test.ts index 6f141aa..35d3eaa 100644 --- a/test/rest/spot_trading.test.ts +++ b/test/rest/spot_trading.test.ts @@ -1,8 +1,11 @@ import assert from "assert"; import "mocha"; import { Client } from "../../lib/client"; -import { SIDE } from "../../lib/constants"; + +import { CONTINGENCY, ORDER_TYPE, SIDE, TIME_IN_FORCE } from "../../lib/constants"; import { + SECOND, + timeout, emptyList, goodBalance, goodList, @@ -10,7 +13,7 @@ import { goodTradingCommission, listSize, } from "../test_helpers"; -const keys = require("/home/ismael/cryptomarket/keys-v3.json"); +const keys = require("/home/ismael/cryptomarket/keys.json"); describe("spot trading", () => { let client = new Client(keys.apiKey, keys.apiSecret); @@ -148,4 +151,36 @@ describe("spot trading", () => { ); }); }); + + describe("create spot order list", () => { + it("should create a list of orders", async function () { + this.timeout(0); + await timeout(3 * SECOND) + + let order_list_id = Date.now().toString(); + await client.createNewSpotOrderList({ + // order_list_id: order_list_id, + contingency_type: CONTINGENCY.ALL_OR_NONE, + orders: [ + { + symbol: 'EOSETH', + side: SIDE.SELL, + type: ORDER_TYPE.LIMIT, + time_in_force: TIME_IN_FORCE.FOK, + quantity: '0.1', + price: '1000', + // client_order_id: order_list_id + }, + { + symbol: 'EOSUSDT', + side: SIDE.SELL, + type: ORDER_TYPE.LIMIT, + time_in_force: TIME_IN_FORCE.FOK, + quantity: '0.1', + price: '1000' + } + ] + }); + }) + }) }); diff --git a/test/rest/spot_trading_history.test.ts b/test/rest/spot_trading_history.test.ts index f4d0119..8e1dd03 100644 --- a/test/rest/spot_trading_history.test.ts +++ b/test/rest/spot_trading_history.test.ts @@ -1,7 +1,7 @@ import assert from "assert"; import { Client } from "../../lib"; import { goodList, goodOrder, goodTrade } from "../test_helpers"; -const keys = require("/home/ismael/cryptomarket/keys-v3.json"); +const keys = require("/home/ismael/cryptomarket/keys.json"); import "mocha"; diff --git a/test/rest/wallet_management.test.ts b/test/rest/wallet_management.test.ts index acbd9e2..7294bd3 100644 --- a/test/rest/wallet_management.test.ts +++ b/test/rest/wallet_management.test.ts @@ -7,10 +7,11 @@ import { goodAddress, goodAmountLock, goodBalance, + goodFee, goodList, goodTransaction, } from "../test_helpers"; -const keys = require("/home/ismael/cryptomarket/keys-v3.json"); +const keys = require("/home/ismael/cryptomarket/keys.json"); describe("wallet management", () => { let client = new Client(keys.apiKey, keys.apiSecret); @@ -138,6 +139,16 @@ describe("wallet management", () => { assert(fee !== "", "not a good fee"); }); }); + describe("get estimates withdrawal fees", () => { + it("", async function () { + this.timeout(0); + let fees = await client.getEstimateWithdrawFees([ + { currency: "CRO", amount: "100" }, + { currency: "EOS", amount: "12" } + ]); + fees.forEach(fee => assert(goodFee(fee), "not good address")) + }); + }); describe("check if crypto address belongs to current account", () => { it("cro belongs", async function () { this.timeout(0); @@ -145,7 +156,7 @@ describe("wallet management", () => { let result = await client.checkIfCryptoAddressBelongsToCurrentAccount( croAddress.address ); - assert(result == true, "does not belong"); + assert(result === true, "does not belong"); }); it.skip("eos belongs", async function () { this.timeout(0); @@ -153,14 +164,14 @@ describe("wallet management", () => { let result = await client.checkIfCryptoAddressBelongsToCurrentAccount( eosAddress.address ); - assert(result == true, "does not belong"); + assert(result === true, "does not belong"); }); it("does not belong", async function () { this.timeout(0); const result = await client.checkIfCryptoAddressBelongsToCurrentAccount( "abc" ); - assert(result == false, "belong"); + assert(result === false, "belong"); }); }); describe("transfer between wallet and exchange", () => { diff --git a/test/test_helpers.ts b/test/test_helpers.ts index 6c81ad3..29bbc74 100644 --- a/test/test_helpers.ts +++ b/test/test_helpers.ts @@ -1,3 +1,5 @@ +import { Fee, Report } from "../lib/models"; + export const timeout = (ms: number) => { return new Promise((resolve) => setTimeout(resolve, ms)); }; @@ -151,6 +153,41 @@ export function goodPublicTrade(trade: any) { return goodObject(trade, ["id", "price", "qty", "side", "timestamp"]); } +// goodPublicTrade check the precence of every field in the trade dict +export function goodWSTrade(trade: any) { + return goodObject(trade, ["t", "i", "p", "q", "s"]); +} + +// goodPublicTrade check the precence of every field in the trade dict +export function goodWSCandle(candle: any) { + return goodObject(candle, ["t", "o", "c", "h", "l", "v", "q"]); +} + + +// goodPublicTrade check the precence of every field in the trade dict +export function goodWSTicker(ticker: any) { + return goodObject(ticker, ["t", "a", "A", "b", "B", "c", "o", "h", "l", "v", "q", "p", "P", "L"]); +} + + +export function goodWSOrderbook(orderbook: any) { + const goodOrderbook = goodObject(orderbook, ["t", "s", "a", "b"]); + if (!goodOrderbook) return false; + + for (const level of orderbook["a"]) { + if (!goodOrderbookLevel(level)) return false; + } + for (const level of orderbook["b"]) { + if (!goodOrderbookLevel(level)) return false; + } + return true; +} +// goodPublicTrade check the precence of every field in the trade dict +export function goodWSOrderbookTop(orderbookTop: any) { + return goodObject(orderbookTop, ["t", "a", "A", "b", "B"]); +} + + // goodOrderbookLevel check the precence of every field in the level dict export function goodOrderbookLevel(level: string | any[]) { return level.length == 2; @@ -263,6 +300,15 @@ export function goodAddress(transaction: any) { ]); } +export function goodFee(fee: Fee) { + return goodObject(fee, [ + "fee", + "networkFee", + "amount", + "currency" + ]); +} + // goodTransaction check the precence of every field in the transaction dict export function goodTransaction(transaction: { native: any }) { let good = goodObject(transaction, [ @@ -325,22 +371,22 @@ export function goodMetaTransaction(transaction: any) { ]); } -export function goodReport(report: any) { +export function goodReport(report: Report) { return goodObject(report, [ "id", - "clientOrderId", + "client_order_id", "symbol", "side", "status", "type", - "timeInForce", + "time_in_force", "quantity", + "quantity_cumulative", "price", - "cumQuantity", - // "postOnly", // does not appears in the orders in orders history - "createdAt", - "updatedAt", - "reportType", + "post_only", + "created_at", + "updated_at", + // "report_type", not present in order list reports ]); } @@ -356,4 +402,12 @@ export function goodAmountLock(report: any) { "cancel_description", "created_at", ]); -} \ No newline at end of file +} + + +export function goodPriceRate(report: any) { + return goodObject(report, [ + "t", + "r" + ]); +} diff --git a/test/websocket/market_data.test.ts b/test/websocket/market_data.test.ts index 14b9d0b..8752df5 100644 --- a/test/websocket/market_data.test.ts +++ b/test/websocket/market_data.test.ts @@ -1,20 +1,21 @@ -import assert from "assert"; -import "mocha"; import { expect } from "chai"; +import "mocha"; import { WSMarketDataClient } from "../../lib"; import { DEPTH, ORDER_BOOK_SPEED, PERIOD, + PRICE_RATE_SPEED, TICKER_SPEED, } from "../../lib/constants"; -import { SECOND, timeout } from "../test_helpers"; +import { SECOND, goodCandle, goodOrderbook, goodPriceRate, goodPublicTrade, goodTicker, goodWSCandle, goodWSOrderbook, goodWSOrderbookTop, goodWSTicker, goodWSTrade, timeout } from "../test_helpers"; +import { WSTicker } from "../../lib/models"; describe.only("websocket market data client", function () { let wsclient: WSMarketDataClient; beforeEach(() => { - wsclient = new WSMarketDataClient(); + wsclient = new WSMarketDataClient(10_000); }); afterEach(() => { @@ -22,102 +23,65 @@ describe.only("websocket market data client", function () { }); describe("subscribe to trades", function () { - it("should succeed", async function () { + it("gets a feed of trades", async function () { this.timeout(0); await wsclient.connect(); - try { - const symbols = await wsclient.subscribeToTrades({ - params: { - symbols: ["ETHBTC"], - limit: 4, - }, - callback: (trades, type): void => { - console.log(type); - for (const symbol in trades) { - console.log(""); - console.log(symbol); - console.log(trades[symbol]); - } - }, - }); - expect(symbols.length).equal(1); - await timeout(45 * SECOND); - } catch (err) { - assert.fail("should not fail: " + err); - } + const symbols = await wsclient.subscribeToTrades({ + params: { + symbols: ["ETHBTC"], + limit: 4, + }, + callback: checkGoodMapListValues(goodWSTrade), + }); + expect(symbols.length).equal(1); + await timeout(45 * SECOND); }); }); describe("subscribe to candles", function () { - it("should succeed", async function () { + it("gets a feed of candles", async function () { + // TODO: unknown state this.timeout(0); await wsclient.connect(); - try { - await wsclient.subscribeToCandles({ - callback: (candles, type) => { - for (const symbol in candles) { - console.log(symbol); - console.log(candles[symbol]); - } - }, - params: { - period: PERIOD._1_MINUTE, - symbols: ["EOSETH", "ETHBTC"], - limit: 3, - }, - }); - await timeout(3 * SECOND); - } catch (e) { - assert.fail("should not fail: " + e); - } + await wsclient.subscribeToCandles({ + callback: checkGoodMapListValues(goodWSCandle), + params: { + period: PERIOD._1_MINUTE, + symbols: ["EOSETH", "ETHBTC"], + limit: 3, + }, + }); + await timeout(3 * SECOND); }); }); describe("subscribe to mini ticker", function () { - it("should succeed", async function () { + it("gets a feed of mini tickers (with are candles)", async function () { this.timeout(0); await wsclient.connect(); - try { - await wsclient.subscribeToMiniTicker({ - callback: (miniTickers, type) => { - for (const symbol in miniTickers) { - console.log(symbol); - console.log(miniTickers[symbol]); - } - }, - params: { - speed: TICKER_SPEED._1_S, - symbols: ["EOSETH", "ETHBTC"], - }, - }); - await timeout(3 * SECOND); - } catch (e) { - assert.fail("should not fail: " + e); - } + await wsclient.subscribeToMiniTicker({ + callback: checkGoodMapValues(goodWSCandle), + params: { + speed: TICKER_SPEED._1_S, + symbols: ["EOSETH", "ETHBTC"], + }, + }); + await timeout(3 * SECOND); }); }); describe("subscribe to ticker", function () { - it("should succeed", async function () { + it("gets a feed of tickers", async function () { this.timeout(0); await wsclient.connect(); - try { - await wsclient.subscribeToTicker({ - callback: (tickers, type) => { - for (const symbol in tickers) { - console.log(symbol); - console.log(tickers[symbol]); - } - }, - params: { - speed: TICKER_SPEED._1_S, - symbols: ["EOSETH", "ETHBTC"], - }, - }); - await timeout(3 * SECOND); - } catch (e) { - assert.fail("should not fail: " + e); - } + await wsclient.subscribeToTicker({ + callback: checkGoodMapValues(goodWSTicker), + params: { + speed: TICKER_SPEED._1_S, + symbols: ["EOSETH", "ETHBTC"], + }, + }); + await timeout(3 * SECOND); }); }); @@ -125,22 +89,13 @@ describe.only("websocket market data client", function () { it("should succeed", async function () { this.timeout(0); await wsclient.connect(); - try { - await wsclient.subscribeToFullOrderBook({ - callback: (orderBooks, type) => { - for (const symbol in orderBooks) { - console.log(symbol); - console.log(orderBooks[symbol]); - } - }, - params: { - symbols: ["EOSETH", "ETHBTC"], - }, - }); - await timeout(3 * SECOND); - } catch (e) { - assert.fail("should not fail: " + e); - } + await wsclient.subscribeToFullOrderBook({ + callback: checkGoodMapValues(goodWSOrderbook), + params: { + symbols: ["EOSETH", "ETHBTC"], + }, + }); + await timeout(3 * SECOND); }); }); @@ -148,87 +103,77 @@ describe.only("websocket market data client", function () { it("should succeed", async function () { this.timeout(0); await wsclient.connect(); - try { - await wsclient.subscribeToPartialOrderBook({ - callback: (orderBooks) => { - for (const symbol in orderBooks) { - console.log(symbol); - console.log(orderBooks[symbol]); - } - }, - params: { - speed: ORDER_BOOK_SPEED._100_MS, - depth: DEPTH._5, - symbols: ["EOSETH", "ETHBTC"], - }, - }); - await timeout(3 * SECOND); - } catch (e) { - assert.fail("should not fail: " + e); - } + await wsclient.subscribeToPartialOrderBook({ + callback: checkGoodMapValues(goodWSOrderbook), + params: { + speed: ORDER_BOOK_SPEED._100_MS, + depth: DEPTH._5, + symbols: ["EOSETH", "ETHBTC"], + }, + }); + await timeout(3 * SECOND); }); }); describe("subscribe to top of orderbook", function () { it("should succeed", async function () { + // TODO: unknown state this.timeout(0); await wsclient.connect(); - try { - await wsclient.subscribeToTopOfOrderBook({ - callback: (orderBookTops) => { - for (const symbol in orderBookTops) { - console.log(symbol); - console.log(orderBookTops[symbol]); - } - }, - params: { - speed: ORDER_BOOK_SPEED._100_MS, - symbols: ["EOSETH", "ETHBTC"], - }, - }); - await timeout(3 * SECOND); - } catch (e) { - assert.fail("should not fail: " + e); - } + await wsclient.subscribeToTopOfOrderBook({ + callback: checkGoodMapValues(goodWSOrderbookTop), + params: { + speed: ORDER_BOOK_SPEED._100_MS, + symbols: ["EOSETH", "ETHBTC"], + }, + }); + await timeout(3 * SECOND); }); }); - - describe("multiple subscriptions", function () { - it("override on second call on same channel", async function () { + describe("subscribe to price rates", function () { + it("should succeed", async function () { this.timeout(0); await wsclient.connect(); - const first_set = [ - "ALGOUSDT", - "ETHARS", - "USDTTUSD", - "ETHTUSD", - "THETAUSDT", - "ATOMBTC", - "NEOETH", - "AAVEUSDT", - ]; - const second_set = [ - "YFIBTC", - "ETHUSDC", - "SOLETH", - "UNIBTC", - "SOLUST", - "BUSDUSDT", - "XRPEURS", - "EURSDAI", - "BTCEURS", - "LUNAUSDT", - "MKRETH", - ]; - wsclient.subscribeToTicker({ - callback: () => console.log("first callbackk"), - params: { speed: TICKER_SPEED._1_S, symbols: first_set }, + await wsclient.subscribeToPriceRates({ + callback: checkGoodMapValues(goodPriceRate), + params: { + speed: PRICE_RATE_SPEED._3_S, + target_currency: "BTC", + currencies: ["EOS", "ETH", "CRO"], + }, }); - wsclient.subscribeToTicker({ - callback: () => console.log("second callback"), - params: { speed: TICKER_SPEED._1_S, symbols: second_set }, + await timeout(3 * SECOND); + }); + }); + describe("subscribe to price rates in batches", function () { + it("gets a feed of price rates", async function () { + this.timeout(0); + await wsclient.connect(); + await wsclient.subscribeToPriceRatesInBatches({ + callback: checkGoodMapValues(goodPriceRate), + params: { + speed: PRICE_RATE_SPEED._3_S, + target_currency: "BTC", + currencies: ["EOS", "ETH", "CRO"], + }, }); - await timeout(20 * SECOND); + await timeout(3 * SECOND); }); }); + + function checkGoodMapListValues(checkFn: (valueToCheck: T) => Boolean): (notification: { [x: string]: T[]; }, type: any) => any { + return (entries, _) => { + for (let key in entries) { + const allGood = entries[key].map(checkFn).every(Boolean) + expect(allGood).to.be.true; + } + }; + } + function checkGoodMapValues(checkFn: (valueToCheck: T) => Boolean): (notification: { [x: string]: T; }, type: any) => any { + return (entries, _) => { + for (let key in entries) { + expect(checkFn(entries[key])).to.be.true; + } + }; + } }); diff --git a/test/websocket/response_promise_factory.test.ts b/test/websocket/response_promise_factory.test.ts new file mode 100644 index 0000000..cf2d329 --- /dev/null +++ b/test/websocket/response_promise_factory.test.ts @@ -0,0 +1,60 @@ +import { assert, expect } from "chai"; +import { ResponsePromiseFactory } from "../../lib/websocket/ResponsePromiseFactory"; +import { EventEmitter } from "ws"; + +const withTimeout = (millis: number | undefined, promise: any) => { + let timeout: NodeJS.Timeout | undefined; + const timeoutPromise = new Promise((resolve, reject) => timeout = setTimeout(() => reject(new Error(`Timed out after ${millis} ms.`)), millis)) + return Promise.race([promise, timeoutPromise]) + .finally(() => { + if (timeout) { + clearTimeout(timeout) + } + }) +}; + + +describe("reesponse promise", function () { + let responsePromiseFactory: ResponsePromiseFactory + const emmiter: EventEmitter = new EventEmitter(); + beforeEach(() => { + responsePromiseFactory = new ResponsePromiseFactory(emmiter) + }); + + describe("one event promise", () => { + it("should return after await", async () => { + const { id, promise } = responsePromiseFactory.newMultiResponsePromise(1) + responsePromiseFactory.emit(id.toString(), { aValue: "first value" }) + let result = await promise + assert(result.length === 1) + }) + }) + describe("two event promise", () => { + it("should return after await", async () => { + const { id, promise } = responsePromiseFactory.newMultiResponsePromise(2) + responsePromiseFactory.emit(id.toString(), { aValue: "first value" }) + responsePromiseFactory.emit(id.toString(), { aValue: "second value" }) + let result = await promise + assert(result.length === 2) + }) + it("does not consider third call", async () => { + const { id, promise } = responsePromiseFactory.newMultiResponsePromise(2) + responsePromiseFactory.emit(id.toString(), { aValue: "first value" }) + responsePromiseFactory.emit(id.toString(), { aValue: "second value" }) + responsePromiseFactory.emit(id.toString(), { aValue: "third value should not appear" }) + let result = await promise + assert(result.length === 2) + }) + it("does not return while waiting second call", async () => { + const { id, promise } = responsePromiseFactory.newMultiResponsePromise(2) + responsePromiseFactory.emit(id.toString(), { aValue: "first value" }) + try { + await withTimeout(1000, promise) + expect.fail("should throw timeout, as hangs waiting second value") + } + catch { + // good, it fails + } + }) + }) +}); diff --git a/test/websocket/trading.test.ts b/test/websocket/trading.test.ts index 13585ea..0ac9b4a 100644 --- a/test/websocket/trading.test.ts +++ b/test/websocket/trading.test.ts @@ -1,9 +1,9 @@ -const keys = require("/home/ismael/cryptomarket/keys-v3.json"); -import { fail } from "assert"; -import "mocha"; +const keys = require("/home/ismael/cryptomarket/keys.json"); import { expect } from "chai"; +import "mocha"; import { WSTradingClient } from "../../lib"; -import { SECOND, timeout } from "../test_helpers"; +import { CONTINGENCY, ORDER_STATUS, REPORT_STATUS, SIDE, TIME_IN_FORCE } from "../../lib/constants"; +import { SECOND, goodBalance, goodReport, goodTradingCommission, timeout } from "../test_helpers"; describe("TradingClient", () => { let wsclient: WSTradingClient; @@ -16,122 +16,142 @@ describe("TradingClient", () => { }); describe("get trading balance", function () { - it("list", async function () { + it("gets a balance list", async function () { this.timeout(0); - try { - await wsclient.connect(); - const balances = await wsclient.getSpotTradingBalances(); - console.log(balances); - expect(balances.length).to.be.greaterThanOrEqual(1); - } catch (err) { - fail("should not fail. " + err); - } + await wsclient.connect(); + const balances = await wsclient.getSpotTradingBalances(); + expect(balances.length).to.be.greaterThanOrEqual(1); + const allGood = balances.map(goodBalance).every(Boolean) + expect(allGood).to.be.true }); - it("only one", async function () { + it("gets only one", async function () { this.timeout(0); - try { - await wsclient.connect(); - const balance = await wsclient.getSpotTradingBalanceOfCurrency({ - currency: "EOS", - }); - console.log(balance); - } catch (err) { - fail("should not fail. " + err); - } + await wsclient.connect(); + const balance = await wsclient.getSpotTradingBalanceOfCurrency({ + currency: "EOS", + }); + expect(goodBalance(balance)).to.be.true }); }); describe("order life cycle", function () { - it("should succeed", async function () { + it("creates, replace and cancels an order", async function () { this.timeout(0); - try { - await wsclient.connect(); - let clientOrderID = Math.floor(Date.now() / 1000).toString(); + await wsclient.connect(); + const clientOrderID = newID(); + + let orderReport = await wsclient.createSpotOrder({ + client_order_id: clientOrderID, + symbol: "EOSETH", + side: "sell", + quantity: "0.01", + price: "1000", + }); + + expect(goodReport(orderReport)).to.be.true + await expectActiveOrderWithID(clientOrderID); + + const newClientOrderID = clientOrderID + "new"; + orderReport = await wsclient.replaceSpotOrder({ + client_order_id: clientOrderID, + new_client_order_id: newClientOrderID, + quantity: "0.01", + price: "2000", + }); + + expect(goodReport(orderReport)).to.be.true + await expectActiveOrderWithID(newClientOrderID); + + orderReport = await wsclient.cancelSpotOrder(newClientOrderID); + + expect(goodReport(orderReport)).to.be.true + expect(orderReport.status).to.be.equal(ORDER_STATUS.CANCELED) - let orderReport = await wsclient.createSpotOrder({ - client_order_id: clientOrderID, - symbol: "EOSETH", - side: "sell", - quantity: "0.01", - price: "1000", - }); - console.log("after create order"); - console.log(orderReport); - let activeOrders = await wsclient.getActiveSpotOrders(); - let present = false; - for (orderReport of activeOrders) { - if (orderReport.client_order_id === clientOrderID) present = true; - } - if (!present) fail("order is not present"); - - let newClientOrderID = clientOrderID + "new"; - orderReport = await wsclient.replaceSpotOrder({ - client_order_id: clientOrderID, - new_client_order_id: newClientOrderID, - quantity: "0.01", - price: "2000", - }); - console.log("after replace"); - console.log(orderReport); - orderReport = await wsclient.cancelSpotOrder(newClientOrderID); - console.log("after cancel"); - console.log(orderReport); - } catch (err) { - console.log(err); - fail("err"); - } }); }); describe("cancel all orders", function () { - it("should succeed", async function () { + it("cancel all active orders of the client", async function () { this.timeout(0); await wsclient.connect(); - try { - await wsclient.cancelSpotOrders(); - const list = [1, 2, 3, 4, 5]; - list.map(async () => { - await wsclient.createSpotOrder({ - symbol: "EOSETH", - side: "sell", - quantity: "0.01", - price: "1000", - }); + await wsclient.cancelSpotOrders(); + for (let i = 1; i <= 5; i++) { + await wsclient.createSpotOrder({ + symbol: "EOSETH", + side: "sell", + quantity: "0.01", + price: "1000", }); - await timeout(5 * SECOND); - const activeList = await wsclient.getActiveSpotOrders(); - const canceledOrders = await wsclient.cancelSpotOrders(); - expect(canceledOrders.length).to.equal(activeList.length); - } catch (err) { - fail("should not fail " + err); + await timeout(1 * SECOND); } + const activeList = await wsclient.getActiveSpotOrders(); + const canceledOrders = await wsclient.cancelSpotOrders(); + expect(canceledOrders.length).to.equal(activeList.length); }); }); describe("get trading commission", function () { - it("list", async function () { + it("gets a commission list", async function () { this.timeout(0); - try { - await wsclient.connect(); - const commissions = await wsclient.getSpotCommissions(); - console.log(commissions); - expect(commissions.length).to.be.greaterThanOrEqual(1); - } catch (err) { - fail("should not fail. " + err); - } + await wsclient.connect(); + const commissions = await wsclient.getSpotCommissions(); + expect(commissions.length).to.be.greaterThanOrEqual(1); + const allGood = commissions.map(goodTradingCommission).every(Boolean) + expect(allGood).to.be.true }); - it("only one", async function () { + it("gets only one", async function () { this.timeout(0); - try { - await wsclient.connect(); - const commission = await wsclient.getSpotCommissionOfSymbol({ - symbol: "EOSETH", - }); - console.log(commission); - } catch (err) { - fail("should not fail. " + err); - } + await wsclient.connect(); + const commission = await wsclient.getSpotCommissionOfSymbol({ + symbol: "EOSETH", + }); + expect(goodTradingCommission(commission)).to.be.true }); }); + describe("create spot order list", function () { + it("makes a successfull request", async function () { + this.timeout(0); + const firstOrderID = newID() + await wsclient.connect(); + const reports = await wsclient.createNewSpotOrderList({ + order_list_id: firstOrderID, + contingency_type: CONTINGENCY.ALL_OR_NONE, + orders: [ + { + client_order_id: firstOrderID, + symbol: "EOSETH", + side: SIDE.SELL, + time_in_force: TIME_IN_FORCE.FOK, + quantity: "0.01", + price: "100000" + }, + { + client_order_id: firstOrderID + "2", + symbol: "EOSBTC", + side: SIDE.SELL, + time_in_force: TIME_IN_FORCE.FOK, + quantity: "0.01", + price: "100000" + } + ] + }); + expect(reports.length).to.be.equal(2) + let allGood = reports.map(goodReport).every(Boolean) + expect(allGood).to.be.true + allGood = reports.map(report => report.status === REPORT_STATUS.EXPIRED).every(Boolean) + expect(allGood).to.be.true + }); + }); + + async function expectActiveOrderWithID(clientOrderID: string) { + const activeOrders = await wsclient.getActiveSpotOrders(); + const present = activeOrders.filter(order => order.client_order_id === clientOrderID).length === 1; + if (!present) expect.fail("order is not present"); + } + + function newID() { + return Math.floor(Date.now() / 1000).toString(); + } }); + diff --git a/test/websocket/trading_subscriptions.test.ts b/test/websocket/trading_subscriptions.test.ts index e0d0e5f..2d1fd59 100644 --- a/test/websocket/trading_subscriptions.test.ts +++ b/test/websocket/trading_subscriptions.test.ts @@ -1,13 +1,13 @@ -const keys = require("/home/ismael/cryptomarket/keys-v3.json"); -import { fail } from "assert"; +const keys = require("/home/ismael/cryptomarket/keys.json"); import { expect } from "chai"; import { WSTradingClient } from "../../lib"; -import { SECOND, timeout } from "../test_helpers"; +import { SUBSCRIPTION_MODE } from "../../lib/constants"; +import { SECOND, goodBalance, goodReport, timeout } from "../test_helpers"; describe("tradingClient subscriptions", function () { let wsclient: WSTradingClient; beforeEach(() => { - wsclient = new WSTradingClient(keys.apiKey, keys.apiSecret); + wsclient = new WSTradingClient(keys.apiKey, keys.apiSecret, undefined, 10_000); }); afterEach(() => { @@ -15,29 +15,51 @@ describe("tradingClient subscriptions", function () { }); describe("Subscribe to reports", function () { - it("should succeed", async function () { + it("gets a feed of order reports", async function () { this.timeout(0); await wsclient.connect(); - try { - await wsclient.subscribeToReports((notification, type) => { - console.log(notification); - }); - await timeout(3 * SECOND); - let clientOrderID = Math.floor(Date.now() / 1000).toString(); - await wsclient.createSpotOrder({ - client_order_id: clientOrderID, - symbol: "EOSETH", - side: "sell", - quantity: "0.01", - price: "1000", - }); - await timeout(3 * SECOND); - await wsclient.cancelSpotOrder(clientOrderID); - const result = await wsclient.unsubscribeToReports(); - expect(result).to.be.true; - } catch (err) { - fail("should not fail. " + err); - } + await wsclient.subscribeToReports((reports, type) => { + const allGood = reports.map(goodReport).every(Boolean) + expect(allGood).to.be.true + }); + await timeout(3 * SECOND); + const clientOrderID = newRandomID(); + await newOrderRequest(clientOrderID); + await timeout(3 * SECOND); + await wsclient.cancelSpotOrder(clientOrderID); + const unsubscriptionSuccess = await wsclient.unsubscribeToReports(); + expect(unsubscriptionSuccess).to.be.true; }); }); + describe("subscribe to spot balances", function () { + it("gets a feed of spot balances", async function () { + this.timeout(0) + await wsclient.connect() + await wsclient.subscribeToSpotBalance(balances => { + const allGood = balances.map(goodBalance).every(Boolean) + expect(allGood).to.be.true + }, SUBSCRIPTION_MODE.UPDATES) + await timeout(3 * SECOND) + const clientOrderID = newRandomID() + await newOrderRequest(clientOrderID); + await timeout(3 * SECOND); + await wsclient.cancelSpotOrder(clientOrderID); + const unsubscriptionSuccess = await wsclient.unsubscribeToSpotBalance(); + expect(unsubscriptionSuccess).to.be.true; + }) + }) + function newOrderRequest(clientOrderID: string) { + return wsclient.createSpotOrder({ + client_order_id: clientOrderID, + symbol: "EOSETH", + side: "sell", + quantity: "0.01", + price: "1000", + }); + } + + function newRandomID(): string { + return Math.floor(Date.now() / 1000).toString(); + } }); + diff --git a/test/websocket/wallet.test.ts b/test/websocket/wallet.test.ts index 3b44f7a..4c39ea1 100644 --- a/test/websocket/wallet.test.ts +++ b/test/websocket/wallet.test.ts @@ -1,59 +1,44 @@ -const keys = require("/home/ismael/cryptomarket/keys-v3.json"); -import { fail } from "assert"; -import "mocha"; +const keys = require("/home/ismael/cryptomarket/keys.json"); import { expect } from "chai"; +import "mocha"; import { WSWalletClient } from "../../lib"; +import { goodBalance, goodTransaction } from "../test_helpers"; describe("WalletClient", function () { let wsclient: WSWalletClient; beforeEach(() => { - wsclient = new WSWalletClient(keys.apiKey, keys.apiSecret); + wsclient = new WSWalletClient(keys.apiKey, keys.apiSecret, undefined, 10_000); }); afterEach(() => { wsclient.close(); }); - describe("get account balance", function () { - it("list", async function () { + describe("get wallet balance", function () { + it("gets wallet balance list", async function () { this.timeout(0); await wsclient.connect(); - try { - const balances = await wsclient.getWalletBalances(); - console.log(balances); - expect(balances.length).to.be.greaterThan(1); - } catch (err) { - fail("should not fail. " + err); - } + const balances = await wsclient.getWalletBalances(); + expect(balances.length).to.be.greaterThan(1); + const allGood = balances.map(goodBalance).every(Boolean) + expect(allGood).to.be.true }); - it("single", async function () { + it("gets only one balance", async function () { this.timeout(0); await wsclient.connect(); - try { - const balance = await wsclient.getWalletBalanceOfCurrency({ - currency: "EOS", - }); - console.log(balance); - } catch (err) { - fail("should not fail. " + err); - } + const balance = await wsclient.getWalletBalanceOfCurrency("EOS"); + expect(goodBalance(balance)).to.be.true }); }); describe("get transactions", function () { - it("should succeed", async function () { + it("gets a list of transactions", async function () { this.timeout(0); await wsclient.connect(); - try { - const transactions = await wsclient.getTransactions({ - currencies: ["EOS"], - limit: 3, - }); - console.log(transactions); - } catch (err) { - fail("should not fail. " + err); - } - wsclient.close(); + const transactions = await wsclient.getTransactions({ currencies: ["EOS"], limit: 3 }); + const allGood = transactions.map(goodTransaction).every(Boolean) + expect(allGood).to.be.true + await wsclient.close(); }); }); }); diff --git a/test/websocket/wallet_subscriptions.test.ts b/test/websocket/wallet_subscriptions.test.ts index 35622b8..0616c07 100644 --- a/test/websocket/wallet_subscriptions.test.ts +++ b/test/websocket/wallet_subscriptions.test.ts @@ -1,16 +1,14 @@ -//@ts-ignore -import { fail } from "assert"; -import { NOTIFICATION_TYPE } from "../../lib/constants"; +import { expect } from "chai"; import { Client, WSWalletClient } from "../../lib/index"; import { Balance, Transaction } from "../../lib/models"; -import { SECOND, timeout } from "../test_helpers"; -const keys = require("/home/ismael/cryptomarket/keys-v3.json"); +import { SECOND, goodBalance, goodTransaction, timeout } from "../test_helpers"; +const keys = require("/home/ismael/cryptomarket/keys.json"); describe("wallet transactions", function () { let wsclient: WSWalletClient; let restClient: any; beforeEach(() => { - wsclient = new WSWalletClient(keys.apiKey, keys.apiSecret); + wsclient = new WSWalletClient(keys.apiKey, keys.apiSecret, undefined, 10_000); restClient = new Client(keys.apiKey, keys.apiSecret); }); @@ -19,66 +17,56 @@ describe("wallet transactions", function () { }); describe("Subscribe to transactions", function () { - it("should succeed", async function () { + it("gets a feed of transactions", async function () { this.timeout(0); - try { - await wsclient.connect(); - await wsclient.subscribeToTransactions( - (notification: Transaction[], type: NOTIFICATION_TYPE) => { - console.log("transaction notification"); - console.log("type: " + type); - console.log(notification); - } - ); - await restClient.transferBetweenWalletAndExchange({ - source: "wallet", - destination: "spot", - currency: "ADA", - amount: 1, - }); - await timeout(3 * SECOND); - await restClient.transferBetweenWalletAndExchange({ - source: "spot", - destination: "wallet", - currency: "ADA", - amount: 1, - }); - await timeout(3 * SECOND); - } catch (err) { - fail("should not fail. " + err); - } + await wsclient.connect(); + await wsclient.subscribeToTransactions((transaction: Transaction, _) => { + expect(goodTransaction(transaction)).to.be.true + }); + await restClient.transferBetweenWalletAndExchange({ + source: "wallet", + destination: "spot", + currency: "USDT", + amount: 1, + }); + await timeout(3 * SECOND); + await restClient.transferBetweenWalletAndExchange({ + source: "spot", + destination: "wallet", + currency: "USDT", + amount: 1, + }); + await timeout(3 * SECOND); + const unsubscriptionSuccess = await wsclient.unsubscribeToTransactions() + expect(unsubscriptionSuccess).to.be.true }); }); describe("Subscribe to balance", function () { - it("should succeed", async function () { + it("gets a feed of wallet balances", async function () { this.timeout(0); - try { - await wsclient.connect(); - await wsclient.subscribeToBalance( - (notification: Balance[], type: NOTIFICATION_TYPE) => { - console.log("balance notification"); - console.log("type: " + type); - console.log(notification); - } - ); - await restClient.transferBetweenWalletAndExchange({ - source: "wallet", - destination: "spot", - currency: "ADA", - amount: 1, - }); - await timeout(3 * SECOND); - await restClient.transferBetweenWalletAndExchange({ - source: "spot", - destination: "wallet", - currency: "ADA", - amount: 1, - }); - await timeout(3 * SECOND); - } catch (err) { - fail("should not fail. " + err); - } + await wsclient.connect(); + await wsclient.subscribeToBalance((balances: Balance[], _) => { + const allGood = balances.map(goodBalance).every(Boolean) + expect(allGood).to.be.true + }); + await restClient.transferBetweenWalletAndExchange({ + source: "wallet", + destination: "spot", + currency: "USDT", + amount: 1, + }); + await timeout(3 * SECOND); + await restClient.transferBetweenWalletAndExchange({ + source: "spot", + destination: "wallet", + currency: "USDT", + amount: 1, + }); + await timeout(3 * SECOND); + const unsubscriptionSuccess = await wsclient.unsubscribeToBalance() + expect(unsubscriptionSuccess).to.be.true + }); }); }); From c9de800ee9f09fbf441b69da8e7cfeba8207d4f0 Mon Sep 17 00:00:00 2001 From: "T. Ismael Verdugo" Date: Tue, 6 Feb 2024 23:44:54 -0300 Subject: [PATCH 04/16] feat: adds docs to ws models --- lib/models/OrderBook.ts | 12 +++++++++ lib/models/WSCandle.ts | 21 +++++++++++++++ lib/models/WSTicker.ts | 42 ++++++++++++++++++++++++++++++ lib/models/WSTrades.ts | 15 +++++++++++ test/websocket/market_data.test.ts | 5 ++-- 5 files changed, 92 insertions(+), 3 deletions(-) diff --git a/lib/models/OrderBook.ts b/lib/models/OrderBook.ts index 366c41e..802eee8 100644 --- a/lib/models/OrderBook.ts +++ b/lib/models/OrderBook.ts @@ -1,7 +1,19 @@ export interface WSOrderBook { + /** + * timetsmap + */ t: number; + /** + * sequence number + */ s: number; + /** + * asks + */ a: OrderBookLevel[]; + /** + * bids + */ b: OrderBookLevel[]; } diff --git a/lib/models/WSCandle.ts b/lib/models/WSCandle.ts index 8766be0..46c7fad 100644 --- a/lib/models/WSCandle.ts +++ b/lib/models/WSCandle.ts @@ -1,10 +1,31 @@ export interface WSCandle { + /** + * timestamp + */ t: number; + /** + * open price + */ o: string; + /** + * close price (same as last price) + */ c: string; + /** + * high price + */ h: string; + /** + * low price + */ l: string; + /** + * base asset volume + */ v: string; + /** + * quote asset volume + */ q: string; } diff --git a/lib/models/WSTicker.ts b/lib/models/WSTicker.ts index 13c262d..655a5c9 100644 --- a/lib/models/WSTicker.ts +++ b/lib/models/WSTicker.ts @@ -1,16 +1,58 @@ export interface WSTicker { + /** + * timestamp + */ t: number; + /** + * best ask + */ a: string; + /** + * best ask quantity + */ A: string; + /** + * best bid + */ b: string; + /** + * best bid quantity + */ B: string; + /** + * close price (same as last price) + */ c: string; + /** + * open price + */ o: string; + /** + * high price + */ h: string; + /** + * low price + */ l: string; + /** + * base asset volume + */ v: string; + /** + * quote asset volume + */ q: string; + /** + * price change + */ p: string; + /** + * price change percent + */ P: string; + /** + * last trade identifier + */ L: number; } diff --git a/lib/models/WSTrades.ts b/lib/models/WSTrades.ts index 6a50c62..0d57817 100644 --- a/lib/models/WSTrades.ts +++ b/lib/models/WSTrades.ts @@ -1,9 +1,24 @@ import { SIDE } from "../constants"; export interface WSTrade { + /** + * timestamp + */ t: number; + /** + * trade identifier + */ i: number; + /** + * price + */ p: string; + /** + * quantity + */ q: string; + /** + * side + */ s: SIDE; } \ No newline at end of file diff --git a/test/websocket/market_data.test.ts b/test/websocket/market_data.test.ts index 8752df5..0f8415c 100644 --- a/test/websocket/market_data.test.ts +++ b/test/websocket/market_data.test.ts @@ -8,10 +8,9 @@ import { PRICE_RATE_SPEED, TICKER_SPEED, } from "../../lib/constants"; -import { SECOND, goodCandle, goodOrderbook, goodPriceRate, goodPublicTrade, goodTicker, goodWSCandle, goodWSOrderbook, goodWSOrderbookTop, goodWSTicker, goodWSTrade, timeout } from "../test_helpers"; -import { WSTicker } from "../../lib/models"; +import { SECOND, goodPriceRate, goodWSCandle, goodWSOrderbook, goodWSOrderbookTop, goodWSTicker, goodWSTrade, timeout } from "../test_helpers"; -describe.only("websocket market data client", function () { +describe("websocket market data client", function () { let wsclient: WSMarketDataClient; beforeEach(() => { From afa15ba79ef881972c0ccf52c90c47c545fed6de Mon Sep 17 00:00:00 2001 From: "T. Ismael Verdugo" Date: Wed, 7 Feb 2024 16:46:21 -0300 Subject: [PATCH 05/16] fix: rest requests --- lib/client.ts | 10 +++++----- lib/httpClient.ts | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/lib/client.ts b/lib/client.ts index 27e8cff..b1b8336 100644 --- a/lib/client.ts +++ b/lib/client.ts @@ -89,8 +89,8 @@ export class Client { * * @return A list of available currencies */ - getCurrencies(currencies?: string[], preferred_network?: string): Promise<{ [key: string]: Currency[] }> { - return this.get("public/currency/", { currencies }); + getCurrencies(currencies?: string[], preferred_network?: string): Promise<{ [key: string]: Currency }> { + return this.get("public/currency", { currencies, preferred_network }); } /** @@ -120,7 +120,7 @@ export class Client { * * @return A list of symbols traded on the exchange */ - getSymbols(symbols?: string[]): Promise<{ [key: string]: Symbol[] }> { + getSymbols(symbols?: string[]): Promise<{ [key: string]: Symbol }> { return this.get("public/symbol/", { symbols }); } @@ -152,7 +152,7 @@ export class Client { * * @returns An object/dict with symbols ids as keys. */ - getTickers(symbols?: string[]): Promise<{ [key: string]: Ticker[] }> { + getTickers(symbols?: string[]): Promise<{ [key: string]: Ticker }> { return this.get("public/ticker/", { symbols }); } @@ -187,7 +187,7 @@ export class Client { getPrices(params: { to: string; from?: string; - }): Promise<{ [key: string]: Price[] }> { + }): Promise<{ [key: string]: Price }> { return this.get(`public/price/rate`, params); } diff --git a/lib/httpClient.ts b/lib/httpClient.ts index 0ea4a65..c51fb65 100644 --- a/lib/httpClient.ts +++ b/lib/httpClient.ts @@ -24,6 +24,7 @@ export class HttpClient { publc: boolean = false ): Promise { const { url, opts } = this.prepareRequest(params, method, publc, endpoint); + console.log(opts) try { return await this.makeFetch(url, opts); } catch (e) { @@ -33,9 +34,12 @@ export class HttpClient { private prepareRequest(params_raw: any, method: HTTP_METHOD, publicMethod: boolean, endpoint: string): { url: URL, opts: Map } { + if (params_raw === undefined || params_raw === null) { + params_raw = {} + } let url = new URL(this.apiPath + endpoint); + this.removeNulls(params_raw); const params: [string, string][] = Object.entries(params_raw) - .filter(([k, v]) => v !== null) .map(([k, v]) => [k, String(v)]) let rawQuery = new URLSearchParams(params); rawQuery.sort(); @@ -53,17 +57,25 @@ export class HttpClient { opts.headers["Content-Type"] = "application/json"; credentialParams = JSON.stringify(params_raw) } + if (method === HTTP_METHOD.PATCH) { + opts.headers["Content-Type"] = "application/x-www-form-urlencoded"; + } // add auth header if not public endpoint if (!publicMethod) opts.headers["Authorization"] = this.hmac.buildCredential(method, url, credentialParams); // include query params to call if (method === HTTP_METHOD.GET || method === HTTP_METHOD.PUT) url.search = query; - else + else { opts.body = credentialParams; + } return { url, opts }; } + private removeNulls(params_raw: any) { + Object.keys(params_raw).forEach(key => (params_raw[key] === undefined || params_raw[key] == null) ? delete params_raw[key] : {}); + } + private async makeFetch(url: URL, opts: any): Promise { const response = await fetch(url, opts) let jsonResponse: any From 29743e220aa52661e6a391608599a33f1a31478e Mon Sep 17 00:00:00 2001 From: "T. Ismael Verdugo" Date: Wed, 7 Feb 2024 17:12:16 -0300 Subject: [PATCH 06/16] feat: get currencies preferred_netwokr param --- lib/client.ts | 12 +++++++----- lib/httpClient.ts | 1 - lib/models/Currency.ts | 1 + 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/client.ts b/lib/client.ts index b1b8336..4e39e9f 100644 --- a/lib/client.ts +++ b/lib/client.ts @@ -85,12 +85,14 @@ export class Client { * https://api.exchange.cryptomkt.com/#currencies * * @param {string[]} [currencies] Optional. A list of currencies ids - * @param [preferred_network] Optional. Code of the default network for currencies. + * @param {string} [preferred_network] Optional. Code of the default network for currencies. * * @return A list of available currencies */ - getCurrencies(currencies?: string[], preferred_network?: string): Promise<{ [key: string]: Currency }> { - return this.get("public/currency", { currencies, preferred_network }); + async getCurrencies(currencies?: string[], preferred_network?: string): Promise<{ [key: string]: Currency }> { + const response = await this.get("public/currency", { currencies, preferred_network }); + console.log(response) + return response } /** @@ -828,13 +830,13 @@ export class Client { * * Requires the "Payment information" API key Access Right. * - * https://api.exchange.cryptomkt.com/#deposit-crypto-address + * https://api.exchange.cryptomkt.com/#generate-deposit-crypto-address * * @param {string} currency currency to create a new address * * @return The created address for the currency */ - createDepositCryptoAddress(currency: string): Promise
{ + createDepositCryptoAddress(currency: string, network_code?: string): Promise
{ return this.post(`wallet/crypto/address`, { currency }); } diff --git a/lib/httpClient.ts b/lib/httpClient.ts index c51fb65..f20746e 100644 --- a/lib/httpClient.ts +++ b/lib/httpClient.ts @@ -24,7 +24,6 @@ export class HttpClient { publc: boolean = false ): Promise { const { url, opts } = this.prepareRequest(params, method, publc, endpoint); - console.log(opts) try { return await this.makeFetch(url, opts); } catch (e) { diff --git a/lib/models/Currency.ts b/lib/models/Currency.ts index 8562ac4..128128a 100644 --- a/lib/models/Currency.ts +++ b/lib/models/Currency.ts @@ -13,6 +13,7 @@ export interface Currency { account_top_order: number; qr_prefix: string; delisted: boolean; + contract_address: string; networks: Network[]; } From f2587d419b0e6378a9376570b803b59f2dc25239 Mon Sep 17 00:00:00 2001 From: "T. Ismael Verdugo" Date: Thu, 8 Feb 2024 00:07:14 -0300 Subject: [PATCH 07/16] feat: network code parameter on create deposit crypto address --- lib/client.ts | 13 +++++++++++-- lib/models/Address.ts | 1 + 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/client.ts b/lib/client.ts index 4e39e9f..bdaf1e1 100644 --- a/lib/client.ts +++ b/lib/client.ts @@ -820,7 +820,7 @@ export class Client { * * @return the currency address */ - async getDepositCryptoAddressOfCurrency(currency: string): Promise
{ + async getDepositCryptoAddress(currency: string, network_code?: string): Promise
{ const addressList = await this.get(`wallet/crypto/address`, { currency }); return addressList[0]; } @@ -828,16 +828,25 @@ export class Client { /** * Creates a new address for the currency * + * Creates a new deposit address. + * Existing addresses may still receive funds. + * For some tokens (e.g., Ethereum tokens), + * a single address is generated per base currency with additional + * identifiers which differ for each address: payment_id or public_key. + * As a result, generating a new address for such a token + * will change the current address for an entire base currency accordingly. + * * Requires the "Payment information" API key Access Right. * * https://api.exchange.cryptomkt.com/#generate-deposit-crypto-address * * @param {string} currency currency to create a new address + * @param {string} network_code Optional. network code * * @return The created address for the currency */ createDepositCryptoAddress(currency: string, network_code?: string): Promise
{ - return this.post(`wallet/crypto/address`, { currency }); + return this.post(`wallet/crypto/address`, { currency, network_code }); } /** diff --git a/lib/models/Address.ts b/lib/models/Address.ts index fab1ada..df5818c 100644 --- a/lib/models/Address.ts +++ b/lib/models/Address.ts @@ -3,4 +3,5 @@ export interface Address { currency: string; payment_id: string; public_key: string; + netwokd_code: string; } From 1db170a354101f02dfcf0bfccd01206fd00bedc1 Mon Sep 17 00:00:00 2001 From: "T. Ismael Verdugo" Date: Thu, 8 Feb 2024 13:03:12 -0300 Subject: [PATCH 08/16] feat: updates rest wallet management methods --- lib/client.ts | 27 ++++++++++++++++++--------- lib/models/Transactions.ts | 26 ++++++++------------------ test/rest/wallet_management.test.ts | 20 ++++++++++---------- 3 files changed, 36 insertions(+), 37 deletions(-) diff --git a/lib/client.ts b/lib/client.ts index bdaf1e1..ced6dc2 100644 --- a/lib/client.ts +++ b/lib/client.ts @@ -91,7 +91,6 @@ export class Client { */ async getCurrencies(currencies?: string[], preferred_network?: string): Promise<{ [key: string]: Currency }> { const response = await this.get("public/currency", { currencies, preferred_network }); - console.log(response) return response } @@ -799,9 +798,9 @@ export class Client { /** * Get the current addresses of the user * - * Requires the "Payment information" API key Access Right. + * Requires the "Payment information" API key AccesrkRight. * - * https://api.exchange.cryptomkt.com/#deposit-crypto-address + *, network_code https://api.exchange.cryptomkt.com/#deposit-crypto-address * * @return A list of currency addresses */ @@ -859,11 +858,12 @@ export class Client { * https://api.exchange.cryptomkt.com/#last-10-deposit-crypto-address * * @param {string} currency currency to get the list of addresses + * @param {string} [network_code] Optional. network code * * @return A list of addresses */ - getLast10DepositCryptoAddresses(currency: string): Promise { - return this.get(`wallet/crypto/address/recent-deposit`, { currency }); + getLast10DepositCryptoAddresses(currency: string, network_code?: string): Promise { + return this.get(`wallet/crypto/address/recent-deposit`, { currency, network_code }); } /** @@ -876,11 +876,12 @@ export class Client { * https://api.exchange.cryptomkt.com/#last-10-withdrawal-crypto-addresses * * @param {string} currency currency to get the list of addresses + * @param {string} [network_code] Optional. network code * * @return A list of addresses */ - getLast10WithdrawalCryptoAddresses(currency: string): Promise { - return this.get(`wallet/crypto/address/recent-withdraw`, { currency }); + getLast10WithdrawalCryptoAddresses(currency: string, network_code?: string): Promise { + return this.get(`wallet/crypto/address/recent-withdraw`, { currency, network_code }); } /** @@ -903,6 +904,7 @@ export class Client { * @param {string} params.currency currency code of the crypto to withdraw * @param {string} params.amount the amount to be sent to the specified address * @param {string} params.address the address identifier + * @param {string} [params.network_code] Optional. network code * @param {string} [params.payment_id] Optional. * @param {boolean} [params.include_fee] Optional. If true then the total spent amount includes fees. Default false * @param {boolean} [params.auto_commit] Optional. If false then you should commit or rollback transaction in an hour. Used in two phase commit schema. Default true @@ -916,6 +918,7 @@ Accepted values: never, optionally, required currency: string; amount: string; address: string; + network_code?: string paymend_id?: string; include_fee?: boolean; auto_commit?: boolean; @@ -1112,6 +1115,8 @@ Accepted values: wallet, spot. Must not be the same as source * @param {TRANSACTION_TYPE[]} [params.types] Optional. List of types to query. valid types are: 'DEPOSIT', 'WITHDRAW', 'TRANSFER' and 'SWAP' * @param {TRANSACTION_SUBTYPE[]} [params.subtypes] Optional. List of subtypes to query. valid subtypes are: 'UNCLASSIFIED', 'BLOCKCHAIN', 'AIRDROP', 'AFFILIATE', 'STAKING', 'BUY_CRYPTO', 'OFFCHAIN', 'FIAT', 'SUB_ACCOUNT', 'WALLET_TO_SPOT', 'SPOT_TO_WALLET', 'WALLET_TO_DERIVATIVES', 'DERIVATIVES_TO_WALLET', 'CHAIN_SWITCH_FROM', 'CHAIN_SWITCH_TO' and 'INSTANT_EXCHANGE' * @param {TRANSACTION_STATUS[]} [params.statuses] Optional. List of statuses to query. valid subtypes are: 'CREATED', 'PENDING', 'FAILED', 'SUCCESS' and 'ROLLED_BACK' + * @param {string[]} [params.currencies] Optional. List of currencies of the transactions + * @param {string[]} [params.networks] Optional. List of network codes * @param {SORT_BY} [params.order_by] Optional. Defines the sorting type.'created_at' or 'id'. Default is 'created_at' * @param {string} [params.from] Optional. Interval initial value when ordering by 'created_at'. As Datetime * @param {string} [params.till] Optional. Interval end value when ordering by 'created_at'. As Datetime @@ -1120,6 +1125,7 @@ Accepted values: wallet, spot. Must not be the same as source * @param {string} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC'. * @param {number} [params.limit] Optional. Transactions per query. Defaul is 100. Max is 1000 * @param {number} [params.offset] Optional. Default is 0. Max is 100000 + * @param {Boolean} [params.group_transactions] Optional. Flag indicating whether the returned transactions will be parts of a single operation * * @return A list of transactions */ @@ -1128,14 +1134,17 @@ Accepted values: wallet, spot. Must not be the same as source types?: TRANSACTION_TYPE[]; subtypes?: TRANSACTION_SUBTYPE[]; statuses?: TRANSACTION_STATUS[]; - order_by: SORT_BY; - from: string; + currencies?: string[], + networks?: string[], + order_by?: SORT_BY; + from?: string; till?: string; id_from?: string; id_till?: string; sort?: string; limit?: number; offset?: number; + group_transactions?:Boolean }): Promise { return this.get("wallet/transactions", params); } diff --git a/lib/models/Transactions.ts b/lib/models/Transactions.ts index 0770914..ce9f503 100644 --- a/lib/models/Transactions.ts +++ b/lib/models/Transactions.ts @@ -7,12 +7,12 @@ export interface Transaction { created_at: string; updated_at: string; native: NativeTransaction; - primetrust: any; - meta: MetaTransaction; + commit_risk: CommitRisk; } export interface NativeTransaction { tx_id: string; + wallet_id: string; index: number; currency: string; amount: number; @@ -23,23 +23,13 @@ export interface NativeTransaction { offchain_id: string; confirmations: number; public_comment: string; + error_code: string; senders: string[]; + operation_type: string; } -export interface MetaTransaction { - fiat_to_crypto: JSON; - id: number; - provider_name: string; - order_type: string; - source_currency: string; - target_currency: string; - wallet_address: string; - tx_hash: string; - target_amount: string; - source_amount: string; - status: string; - created_at: string; - updated_at: string; - deleted_at: string; - payment_method_type: string; +export interface CommitRisk { + score: number; + rbf: Boolean; + low_fee: Boolean; } diff --git a/test/rest/wallet_management.test.ts b/test/rest/wallet_management.test.ts index 7294bd3..d4dc0de 100644 --- a/test/rest/wallet_management.test.ts +++ b/test/rest/wallet_management.test.ts @@ -46,14 +46,14 @@ describe("wallet management", () => { describe("Get deposit crypto address of symbol", () => { it("", async function () { this.timeout(0); - let address = await client.getDepositCryptoAddressOfCurrency("EOS"); + let address = await client.getDepositCryptoAddress("EOS"); assert(goodAddress(address), "not good address"); }); }); describe("create deposit crypto address", () => { it("", async function () { this.timeout(0); - let oldAddress = await client.getDepositCryptoAddressOfCurrency("EOS"); + let oldAddress = await client.getDepositCryptoAddress("EOS"); let newAddres = await client.createDepositCryptoAddress("EOS"); assert(oldAddress.address !== newAddres.address, "not a new address"); assert(goodAddress(newAddres), "not good address"); @@ -78,7 +78,7 @@ describe("wallet management", () => { describe("withdraw crypto and commitment/rollback", () => { it("exception amount too low", async function () { this.timeout(0); - let adaAddress = await client.getDepositCryptoAddressOfCurrency("ADA"); + let adaAddress = await client.getDepositCryptoAddress("ADA"); try { let transactionID = await client.withdrawCrypto({ currency: "ADA", @@ -94,7 +94,7 @@ describe("wallet management", () => { }); it("with auto commit (default mode)", async function () { this.timeout(0); - let adaAddress = await client.getDepositCryptoAddressOfCurrency("ADA"); + let adaAddress = await client.getDepositCryptoAddress("ADA"); let transactionID = await client.withdrawCrypto({ currency: "ADA", amount: "0.1", @@ -104,7 +104,7 @@ describe("wallet management", () => { }); it("with commit", async function () { this.timeout(0); - let adaAddress = await client.getDepositCryptoAddressOfCurrency("ADA"); + let adaAddress = await client.getDepositCryptoAddress("ADA"); let transactionID = await client.withdrawCrypto({ currency: "ADA", amount: "0.1", @@ -117,7 +117,7 @@ describe("wallet management", () => { }); it("with rollback", async function () { this.timeout(0); - let adaAddress = await client.getDepositCryptoAddressOfCurrency("ADA"); + let adaAddress = await client.getDepositCryptoAddress("ADA"); let transactionID = await client.withdrawCrypto({ currency: "ADA", amount: "0.1", @@ -152,7 +152,7 @@ describe("wallet management", () => { describe("check if crypto address belongs to current account", () => { it("cro belongs", async function () { this.timeout(0); - let croAddress = await client.getDepositCryptoAddressOfCurrency("CRO"); + let croAddress = await client.getDepositCryptoAddress("CRO"); let result = await client.checkIfCryptoAddressBelongsToCurrentAccount( croAddress.address ); @@ -160,7 +160,7 @@ describe("wallet management", () => { }); it.skip("eos belongs", async function () { this.timeout(0); - let eosAddress = await client.getDepositCryptoAddressOfCurrency("EOS"); + let eosAddress = await client.getDepositCryptoAddress("EOS"); let result = await client.checkIfCryptoAddressBelongsToCurrentAccount( eosAddress.address ); @@ -208,7 +208,7 @@ describe("wallet management", () => { describe("get transaction history", () => { it("", async function () { this.timeout(0); - let transactions = await client.getTransactionHistory(); + let transactions = await client.getTransactionHistory({ currencies: ["CRO", "ETH"] }); assert(goodList(goodTransaction, transactions), "not good transaction"); }); }); @@ -221,7 +221,7 @@ describe("wallet management", () => { describe("check if offchain is available", () => { it("", async function () { this.timeout(0); - let myEOSAddress = await client.getDepositCryptoAddressOfCurrency("EOS"); + let myEOSAddress = await client.getDepositCryptoAddress("EOS"); let result = await client.checkIfOffchainIsAvailable({ currency: "EOS", address: myEOSAddress.address, From 0432f0e6b68ad252908b0658e935bfff7d6f064f Mon Sep 17 00:00:00 2001 From: "T. Ismael Verdugo" Date: Thu, 8 Feb 2024 13:48:36 -0300 Subject: [PATCH 09/16] refactor: rename methods --- lib/client.ts | 18 +++++++++--------- test/rest/market_data.test.ts | 12 ++++++------ test/rest/spot_trading.test.ts | 2 +- test/rest/wallet_management.test.ts | 16 +++++++++++----- 4 files changed, 27 insertions(+), 21 deletions(-) diff --git a/lib/client.ts b/lib/client.ts index ced6dc2..8c60bea 100644 --- a/lib/client.ts +++ b/lib/client.ts @@ -210,7 +210,7 @@ export class Client { * * @returns An object/dict of quotation prices of currencies, indexed by source currency code. */ - getPricesHistory(params?: { + getPriceHistory(params?: { from?: string; to?: string; until?: string; @@ -246,7 +246,7 @@ export class Client { * @param {string} symbol A symbol id. * @returns The ticker's last price of a symbol. */ - getTickerLastPriceOfSymbol(symbol: string): Promise { + getTickerLastPrice(symbol: string): Promise { return this.get(`public/price/ticker/${symbol}`); } @@ -300,7 +300,7 @@ export class Client { * * @return A list of trades of the symbol */ - getTradesOfSymbol( + getTradesBySymbol( symbol: string, params?: { sort?: SORT; @@ -350,7 +350,7 @@ export class Client { * * @return The order book of the symbol */ - getOrderBookOfSymbol(symbol: string, depth?: number): Promise { + getOrderBook(symbol: string, depth?: number): Promise { return this.get(`public/orderbook/${symbol}`, { depth }); } @@ -433,7 +433,7 @@ export class Client { * * @return A list of candles of a symbol */ - getCandlesOfSymbol( + getCandlesBySymbol( symbol: string, params?: { period?: PERIOD; @@ -479,7 +479,7 @@ export class Client { * * @return the spot trading balance of a currency. */ - async getSpotTradingBalanceOfCurrency(currency: string): Promise { + async getSpotTradingBalance(currency: string): Promise { const balance = await this.get(`spot/balance/${currency}`); balance.currency = currency; return balance; @@ -774,7 +774,7 @@ export class Client { * * @return A list of wallet balances */ - getWalletBalance(): Promise { + getWalletBalances(): Promise { return this.get("wallet/balance"); } @@ -789,7 +789,7 @@ export class Client { * * @return The wallet balance of the currency */ - async getWalletBalanceOfCurrency(currency: string): Promise { + async getWalletBalance(currency: string): Promise { let balance = await this.get(`wallet/balance/${currency}`); balance.currency = currency; return balance; @@ -977,7 +977,7 @@ Accepted values: never, optionally, required } /** - * Get an estimate of the withdrawal fee + * Get an estimate of a withdrawal fee * * Requires the "Payment information" API key Access Right. * diff --git a/test/rest/market_data.test.ts b/test/rest/market_data.test.ts index 82af0c6..c5f8e46 100644 --- a/test/rest/market_data.test.ts +++ b/test/rest/market_data.test.ts @@ -108,7 +108,7 @@ describe("Rest client test", () => { describe("Get Prices History", () => { it("for all currencies as origin", async function () { this.timeout(0); - let pricesHistory = await client.getPricesHistory({ + let pricesHistory = await client.getPriceHistory({ to: "ETH", period: PERIOD._15_MINUTES, }); @@ -120,7 +120,7 @@ describe("Rest client test", () => { }); it("for one currency pair", async function () { this.timeout(0); - let pricesHistory = await client.getPricesHistory({ + let pricesHistory = await client.getPriceHistory({ to: "ETH", from: "BTC", period: PERIOD._15_MINUTES, @@ -149,7 +149,7 @@ describe("Rest client test", () => { describe("Get Ticker Price Of Symbol", () => { it("", async function () { this.timeout(0); - let price = await client.getTickerLastPriceOfSymbol("EOSETH"); + let price = await client.getTickerLastPrice("EOSETH"); assert(goodTickerPrice(price), "not a good ticker price"); }); }); @@ -179,7 +179,7 @@ describe("Rest client test", () => { describe("Get Trades of symbol", () => { it("", async function () { this.timeout(0); - let trades = await client.getTradesOfSymbol("EOSETH"); + let trades = await client.getTradesBySymbol("EOSETH"); assert(!emptyList(trades), "empty dict of trades"); assert(goodList(goodPublicTrade, trades), "not good trades"); }); @@ -202,7 +202,7 @@ describe("Rest client test", () => { describe("Get Order book of symbol", () => { it("", async function () { this.timeout(0); - let orderBook = await client.getOrderBookOfSymbol("EOSETH"); + let orderBook = await client.getOrderBook("EOSETH"); assert(goodOrderbook(orderBook), "not good orderbook"); }); }); @@ -243,7 +243,7 @@ describe("Rest client test", () => { describe("Get candles Of Symbol", () => { it("with period and limit", async function () { this.timeout(0); - let candles = await client.getCandlesOfSymbol("ADAETH", { + let candles = await client.getCandlesBySymbol("ADAETH", { period: PERIOD._30_MINUTES, limit: 2, }); diff --git a/test/rest/spot_trading.test.ts b/test/rest/spot_trading.test.ts index 35d3eaa..67bca3e 100644 --- a/test/rest/spot_trading.test.ts +++ b/test/rest/spot_trading.test.ts @@ -34,7 +34,7 @@ describe("spot trading", () => { describe("get spot trading balance of currency", () => { it("", async function () { this.timeout(0); - let balance = await client.getSpotTradingBalanceOfCurrency("ADA"); + let balance = await client.getSpotTradingBalance("ADA"); assert(goodBalance(balance), "not good balance"); }); }); diff --git a/test/rest/wallet_management.test.ts b/test/rest/wallet_management.test.ts index d4dc0de..ad58d7b 100644 --- a/test/rest/wallet_management.test.ts +++ b/test/rest/wallet_management.test.ts @@ -11,6 +11,7 @@ import { goodList, goodTransaction, } from "../test_helpers"; +import { Address } from "../../lib/models"; const keys = require("/home/ismael/cryptomarket/keys.json"); describe("wallet management", () => { @@ -25,14 +26,14 @@ describe("wallet management", () => { describe("Get wallet balance", () => { it("", async function () { this.timeout(0); - let balances = await client.getWalletBalance(); + let balances = await client.getWalletBalances(); assert(goodList(goodBalance, balances), "not good balance"); }); }); describe("get wallet balance of currency", () => { it("", async function () { this.timeout(0); - let balance = await client.getWalletBalanceOfCurrency("ADA"); + let balance = await client.getWalletBalance("ADA"); assert(goodBalance(balance), "not good balance"); }); }); @@ -55,7 +56,7 @@ describe("wallet management", () => { this.timeout(0); let oldAddress = await client.getDepositCryptoAddress("EOS"); let newAddres = await client.createDepositCryptoAddress("EOS"); - assert(oldAddress.address !== newAddres.address, "not a new address"); + assert(!sameAddress(oldAddress, newAddres), "not a new address"); assert(goodAddress(newAddres), "not good address"); }); }); @@ -178,7 +179,7 @@ describe("wallet management", () => { it("", async function () { this.timeout(0); // original wallet amount - let startingADAInWallet = await client.getWalletBalanceOfCurrency("ADA"); + let startingADAInWallet = await client.getWalletBalance("ADA"); // transfer to spot let transactionID = await client.transferBetweenWalletAndExchange({ source: ACCOUNT.WALLET, @@ -198,7 +199,7 @@ describe("wallet management", () => { assert(transactionID !== "", "not good identifier of transfer to wallet"); // end wallet amount - let endADAInWallet = await client.getWalletBalanceOfCurrency("ADA"); + let endADAInWallet = await client.getWalletBalance("ADA"); assert( startingADAInWallet.available === endADAInWallet.available, "not good tranfer" @@ -238,3 +239,8 @@ describe("wallet management", () => { }); }); }); +function sameAddress(oldAddress: Address, newAddres: Address) { + return oldAddress.address === newAddres.address && oldAddress.currency && newAddres.currency + && oldAddress.payment_id === newAddres.payment_id && oldAddress.public_key === oldAddress.public_key +} + From b4e6ee212fccc5ee617cbcbf461c31973ffbea22 Mon Sep 17 00:00:00 2001 From: "T. Ismael Verdugo" Date: Thu, 8 Feb 2024 14:00:42 -0300 Subject: [PATCH 10/16] refactor: update rest method links to api docs --- lib/client.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/client.ts b/lib/client.ts index 8c60bea..78cc6fd 100644 --- a/lib/client.ts +++ b/lib/client.ts @@ -505,7 +505,7 @@ export class Client { * * Requires the "Place/cancel orders" API key Access Right. * - * https://api.exchange.cryptomkt.com/#get-active-spot-orders + * https://api.exchange.cryptomkt.com/#get-active-spot-order * * @param {string} client_order_id The clientOrderId of the order * @@ -669,7 +669,7 @@ export class Client { * * Requires the "Place/cancel orders" API key Access Right. * - * https://api.exchange.cryptomkt.com/#get-all-trading-commission + * https://api.exchange.cryptomkt.com/#get-all-trading-commissions * * @return A list of commission rates */ @@ -800,7 +800,7 @@ export class Client { * * Requires the "Payment information" API key AccesrkRight. * - *, network_code https://api.exchange.cryptomkt.com/#deposit-crypto-address + * https://api.exchange.cryptomkt.com/#get-deposit-crypto-address * * @return A list of currency addresses */ @@ -813,7 +813,7 @@ export class Client { * * Requires the "Payment information" API key Access Right. * - * https://api.exchange.cryptomkt.com/#deposit-crypto-address + * https://api.exchange.cryptomkt.com/#get-deposit-crypto-address * * @param {string} currency currency to get the address * @@ -855,7 +855,7 @@ export class Client { * * Requires the "Payment information" API key Access Right. * - * https://api.exchange.cryptomkt.com/#last-10-deposit-crypto-address + * https://api.exchange.cryptomkt.com/#last-10-deposit-crypto-addresses * * @param {string} currency currency to get the list of addresses * @param {string} [network_code] Optional. network code @@ -966,13 +966,13 @@ Accepted values: never, optionally, required * * Requires the "Payment information" API key Access Right. * - * https://api.exchange.cryptomkt.com/#get-spot-fees + * https://api.exchange.cryptomkt.com/#estimate-withdrawal-fees * * @param {FeeRequest[]} feeRequests A list of fee requests * * @return The list of requested fees */ - async getEstimateWithdrawFees(feeRequests: FeeRequest[]): Promise { + async getEstimateWithdrawalFees(feeRequests: FeeRequest[]): Promise { return this.post("wallet/crypto/fees/estimate", feeRequests); } @@ -981,7 +981,7 @@ Accepted values: never, optionally, required * * Requires the "Payment information" API key Access Right. * - * https://api.exchange.cryptomkt.com/#estimate-withdraw-fee + * https://api.exchange.cryptomkt.com/#estimate-withdrawal-fee * * @param {object} params * @param {string} params.currency the currency code for withdraw @@ -990,7 +990,7 @@ Accepted values: never, optionally, required * * @return The expected fee */ - async getEstimateWithdrawFee(params: { + async getEstimateWithdrawalFee(params: { currency: string; amount: string; network_code?: string; From 3eff315661dfea9d190db6e6daefd6dd3670d850 Mon Sep 17 00:00:00 2001 From: "T. Ismael Verdugo" Date: Thu, 8 Feb 2024 14:15:27 -0300 Subject: [PATCH 11/16] refactor: websocket method links, fix: rest order list link --- lib/client.ts | 2 +- lib/websocket/marketDataClient.ts | 2 +- lib/websocket/tradingClient.ts | 10 +++++----- lib/websocket/walletClient.ts | 16 +++++++++------- 4 files changed, 16 insertions(+), 14 deletions(-) diff --git a/lib/client.ts b/lib/client.ts index 78cc6fd..93689e1 100644 --- a/lib/client.ts +++ b/lib/client.ts @@ -593,7 +593,7 @@ export class Client { * - For an OTOCO order list, the secondary orders have the same restrictions as an OCO order * - Default is ORDER_TYPE.Limit * - * https://api.exchange.cryptomkt.com/#create-new-spot-order-list-2 + * https://api.exchange.cryptomkt.com/#create-new-spot-order-list * * @param {string} params.order_list_id Order list identifier. If omitted, it will be generated by the system upon order list creation. Must be equal to client_order_id of the first order in the request. * @param {string} params.contingency_type Order list type. diff --git a/lib/websocket/marketDataClient.ts b/lib/websocket/marketDataClient.ts index e1e2556..ea7f03c 100644 --- a/lib/websocket/marketDataClient.ts +++ b/lib/websocket/marketDataClient.ts @@ -548,7 +548,7 @@ export class MarketDataClient extends WSClientBase { * * batch subscriptions have a joined update for all currencies * - * https://api.exchange.cryptomkt.com/#subscribe-to-price-rates + * https://api.exchange.cryptomkt.com/#subscribe-to-price-rates-in-batches * * @param {function} callback recieves a feed of price rates as a map of them, indexed by currency id, and the type of notification, only DATA * @param {PRICE_RATE_SPEED} speed The speed of the feed. '1s' or '3s' diff --git a/lib/websocket/tradingClient.ts b/lib/websocket/tradingClient.ts index 06326f1..23ae1b8 100644 --- a/lib/websocket/tradingClient.ts +++ b/lib/websocket/tradingClient.ts @@ -218,32 +218,32 @@ export class TradingClient extends AuthClient { * @param {string} params.currency The currency code to query the balance * @return A promise that resolves with the spot trading balance of a currency */ - async getSpotTradingBalanceOfCurrency(params: { + async getSpotTradingBalance(params: { currency: string; }): Promise { return this.makeRequest({ method: "spot_balance", params }); } /** - * Get the personal trading commission rates for all symbols + * Get the personal trading fee rates for all symbols * * https://api.exchange.cryptomkt.com/#get-spot-fees * * @return A promise that resolves with a list of commission rates */ - async getSpotCommissions(): Promise { + async getSpotFees(): Promise { return this.makeRequest({ method: "spot_fees" }); } /** - * Get the personal trading commission rate of a symbol + * Get the personal trading fee rate of a symbol * * https://api.exchange.cryptomkt.com/#get-spot-fee * * @param {string} params.symbol The symbol of the commission rate * @return A promise that resolves with the commission rate of a symbol */ - async getSpotCommissionOfSymbol(params: { + async getSpotFee(params: { symbol: string; }): Promise { return this.makeRequest({ method: "spot_fee", params }); diff --git a/lib/websocket/walletClient.ts b/lib/websocket/walletClient.ts index 4923470..3b73650 100644 --- a/lib/websocket/walletClient.ts +++ b/lib/websocket/walletClient.ts @@ -42,7 +42,7 @@ export class WalletClient extends AuthClient { * * Requires the "Payment information" API key Access Right * - * https://api.exchange.cryptomkt.com/#wallet-balance + * https://api.exchange.cryptomkt.com/#request-wallet-balance * * @return A promise that resolves with a list of wallet balances */ @@ -55,12 +55,12 @@ export class WalletClient extends AuthClient { * * Requires the "Payment information" API key Access Right * - * https://api.exchange.cryptomkt.com/#wallet-balance + * https://api.exchange.cryptomkt.com/#request-wallet-balance * * @param {string} currency The currency code to query the balance * @return A promise that resolves with the wallet balance of the currency */ - async getWalletBalanceOfCurrency(currency: string): Promise { + async getWalletBalance(currency: string): Promise { const response = await this.makeRequest({ method: "wallet_balance", params: { currency } }); return { available: response.available, reserved: response.reserved, currency: currency }; } @@ -78,7 +78,7 @@ export class WalletClient extends AuthClient { * * Requires the "Payment information" API key Access Right * - * https://api.exchange.cryptomkt.com/#get-transactions-history + * https://api.exchange.cryptomkt.com/#get-transactions * * @param {string[]} [params.tx_ids] Optional. List of transaction identifiers to query * @param {TRANSACTION_TYPE[]} [params.transaction_types] Optional. List of types to query. valid types are: 'DEPOSIT', 'WITHDRAW', 'TRANSFER' and 'SWAP' @@ -92,6 +92,7 @@ export class WalletClient extends AuthClient { * @param {SORT} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC' * @param {number} [params.limit] Optional. Transactions per query. Defaul is 100. Max is 1000 * @param {number} [params.offset] Optional. Default is 0. Max is 100000 + * @param {Boolean} [params.group_transactions] Flag indicating whether the returned transactions will be parts of a single operation. Default is false * @return A promise that resolves with a list of transactions */ getTransactions(params: { @@ -108,6 +109,7 @@ export class WalletClient extends AuthClient { sort?: SORT; limit?: number; offset?: number; + group_transactions?: Boolean; }): Promise { const clean_params: any = { ...params } clean_params.currencies = params.currencies?.join(", ") @@ -142,7 +144,7 @@ export class WalletClient extends AuthClient { /** * unsubscribe to the transaction feed. * - * https://api.exchange.cryptomkt.com/#subscription-to-the-transactions + * https://api.exchange.cryptomkt.com/#subscribe-to-transactions * * @return {Promise} A Promise of the unsubscription result. True if unsubscribed */ @@ -158,7 +160,7 @@ export class WalletClient extends AuthClient { * the first notification has a snapshot of the wallet. further notifications * are updates of the wallet * - * https://api.exchange.cryptomkt.com/#subscription-to-the-balance + * https://api.exchange.cryptomkt.com/#subscribe-to-wallet-balances * * @param {function} callback A function that recieves notifications with a list of balances, and the type of notification (either SNAPSHOT or UPDATE) * @return {Promise} A Promise of the subscription result. True if subscribed @@ -185,7 +187,7 @@ export class WalletClient extends AuthClient { /** * unsubscribe to the balance feed. * - * https://api.exchange.cryptomkt.com/#subscription-to-the-balance + * https://api.exchange.cryptomkt.com/#subscribe-to-wallet-balances * * @return {Promise} A Promise of the unsubscription result. True if unsubscribed */ From 2474636a0514e70f287bff3c7538adefc329be7b Mon Sep 17 00:00:00 2001 From: "T. Ismael Verdugo" Date: Thu, 8 Feb 2024 14:19:59 -0300 Subject: [PATCH 12/16] refactor: simplifies some one argument method calls. fix: websocket tests --- lib/websocket/tradingClient.ts | 16 ++++++---------- test/websocket/trading.test.ts | 10 +++------- test/websocket/wallet.test.ts | 2 +- 3 files changed, 10 insertions(+), 18 deletions(-) diff --git a/lib/websocket/tradingClient.ts b/lib/websocket/tradingClient.ts index 23ae1b8..9f90c8f 100644 --- a/lib/websocket/tradingClient.ts +++ b/lib/websocket/tradingClient.ts @@ -215,13 +215,11 @@ export class TradingClient extends AuthClient { * * https://api.exchange.cryptomkt.com/#get-spot-trading-balance-2 * - * @param {string} params.currency The currency code to query the balance + * @param {string} currency The currency code to query the balance * @return A promise that resolves with the spot trading balance of a currency */ - async getSpotTradingBalance(params: { - currency: string; - }): Promise { - return this.makeRequest({ method: "spot_balance", params }); + async getSpotTradingBalance(currency: string): Promise { + return this.makeRequest({ method: "spot_balance", params: { currency } }); } /** @@ -240,13 +238,11 @@ export class TradingClient extends AuthClient { * * https://api.exchange.cryptomkt.com/#get-spot-fee * - * @param {string} params.symbol The symbol of the commission rate + * @param {string} symbol The symbol of the commission rate * @return A promise that resolves with the commission rate of a symbol */ - async getSpotFee(params: { - symbol: string; - }): Promise { - return this.makeRequest({ method: "spot_fee", params }); + async getSpotFee(symbol: string): Promise { + return this.makeRequest({ method: "spot_fee", params: { symbol } }); } /////////////////// diff --git a/test/websocket/trading.test.ts b/test/websocket/trading.test.ts index 0ac9b4a..1fc1409 100644 --- a/test/websocket/trading.test.ts +++ b/test/websocket/trading.test.ts @@ -28,9 +28,7 @@ describe("TradingClient", () => { it("gets only one", async function () { this.timeout(0); await wsclient.connect(); - const balance = await wsclient.getSpotTradingBalanceOfCurrency({ - currency: "EOS", - }); + const balance = await wsclient.getSpotTradingBalance("EOS"); expect(goodBalance(balance)).to.be.true }); }); @@ -94,7 +92,7 @@ describe("TradingClient", () => { it("gets a commission list", async function () { this.timeout(0); await wsclient.connect(); - const commissions = await wsclient.getSpotCommissions(); + const commissions = await wsclient.getSpotFees(); expect(commissions.length).to.be.greaterThanOrEqual(1); const allGood = commissions.map(goodTradingCommission).every(Boolean) expect(allGood).to.be.true @@ -103,9 +101,7 @@ describe("TradingClient", () => { it("gets only one", async function () { this.timeout(0); await wsclient.connect(); - const commission = await wsclient.getSpotCommissionOfSymbol({ - symbol: "EOSETH", - }); + const commission = await wsclient.getSpotFee("EOSETH"); expect(goodTradingCommission(commission)).to.be.true }); }); diff --git a/test/websocket/wallet.test.ts b/test/websocket/wallet.test.ts index 4c39ea1..21b2830 100644 --- a/test/websocket/wallet.test.ts +++ b/test/websocket/wallet.test.ts @@ -26,7 +26,7 @@ describe("WalletClient", function () { it("gets only one balance", async function () { this.timeout(0); await wsclient.connect(); - const balance = await wsclient.getWalletBalanceOfCurrency("EOS"); + const balance = await wsclient.getWalletBalance("EOS"); expect(goodBalance(balance)).to.be.true }); }); From cec28d3d7817ffe52255dc2e79632e9d0edd2f1e Mon Sep 17 00:00:00 2001 From: "T. Ismael Verdugo" Date: Fri, 9 Feb 2024 13:09:01 -0300 Subject: [PATCH 13/16] refactor: update models --- lib/models/ACLSettings.ts | 10 ++-- lib/models/Address.ts | 6 +-- lib/models/AmountLock.ts | 8 +-- lib/models/Balance.ts | 2 +- lib/models/Candle.ts | 2 +- lib/models/Commission.ts | 4 +- lib/models/Currency.ts | 20 ++++---- lib/models/FeeRequest.ts | 2 +- lib/models/Network.ts | 24 ++++----- lib/models/Order.ts | 38 +++++++-------- lib/models/Report.ts | 28 +++++------ lib/models/SubAccount.ts | 2 +- lib/models/Symbol.ts | 18 +++---- lib/models/Ticker.ts | 2 +- lib/models/Trade.ts | 4 +- lib/models/Transactions.ts | 24 ++++----- lib/paramStyleConverter.ts | 52 ++++++++++++++++++++ test/objectKeyConverter.test.ts | 76 +++++++++++++++++++++++++++++ test/rest/wallet_management.test.ts | 4 +- 19 files changed, 227 insertions(+), 99 deletions(-) create mode 100644 lib/paramStyleConverter.ts create mode 100644 test/objectKeyConverter.test.ts diff --git a/lib/models/ACLSettings.ts b/lib/models/ACLSettings.ts index bc85777..b1a419e 100644 --- a/lib/models/ACLSettings.ts +++ b/lib/models/ACLSettings.ts @@ -1,8 +1,8 @@ export interface ACLSettings { - sub_account_id: string; - deposit_address_generation_enabled: boolean; - withdraw_enabled: boolean; + SubAccountId: string; + depositAddressGenerationEnabled: boolean; + withdrawEnabled: boolean; description: string; - created_at: string; - updated_at: string; + createdAt: string; + updatedAt: string; } diff --git a/lib/models/Address.ts b/lib/models/Address.ts index df5818c..d34bfd0 100644 --- a/lib/models/Address.ts +++ b/lib/models/Address.ts @@ -1,7 +1,7 @@ export interface Address { address: string; currency: string; - payment_id: string; - public_key: string; - netwokd_code: string; + paymentId: string; + publicKey: string; + netwokdCode: string; } diff --git a/lib/models/AmountLock.ts b/lib/models/AmountLock.ts index 6083c18..98938ff 100644 --- a/lib/models/AmountLock.ts +++ b/lib/models/AmountLock.ts @@ -2,10 +2,10 @@ export interface AmountLock { id: number; currency: string; amount: string; - date_end: string; + dateEnd: string; description: string; cancelled: boolean; - cancelled_at: string; - cancel_description: string; - created_at: string; + cancelledAt: string; + cancelDescription: string; + createdAt: string; } diff --git a/lib/models/Balance.ts b/lib/models/Balance.ts index 112e515..2312ce2 100644 --- a/lib/models/Balance.ts +++ b/lib/models/Balance.ts @@ -2,5 +2,5 @@ export interface Balance { currency: string; available: string; reserved: string; - reserved_margin?: string; + reservedMargin?: string; } diff --git a/lib/models/Candle.ts b/lib/models/Candle.ts index 4cf7186..91a21cd 100644 --- a/lib/models/Candle.ts +++ b/lib/models/Candle.ts @@ -5,5 +5,5 @@ export interface Candle { min: string; max: string; volume: string; - volume_quote: string; + volumeQuote: string; } diff --git a/lib/models/Commission.ts b/lib/models/Commission.ts index ae80e71..1ba5ee5 100644 --- a/lib/models/Commission.ts +++ b/lib/models/Commission.ts @@ -1,5 +1,5 @@ export interface Commission { symbol: string; - take_rate: string; - make_rate: string; + takeRate: string; + makeRate: string; } diff --git a/lib/models/Currency.ts b/lib/models/Currency.ts index 128128a..6a4f026 100644 --- a/lib/models/Currency.ts +++ b/lib/models/Currency.ts @@ -1,19 +1,19 @@ import { Network } from "./Network"; export interface Currency { - full_name: string; + fullName: string; crypto: boolean; - payin_enabled: boolean; - payout_enabled: boolean; - transfer_enabled: boolean; - precision_transfer: string; + payinEnabled: boolean; + payoutEnabled: boolean; + transferEnabled: boolean; + precisionTransfer: string; sign: string; - crypto_payment_id_name: string; - crypto_explorer: string; - account_top_order: number; - qr_prefix: string; + cryptoPaymentIdName: string; + cryptoExplorer: string; + accountTopOrder: number; + qrPrefix: string; delisted: boolean; - contract_address: string; + contractAddress: string; networks: Network[]; } diff --git a/lib/models/FeeRequest.ts b/lib/models/FeeRequest.ts index 07662fa..f6d492b 100644 --- a/lib/models/FeeRequest.ts +++ b/lib/models/FeeRequest.ts @@ -1,5 +1,5 @@ export interface FeeRequest { currency: string; amount: string; - network_code?: string; + networkCode?: string; } \ No newline at end of file diff --git a/lib/models/Network.ts b/lib/models/Network.ts index da79b30..d57082c 100644 --- a/lib/models/Network.ts +++ b/lib/models/Network.ts @@ -2,16 +2,16 @@ export interface Network { network: string; protocol: string; default: boolean; - payin_enabled: boolean; - payout_enabled: boolean; - precision_payout: string; - payout_fee: string; - payout_is_payment_id: boolean; - payin_payment_id: boolean; - payin_confirmations: number; - address_regrex: string; - payment_id_regex: string; - low_processing_time: string; - high_processing_time: string; - avg_processing_time: string; + payinEnabled: boolean; + payoutEnabled: boolean; + precisionPayout: string; + payoutFee: string; + payoutIsPaymentId: boolean; + payinPaymentId: boolean; + payinConfirmations: number; + addressRegrex: string; + paymentIdRegex: string; + lowProcessingTime: string; + highProcessingTime: string; + avgProcessingTime: string; } diff --git a/lib/models/Order.ts b/lib/models/Order.ts index 43a74a0..a694ccd 100644 --- a/lib/models/Order.ts +++ b/lib/models/Order.ts @@ -8,38 +8,38 @@ import { export interface Order { id: number; - client_order_id: string; + clientOrderId: string; symbol: string; side: SIDE; status: ORDER_STATUS; type: ORDER_TYPE; - time_in_force: TIME_IN_FORCE; + timeInForce: TIME_IN_FORCE; quantity: string; price: string; - quantity_cumulative: string; - created_at: string; - updated_at: string; - expire_time: string; - stop_price: string; - post_only: boolean; + quantityCumulative: string; + createdAt: string; + updatedAt: string; + expireTime: string; + stopPrice: string; + postOnly: boolean; trades: string; - original_client_order_id: string; - order_list_id: string; - contingency_type: CONTINGENCY; + originalClientOrderId: string; + orderListId: string; + contingencyType: CONTINGENCY; } export interface OrderRequest { symbol: string; side: SIDE; quantity: string; - client_order_id?: string; + clientOrderId?: string; type?: ORDER_TYPE; - time_in_force?: TIME_IN_FORCE; + timeInForce?: TIME_IN_FORCE; price?: string; - stop_price?: string; - expire_time?: string; - strict_validate?: boolean; - post_only?: boolean; - take_rate?: string; - make_rate?: string; + stopPrice?: string; + expireTime?: string; + strictValidate?: boolean; + postOnly?: boolean; + takeRate?: string; + makeRate?: string; } diff --git a/lib/models/Report.ts b/lib/models/Report.ts index c091874..91ce3fc 100644 --- a/lib/models/Report.ts +++ b/lib/models/Report.ts @@ -1,23 +1,23 @@ export interface Report { id: number; - client_order_id: string; + clientOrderId: string; symbol: string; side: string; status: string; type: string; - time_in_force: string; + timeInForce: string; quantity: number; price: number; - cum_quantity: number; - post_only: boolean; - created_at: string; - updated_at: string; - stop_price: number; - original_client_order_id: string; - trade_id: number; - trade_quantity: number; - trade_price: number; - trade_fee: number; - trade_taker: boolean; - report_type: string; + cumQuantity: number; + postOnly: boolean; + createdAt: string; + updatedAt: string; + stopPrice: number; + originalClientOrderId: string; + tradeId: number; + tradeQuantity: number; + tradePrice: number; + tradeFee: number; + tradeTaker: boolean; + reportType: string; } diff --git a/lib/models/SubAccount.ts b/lib/models/SubAccount.ts index db4e381..540850e 100644 --- a/lib/models/SubAccount.ts +++ b/lib/models/SubAccount.ts @@ -1,7 +1,7 @@ import { SUB_ACCOUNT_STATUS } from "../constants"; export interface SubAccount { - sub_account_id: string; + subAccountId: string; email: string; status: SUB_ACCOUNT_STATUS; } diff --git a/lib/models/Symbol.ts b/lib/models/Symbol.ts index 7395cb9..e430278 100644 --- a/lib/models/Symbol.ts +++ b/lib/models/Symbol.ts @@ -1,13 +1,13 @@ export interface Symbol { type:string; - base_currency:string; - quote_currency:string; + baseCurrency:string; + quoteCurrency:string; status:string; - quantity_increment:string; - tick_size:string; - take_rate:string; - make_rate:string; - fee_currency:string; - margin_trading:boolean; - max_initial_leverage:string; + quantityIncrement:string; + tickSize:string; + takeRate:string; + makeRate:string; + feeCurrency:string; + marginTrading:boolean; + maxInitialLeverage:string; } \ No newline at end of file diff --git a/lib/models/Ticker.ts b/lib/models/Ticker.ts index db09891..44dac9b 100644 --- a/lib/models/Ticker.ts +++ b/lib/models/Ticker.ts @@ -7,5 +7,5 @@ export interface Ticker { high: string; low: string; volume: string; - volume_quote: string; + volumeQuote: string; } diff --git a/lib/models/Trade.ts b/lib/models/Trade.ts index 91c8aa8..dbe32bb 100644 --- a/lib/models/Trade.ts +++ b/lib/models/Trade.ts @@ -1,7 +1,7 @@ export interface Trade { id: string; - order_id: string; - client_order_id: string; + orderId: string; + clientOrderId: string; symbol: string; side: string; quantity: string; diff --git a/lib/models/Transactions.ts b/lib/models/Transactions.ts index ce9f503..4c1de03 100644 --- a/lib/models/Transactions.ts +++ b/lib/models/Transactions.ts @@ -1,35 +1,35 @@ export interface Transaction { - operation_id?: string; // present in websocket transaction subscription notifications + operationId?: string; // present in websocket transaction subscription notifications id: number; status: string; type: string; subtype: string; - created_at: string; - updated_at: string; + createdAt: string; + updatedAt: string; native: NativeTransaction; - commit_risk: CommitRisk; + commitRisk: CommitRisk; } export interface NativeTransaction { - tx_id: string; - wallet_id: string; + txId: string; + walletId: string; index: number; currency: string; amount: number; fee: number; address: string; - payment_id: string; + paymentId: string; hash: string; - offchain_id: string; + offchainId: string; confirmations: number; - public_comment: string; - error_code: string; + publicComment: string; + errorCode: string; senders: string[]; - operation_type: string; + operationType: string; } export interface CommitRisk { score: number; rbf: Boolean; - low_fee: Boolean; + lowFee: Boolean; } diff --git a/lib/paramStyleConverter.ts b/lib/paramStyleConverter.ts new file mode 100644 index 0000000..e5dd2b0 --- /dev/null +++ b/lib/paramStyleConverter.ts @@ -0,0 +1,52 @@ +export const fromCamelCaseToSnakeCase = (obj: any): any => { + if (isArray(obj)) { + return (obj as any[]).map(val => fromCamelCaseToSnakeCase(val)); + } + if (isObject(obj)) { + return convertObjectKeysToSnakeCase(obj) + } + return obj; +} + +const convertObjectKeysToSnakeCase = (obj: { [x: string]: any }): { [x: string]: any } => { + const entries = Object.entries(obj).map(([key, value]) => { + return [camelCaseToSnakeCase(key), fromCamelCaseToSnakeCase(value)] + }); + return Object.fromEntries(entries); +} + +export const fromSnakeCaseToCamelCase = (obj: any): any => { + if (isArray(obj)) { + return (obj as any[]).map(val => fromSnakeCaseToCamelCase(val)); + } + if (isObject(obj)) { + return convertObjectKeysToCamelCase(obj) + } + return obj; +} + +const convertObjectKeysToCamelCase = (obj: { [x: string]: any }): { [x: string]: any } => { + const entries = Object.entries(obj).map(([key, value]) => { + return [snakeCaseToCamelCase(key), fromSnakeCaseToCamelCase(value)] + }); + return Object.fromEntries(entries); +} + + +const camelCaseToSnakeCase = (camelCase: string) => { + return camelCase.replace(/([A-Z])/g, letter => `_${letter.toLowerCase()}`); +} + +const snakeCaseToCamelCase = (value: string) => + value.toLowerCase().replace(/([_][a-z])/g, _letter => _letter.replace('_', '').toUpperCase() + ); + +const isObject = (value: any) => { + return typeof (value) == "object"; +} + +const isArray = (value: any) => { + return Array.isArray(value) +} + + diff --git a/test/objectKeyConverter.test.ts b/test/objectKeyConverter.test.ts new file mode 100644 index 0000000..529a389 --- /dev/null +++ b/test/objectKeyConverter.test.ts @@ -0,0 +1,76 @@ +import { expect } from "chai"; +import "mocha"; +import { fromCamelCaseToSnakeCase } from "../lib/camelCaseToSnakeCaseConverter"; +import { fromSnakeCaseToCamelCase } from "../lib/snakeCaseToCamelCaseConverter"; + +describe.only("convert from camel case to snake case", function () { + describe("convert object keys at base level", function () { + it("converts for diferent value tyeps", function () { + const camelCaseObj = { + currenciesAsAList: ["EOS", "CRO"], + symbol: "EOSCRO", + amount: 1.2, + activeSymbol: true, + statusOfSymbol: undefined, + } + const snake_case_obj = fromCamelCaseToSnakeCase(camelCaseObj); + expect(snake_case_obj).has.keys(["currencies_as_a_list", "symbol", "amount", "active_symbol", "status_of_symbol"]) + }); + }); + describe("convert object keys at inner levels", function () { + it("converts for diferent value tyeps", function () { + const camelCaseObj = { + symbol: { aSymbol: "EOSCRO", tickSize: "1" }, + } + const snake_case_obj = fromCamelCaseToSnakeCase(camelCaseObj); + expect(snake_case_obj.symbol).has.keys(["a_symbol", "tick_size"]) + }); + }); + describe("convert object keys inside lists", function () { + it("converts for diferent value tyeps", function () { + const camelCaseObj = { + currenciesAsAList: [{ code: "EOS", networkCode: "ETH" }, { code: "CRO", networkCode: ["netork 1", "network 2"] }], + } + const snake_case_obj = fromCamelCaseToSnakeCase(camelCaseObj); + (snake_case_obj["currencies_as_a_list"] as any[]).forEach(currency => + expect(currency).has.keys(["code", "network_code"])); + }); + }); +}); +describe.only("convert from camel case to snake case", function () { + describe("convert object keys at base level", function () { + it("converts for diferent value tyeps", function () { + const snake_case_obj = { + currencies_as_a_list: ["EOS", "CRO"], + symbol: "EOSCRO", + amount: 1.2, + active_symbol: true, + status_of_symbol: undefined, + } + const camel_case_obj = fromSnakeCaseToCamelCase(snake_case_obj); + expect(camel_case_obj).has.keys(["currenciesAsAList", "symbol", "amount", "activeSymbol", "statusOfSymbol"]) + }); + }); + describe("convert object keys at inner levels", function () { + it("converts for diferent value tyeps", function () { + const snake_case_obj = { + symbol: { a_symbol: "EOSCRO", tick_size: "1" }, + } + const camelCaseObj = fromSnakeCaseToCamelCase(snake_case_obj); + expect(camelCaseObj.symbol).has.keys(["aSymbol", "tickSize"]) + }); + }); + describe("convert object keys inside lists", function () { + it("converts for diferent value tyeps", function () { + const snake_case_obj = { + currencies_as_a_list: [ + { code: "EOS", network_code: "ETH" }, + { code: "CRO", network_code: ["netork 1", "network 2"] } + ], + } + const camelCaseObj = fromSnakeCaseToCamelCase(snake_case_obj); + (camelCaseObj["currenciesAsAList"] as any[]).forEach(currency => + expect(currency).has.keys(["code", "networkCode"])); + }); + }); +}); \ No newline at end of file diff --git a/test/rest/wallet_management.test.ts b/test/rest/wallet_management.test.ts index ad58d7b..905f9d7 100644 --- a/test/rest/wallet_management.test.ts +++ b/test/rest/wallet_management.test.ts @@ -133,7 +133,7 @@ describe("wallet management", () => { describe("get estimate withdrawal fee", () => { it("", async function () { this.timeout(0); - let fee = await client.getEstimateWithdrawFee({ + let fee = await client.getEstimateWithdrawalFee({ currency: "CRO", amount: "100", }); @@ -143,7 +143,7 @@ describe("wallet management", () => { describe("get estimates withdrawal fees", () => { it("", async function () { this.timeout(0); - let fees = await client.getEstimateWithdrawFees([ + let fees = await client.getEstimateWithdrawalFees([ { currency: "CRO", amount: "100" }, { currency: "EOS", amount: "12" } ]); From a495fd3870f16b30bc9e1f939a67a4a534545ed4 Mon Sep 17 00:00:00 2001 From: "T. Ismael Verdugo" Date: Mon, 12 Feb 2024 17:52:38 -0300 Subject: [PATCH 14/16] feat: camel case only --- lib/client.ts | 222 +++++++++--------- lib/models/Fee.ts | 2 +- lib/models/OrderBook.ts | 34 +++ lib/models/WSCandle.ts | 14 +- lib/models/WSTicker.ts | 17 ++ lib/models/WSTrades.ts | 8 + lib/paramStyleConverter.ts | 12 +- lib/websocket/DataParser.ts | 92 ++++++++ lib/websocket/authClient.ts | 4 +- lib/websocket/clientBase.ts | 7 +- lib/websocket/marketDataClient.ts | 53 +++-- lib/websocket/tradingClient.ts | 80 ++++--- lib/websocket/walletClient.ts | 36 +-- ...er.test.ts => paramStyleConverter.test.ts} | 7 +- ...market_data.test.ts => marketData.test.ts} | 2 +- ...ot_trading.test.ts => spotTrading.test.ts} | 16 +- ...ory.test.ts => spotTradingHistory.test.ts} | 2 +- ...ement.test.ts => walletManagement.test.ts} | 8 +- test/{test_helpers.ts => testHelpers.ts} | 153 +++++++----- ...market_data.test.ts => marketData.test.ts} | 4 +- ...test.ts => responsePromiseFactory.test.ts} | 0 test/websocket/trading.test.ts | 24 +- ...s.test.ts => tradingSubscriptions.test.ts} | 4 +- test/websocket/wallet.test.ts | 2 +- ...ns.test.ts => walletSubscriptions.test.ts} | 2 +- 25 files changed, 514 insertions(+), 291 deletions(-) create mode 100644 lib/websocket/DataParser.ts rename test/{objectKeyConverter.test.ts => paramStyleConverter.test.ts} (90%) rename test/rest/{market_data.test.ts => marketData.test.ts} (99%) rename test/rest/{spot_trading.test.ts => spotTrading.test.ts} (93%) rename test/rest/{spot_trading_history.test.ts => spotTradingHistory.test.ts} (94%) rename test/rest/{wallet_management.test.ts => walletManagement.test.ts} (97%) rename test/{test_helpers.ts => testHelpers.ts} (81%) rename test/websocket/{market_data.test.ts => marketData.test.ts} (99%) rename test/websocket/{response_promise_factory.test.ts => responsePromiseFactory.test.ts} (100%) rename test/websocket/{trading_subscriptions.test.ts => tradingSubscriptions.test.ts} (97%) rename test/websocket/{wallet_subscriptions.test.ts => walletSubscriptions.test.ts} (99%) diff --git a/lib/client.ts b/lib/client.ts index 93689e1..6bd1439 100644 --- a/lib/client.ts +++ b/lib/client.ts @@ -37,6 +37,7 @@ import { Trade, Transaction, } from "./models"; +import { fromCamelCaseToSnakeCase, fromSnakeCaseToCamelCase } from "./paramStyleConverter" @@ -50,27 +51,39 @@ export class Client { } async publicGet(endpoint: string, params: any) { - return this.httpClient.publicGet(endpoint, params); + const parsedParams = fromCamelCaseToSnakeCase(params) + const response = this.httpClient.publicGet(endpoint, parsedParams); + return fromSnakeCaseToCamelCase(response); } async get(endpoint: string, params: any | null = null) { - return this.httpClient.get(endpoint, params); + const parsedParams = fromCamelCaseToSnakeCase(params) + const response = await this.httpClient.get(endpoint, parsedParams); + return fromSnakeCaseToCamelCase(response); } async patch(endpoint: string, params: any) { - return this.httpClient.patch(endpoint, params); + const parsedParams = fromCamelCaseToSnakeCase(params) + const response = await this.httpClient.patch(endpoint, parsedParams); + return fromSnakeCaseToCamelCase(response); } async post(endpoint: string, params: any) { - return this.httpClient.post(endpoint, params); + const parsedParams = fromCamelCaseToSnakeCase(params) + const response = await this.httpClient.post(endpoint, parsedParams); + return fromSnakeCaseToCamelCase(response); } async delete(endpoint: string, params: any | null = null) { - return this.httpClient.delete(endpoint, params); + const parsedParams = fromCamelCaseToSnakeCase(params) + const response = await this.httpClient.delete(endpoint, parsedParams); + return fromSnakeCaseToCamelCase(response); } async put(endpoint: string, params: any | null = null) { - return this.httpClient.put(endpoint, params); + const parsedParams = fromCamelCaseToSnakeCase(params) + const response = await this.httpClient.put(endpoint, parsedParams); + return fromSnakeCaseToCamelCase(response); } ////////////////// @@ -85,13 +98,12 @@ export class Client { * https://api.exchange.cryptomkt.com/#currencies * * @param {string[]} [currencies] Optional. A list of currencies ids - * @param {string} [preferred_network] Optional. Code of the default network for currencies. + * @param {string} [preferredNetwork] Optional. Code of the default network for currencies. * * @return A list of available currencies */ - async getCurrencies(currencies?: string[], preferred_network?: string): Promise<{ [key: string]: Currency }> { - const response = await this.get("public/currency", { currencies, preferred_network }); - return response + getCurrencies(currencies?: string[], preferredNetwork?: string): Promise<{ [key: string]: Currency }> { + return this.get("public/currency", { currencies, preferredNetwork }); } /** @@ -507,12 +519,12 @@ export class Client { * * https://api.exchange.cryptomkt.com/#get-active-spot-order * - * @param {string} client_order_id The clientOrderId of the order + * @param {string} clientOrderId The clientOrderId of the order * * @return An order of the account */ - getActiveSpotOrder(client_order_id: string): Promise { - return this.get(`spot/order/${client_order_id}`); + getActiveSpotOrder(clientOrderId: string): Promise { + return this.get(`spot/order/${clientOrderId}`); } /** @@ -528,16 +540,16 @@ export class Client { * @param {string} params.symbol Trading symbol * @param {SIDE} params.side 'buy' or 'sell' * @param {string} params.quantity Order quantity - * @param {string} [params.client_order_id] Optional. If given must be unique within the trading day, including all active orders. If not given, is generated by the server + * @param {string} [params.clientOrderId] Optional. If given must be unique within the trading day, including all active orders. If not given, is generated by the server * @param {ORDER_TYPE} [params.type] Optional. 'limit', 'market', 'stopLimit', 'stopMarket', 'takeProfitLimit' or 'takeProfitMarket'. Default is 'limit' - * @param {TIME_IN_FORCE} [params.time_in_force] Optional. 'GTC', 'IOC', 'FOK', 'Day', 'GTD'. Default to 'GTC' + * @param {TIME_IN_FORCE} [params.timeInForce] Optional. 'GTC', 'IOC', 'FOK', 'Day', 'GTD'. Default to 'GTC' * @param {string} [params.price] Required for 'limit' and 'stopLimit'. limit price of the order - * @param {string} [params.stop_price] Required for 'stopLimit' and 'stopMarket' orders. stop price of the order - * @param {string} [params.expire_time] Required for orders with timeInForce = GDT - * @param {boolean} [params.strict_validate] Optional. If False, the server rounds half down for tickerSize and quantityIncrement. Example of ETHBTC: tickSize = '0.000001', then price '0.046016' is valid, '0.0460165' is invalid - * @param {boolean} [params.post_only] Optional. If True, your post_only order causes a match with a pre-existing order as a taker, then the order will be cancelled - * @param {string} [params.take_rate] Optional. Liquidity taker fee, a fraction of order volume, such as 0.001 (for 0.1% fee). Can only increase the fee. Used for fee markup. - * @param {string} [params.make_rate] Optional. Liquidity provider fee, a fraction of order volume, such as 0.001 (for 0.1% fee). Can only increase the fee. Used for fee markup. + * @param {string} [params.stopPrice] Required for 'stopLimit' and 'stopMarket' orders. stop price of the order + * @param {string} [params.expireTime] Required for orders with timeInForce = GDT + * @param {boolean} [params.strictValidate] Optional. If False, the server rounds half down for tickerSize and quantityIncrement. Example of ETHBTC: tickSize = '0.000001', then price '0.046016' is valid, '0.0460165' is invalid + * @param {boolean} [params.postOnly] Optional. If True, your post_only order causes a match with a pre-existing order as a taker, then the order will be cancelled + * @param {string} [params.takeRate] Optional. Liquidity taker fee, a fraction of order volume, such as 0.001 (for 0.1% fee). Can only increase the fee. Used for fee markup. + * @param {string} [params.makeRate] Optional. Liquidity provider fee, a fraction of order volume, such as 0.001 (for 0.1% fee). Can only increase the fee. Used for fee markup. * * @return A new spot order */ @@ -545,16 +557,16 @@ export class Client { symbol: string; side: SIDE; quantity: string; - client_order_id?: string; + clientOrderId?: string; type?: ORDER_TYPE; - time_in_force?: TIME_IN_FORCE; + timeInForce?: TIME_IN_FORCE; price?: string; - stop_price?: string; - expire_time?: string; - strict_validate?: boolean; - post_only?: boolean; - take_rate?: string; - make_rate?: string; + stopPrice?: string; + expireTime?: string; + strictValidate?: boolean; + postOnly?: boolean; + takeRate?: string; + makeRate?: string; }): Promise { return this.post("spot/order", params); } @@ -595,14 +607,14 @@ export class Client { * * https://api.exchange.cryptomkt.com/#create-new-spot-order-list * - * @param {string} params.order_list_id Order list identifier. If omitted, it will be generated by the system upon order list creation. Must be equal to client_order_id of the first order in the request. - * @param {string} params.contingency_type Order list type. + * @param {string} params.orderListId Order list identifier. If omitted, it will be generated by the system upon order list creation. Must be equal to client_order_id of the first order in the request. + * @param {string} params.contingencyType Order list type. * @param {OrderRequest[]} params.orders Orders in the list. * @return A promise that resolves with a list of reports of the created orders */ async createNewSpotOrderList(params: { - order_list_id?: string; - contingency_type: CONTINGENCY; + orderListId?: string; + contingencyType: CONTINGENCY; orders: OrderRequest[]; }): Promise { return this.post("spot/order/list", params); @@ -615,25 +627,25 @@ export class Client { * * https://api.exchange.cryptomkt.com/#replace-spot-order * - * @param {string} client_order_id client_order_id of the old order + * @param {string} clientOrderId client_order_id of the old order * @param {object} params Parameters - * @param {string} params.new_client_order_id client_order_id for the new order. + * @param {string} params.newClientOrderId client_order_id for the new order. * @param {string} params.quantity Order quantity. * @param {string} [params.price] Required if order type is limit, stopLimit, or takeProfitLimit. Order price. - * @param {boolean} [params.strict_validate] Optional. Price and quantity will be checked for incrementation within the symbol’s tick size and quantity step. See the symbol's tick_size and quantity_increment. + * @param {boolean} [params.strictValidate] Optional. Price and quantity will be checked for incrementation within the symbol’s tick size and quantity step. See the symbol's tick_size and quantity_increment. * * @returns the new spot order */ replaceSpotOrder( - client_order_id: string, + clientOrderId: string, params: { - new_client_order_id: string; + newClientOrderId: string; quantity: string; price?: string; strictValidate?: boolean; } ): Promise { - return this.patch(`spot/order/${client_order_id}`, params); + return this.patch(`spot/order/${clientOrderId}`, params); } /** @@ -656,12 +668,12 @@ export class Client { * * https://api.exchange.cryptomkt.com/#cancel-spot-order * - * @param {string} client_order_id the client_order_id of the order to cancel + * @param {string} clientOrderId the client_order_id of the order to cancel * * @return The canceled order */ - cancelSpotOrder(client_order_id: string): Promise { - return this.delete(`spot/order/${client_order_id}`); + cancelSpotOrder(clientOrderId: string): Promise { + return this.delete(`spot/order/${clientOrderId}`); } /** @@ -840,12 +852,12 @@ export class Client { * https://api.exchange.cryptomkt.com/#generate-deposit-crypto-address * * @param {string} currency currency to create a new address - * @param {string} network_code Optional. network code + * @param {string} networkCode Optional. network code * * @return The created address for the currency */ - createDepositCryptoAddress(currency: string, network_code?: string): Promise
{ - return this.post(`wallet/crypto/address`, { currency, network_code }); + createDepositCryptoAddress(currency: string, networkCode?: string): Promise
{ + return this.post(`wallet/crypto/address`, { currency, networkCode }); } /** @@ -858,12 +870,12 @@ export class Client { * https://api.exchange.cryptomkt.com/#last-10-deposit-crypto-addresses * * @param {string} currency currency to get the list of addresses - * @param {string} [network_code] Optional. network code + * @param {string} [networkCode] Optional. network code * * @return A list of addresses */ - getLast10DepositCryptoAddresses(currency: string, network_code?: string): Promise { - return this.get(`wallet/crypto/address/recent-deposit`, { currency, network_code }); + getLast10DepositCryptoAddresses(currency: string, networkCode?: string): Promise { + return this.get(`wallet/crypto/address/recent-deposit`, { currency, networkCode }); } /** @@ -876,12 +888,12 @@ export class Client { * https://api.exchange.cryptomkt.com/#last-10-withdrawal-crypto-addresses * * @param {string} currency currency to get the list of addresses - * @param {string} [network_code] Optional. network code + * @param {string} [networkCode] Optional. network code * * @return A list of addresses */ - getLast10WithdrawalCryptoAddresses(currency: string, network_code?: string): Promise { - return this.get(`wallet/crypto/address/recent-withdraw`, { currency, network_code }); + getLast10WithdrawalCryptoAddresses(currency: string, networkCode?: string): Promise { + return this.get(`wallet/crypto/address/recent-withdraw`, { currency, networkCode }); } /** @@ -904,10 +916,10 @@ export class Client { * @param {string} params.currency currency code of the crypto to withdraw * @param {string} params.amount the amount to be sent to the specified address * @param {string} params.address the address identifier - * @param {string} [params.network_code] Optional. network code - * @param {string} [params.payment_id] Optional. - * @param {boolean} [params.include_fee] Optional. If true then the total spent amount includes fees. Default false - * @param {boolean} [params.auto_commit] Optional. If false then you should commit or rollback transaction in an hour. Used in two phase commit schema. Default true + * @param {string} [params.networkCode] Optional. network code + * @param {string} [params.paymentId] Optional. + * @param {boolean} [params.includeFee] Optional. If true then the total spent amount includes fees. Default false + * @param {boolean} [params.autoCommit] Optional. If false then you should commit or rollback transaction in an hour. Used in two phase commit schema. Default true * @param {USE_OFFCHAIN} [params.use_offchain] Whether the withdrawal may be committed offchain. 'never', 'optionally', 'required' Accepted values: never, optionally, required * @param {string} [params.public_comment] Optional. Maximum length is 255. @@ -918,12 +930,12 @@ Accepted values: never, optionally, required currency: string; amount: string; address: string; - network_code?: string - paymend_id?: string; - include_fee?: boolean; - auto_commit?: boolean; - use_offchain?: USE_OFFCHAIN; - public_comment?: string; + networkCode?: string + paymendId?: string; + includeFee?: boolean; + autoCommit?: boolean; + useOffchain?: USE_OFFCHAIN; + publicComment?: string; }): Promise { const response = await this.post("wallet/crypto/withdraw", params); return response["id"]; @@ -986,14 +998,14 @@ Accepted values: never, optionally, required * @param {object} params * @param {string} params.currency the currency code for withdraw * @param {string} params.amount the expected withdraw amount - * @param {string} [params.netwrok_code] Optional. Network code + * @param {string} [params.netwrokCode] Optional. Network code * * @return The expected fee */ async getEstimateWithdrawalFee(params: { currency: string; amount: string; - network_code?: string; + networkCode?: string; }): Promise { const response = await this.get("wallet/crypto/fee/estimate", params); return response["fee"]; @@ -1011,15 +1023,15 @@ Accepted values: never, optionally, required * https://api.exchange.cryptomkt.com/#convert-between-currencies * * @param {object} params - * @param {string} params.from_currency currency code of origin - * @param {string} params.to_currency currency code of destiny + * @param {string} params.fromCurrency currency code of origin + * @param {string} params.toCurrency currency code of destiny * @param {string} params.amount the amount to be sent * * @return A list of transaction identifiers of the convertion */ async convertBetweenCurrencies(params: { - from_currency: string; - to_currency: string; + fromCurrency: string; + toCurrency: string; amount: string; }): Promise { const response = await this.post("wallet/convert", params); @@ -1111,40 +1123,40 @@ Accepted values: wallet, spot. Must not be the same as source * * * @param {object} [params] - * @param {string[]} [params.tx_ids] Optional. List of transaction identifiers to query + * @param {string[]} [params.txIds] Optional. List of transaction identifiers to query * @param {TRANSACTION_TYPE[]} [params.types] Optional. List of types to query. valid types are: 'DEPOSIT', 'WITHDRAW', 'TRANSFER' and 'SWAP' * @param {TRANSACTION_SUBTYPE[]} [params.subtypes] Optional. List of subtypes to query. valid subtypes are: 'UNCLASSIFIED', 'BLOCKCHAIN', 'AIRDROP', 'AFFILIATE', 'STAKING', 'BUY_CRYPTO', 'OFFCHAIN', 'FIAT', 'SUB_ACCOUNT', 'WALLET_TO_SPOT', 'SPOT_TO_WALLET', 'WALLET_TO_DERIVATIVES', 'DERIVATIVES_TO_WALLET', 'CHAIN_SWITCH_FROM', 'CHAIN_SWITCH_TO' and 'INSTANT_EXCHANGE' * @param {TRANSACTION_STATUS[]} [params.statuses] Optional. List of statuses to query. valid subtypes are: 'CREATED', 'PENDING', 'FAILED', 'SUCCESS' and 'ROLLED_BACK' * @param {string[]} [params.currencies] Optional. List of currencies of the transactions * @param {string[]} [params.networks] Optional. List of network codes - * @param {SORT_BY} [params.order_by] Optional. Defines the sorting type.'created_at' or 'id'. Default is 'created_at' + * @param {SORT_BY} [params.orderBy] Optional. Defines the sorting type.'created_at' or 'id'. Default is 'created_at' * @param {string} [params.from] Optional. Interval initial value when ordering by 'created_at'. As Datetime * @param {string} [params.till] Optional. Interval end value when ordering by 'created_at'. As Datetime - * @param {string} [params.id_from] Optional. Interval initial value when ordering by id. Min is 0 - * @param {string} [params.id_till] Optional. Interval end value when ordering by id. Min is 0 + * @param {string} [params.idFrom] Optional. Interval initial value when ordering by id. Min is 0 + * @param {string} [params.idTill] Optional. Interval end value when ordering by id. Min is 0 * @param {string} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC'. * @param {number} [params.limit] Optional. Transactions per query. Defaul is 100. Max is 1000 * @param {number} [params.offset] Optional. Default is 0. Max is 100000 - * @param {Boolean} [params.group_transactions] Optional. Flag indicating whether the returned transactions will be parts of a single operation + * @param {Boolean} [params.groupTransactions] Optional. Flag indicating whether the returned transactions will be parts of a single operation * * @return A list of transactions */ getTransactionHistory(params?: { - tx_ids?: string[]; + txIds?: string[]; types?: TRANSACTION_TYPE[]; subtypes?: TRANSACTION_SUBTYPE[]; statuses?: TRANSACTION_STATUS[]; currencies?: string[], networks?: string[], - order_by?: SORT_BY; + orderBy?: SORT_BY; from?: string; till?: string; - id_from?: string; - id_till?: string; + idFrom?: string; + idTill?: string; sort?: string; limit?: number; offset?: number; - group_transactions?:Boolean + groupTransactions?: Boolean }): Promise { return this.get("wallet/transactions", params); } @@ -1174,14 +1186,14 @@ Accepted values: wallet, spot. Must not be the same as source * @param {object} params * @param {string} params.currency currency code * @param {string} params.address address identifier - * @param {string} [params.payment_id] Optional + * @param {string} [params.paymentId] Optional * * @return True if the offchain is available */ async checkIfOffchainIsAvailable(params: { currency: string; address: string; - payment_id?: string; + paymentId?: string; }): Promise { const response = await this.post( `wallet/crypto/check-offchain-available`, @@ -1250,12 +1262,12 @@ Accepted values: wallet, spot. Must not be the same as source * * https://api.exchange.cryptomkt.com/#freeze-sub-account * - * @param {string[]} sub_account_ids A list of sub-account IDs to freeze + * @param {string[]} subAccountIds A list of sub-account IDs to freeze * * @return true if the freeze was successful */ - async freezeSubAccounts(sub_account_ids: string[]): Promise { - const response = await this.post("sub-account/freeze", { sub_account_ids }); + async freezeSubAccounts(subAccountIds: string[]): Promise { + const response = await this.post("sub-account/freeze", { subAccountIds }); return response["result"]; } @@ -1266,13 +1278,13 @@ Accepted values: wallet, spot. Must not be the same as source * * https://api.exchange.cryptomkt.com/#activate-sub-account * - * @param {string[]} sub_account_ids A list of account IDs to activate + * @param {string[]} subAccountIds A list of account IDs to activate * * @return true if the activation was successful */ - async activateSubAccounts(sub_account_ids: string[]): Promise { + async activateSubAccounts(subAccountIds: string[]): Promise { const response = await this.post("sub-account/activate", { - sub_account_ids, + sub_account_ids: subAccountIds, }); return response["result"]; } @@ -1285,7 +1297,7 @@ Accepted values: wallet, spot. Must not be the same as source * https://api.exchange.cryptomkt.com/#transfer-funds * * @param {object} params Parameters - * @param {number} params.sub_account_id The account ID of the account to transfer from or to + * @param {number} params.subAccountId The account ID of the account to transfer from or to * @param {number} params.amount the amount of currency to transfer * @param {string} params.currency the currency to transfer * @param {TRANSFER_TYPE} params.type the type of transfer. TransferType.TO_SUB_ACCOUNT or TransferType.FROM_SUB_ACCOUNT @@ -1293,7 +1305,7 @@ Accepted values: wallet, spot. Must not be the same as source * @return The transaction ID of the tranfer */ async transferFunds(params: { - sub_account_id: number; + subAccountId: number; amount: number; currency: string; type: TRANSFER_TYPE; @@ -1309,12 +1321,12 @@ Accepted values: wallet, spot. Must not be the same as source * * https://api.exchange.cryptomkt.com/#get-acl-settings * - * @param {string[]} sub_account_ids The list of sub-account IDs of the sub-accounts to get the ACL settings. The subAccountID filed is ignored. + * @param {string[]} subAccountIds The list of sub-account IDs of the sub-accounts to get the ACL settings. The subAccountID filed is ignored. * * @return A list of withdraw settings for sub-accounts listed */ - async getACLSettings(sub_account_ids: string[]): Promise { - const response = await this.get("sub-account/acl", { sub_account_ids }); + async getACLSettings(subAccountIds: string[]): Promise { + const response = await this.get("sub-account/acl", { subAccountIds }); return response["result"]; } @@ -1326,21 +1338,21 @@ Accepted values: wallet, spot. Must not be the same as source * https://api.exchange.cryptomkt.com/#change-acl-settings * * @param {object} params Parameters - * @param {string[]} params.sub_account_ids The list of sub-account IDs to change the ACL settings - * @param {boolean} [params.deposit_address_generation_enabled] value indicaiting permission for deposits - * @param {boolean} [params.withdraw_enabled] value indicating permission for withdrawals + * @param {string[]} params.subAccountIds The list of sub-account IDs to change the ACL settings + * @param {boolean} [params.depositAddressGenerationEnabled] value indicaiting permission for deposits + * @param {boolean} [params.withdrawEnabled] value indicating permission for withdrawals * @param {string} [params.description] Textual description. - * @param {string} [params.created_at] ACL creation time - * @param {string} [params.updated_at] ACL update time + * @param {string} [params.createdAt] ACL creation time + * @param {string} [params.updatedAt] ACL update time * @return The list of the updated withdraw settings of the changed sub-account */ async changeACLSettings(params: { - sub_account_ids: string[]; - deposit_address_generation_enabled?: boolean; + subAccountIds: string[]; + depositAddressGenerationEnabled?: boolean; withdraw_enabled?: boolean; description?: string; - created_at?: string; - updated_at?: string; + createdAt?: string; + updatedAt?: string; }): Promise { const response = await this.post("sub-account/acl", params); return response["result"]; @@ -1357,14 +1369,14 @@ Accepted values: wallet, spot. Must not be the same as source * * https://api.exchange.cryptomkt.com/#get-sub-account-balance * - * @param {string} sub_account_id the sub-account ID of the sub-account to get the balances + * @param {string} subAccountId the sub-account ID of the sub-account to get the balances * * @return A list of balances of the sub-account */ async getSubAccountBalance( - sub_account_id: string + subAccountId: string ): Promise { - const response = await this.get(`sub-account/balance/${sub_account_id}`); + const response = await this.get(`sub-account/balance/${subAccountId}`); return response["result"]; } @@ -1375,17 +1387,17 @@ Accepted values: wallet, spot. Must not be the same as source * * https://api.exchange.cryptomkt.com/#get-sub-account-crypto-address * - * @param {string} sub_account_id the sub-account ID to get the crypto address + * @param {string} subAccountId the sub-account ID to get the crypto address * @param {string} currency currency code to get the crypto address * * @return The crypto address */ async getSubAccountCryptoAddress( - sub_account_id: string, + subAccountId: string, currency: string ): Promise { const response = await this.get( - `sub-account/crypto/address/${sub_account_id}/${currency}` + `sub-account/crypto/address/${subAccountId}/${currency}` ); return response["result"]; } diff --git a/lib/models/Fee.ts b/lib/models/Fee.ts index 7945cf3..5ff1915 100644 --- a/lib/models/Fee.ts +++ b/lib/models/Fee.ts @@ -2,5 +2,5 @@ export interface Fee { fee: string; currency: string; amount: string; - networkCode: string; + networkFee: string; } diff --git a/lib/models/OrderBook.ts b/lib/models/OrderBook.ts index 802eee8..7c1d8be 100644 --- a/lib/models/OrderBook.ts +++ b/lib/models/OrderBook.ts @@ -1,4 +1,11 @@ export interface WSOrderBook { + timestamp: number; + sequence: number; + asks: OrderBookLevel[]; + bids: OrderBookLevel[]; +} + +export interface WSOrderBookRaw { /** * timetsmap */ @@ -20,14 +27,41 @@ export interface WSOrderBook { export type OrderBookLevel = [price: number, amount: number]; export interface OrderBookTop { + timestamp: number; + bestAsk: string; + bestAskQuantity: string; + bestBid: string; + bestBidQuantity: string; +} + +export interface OrderBookTopRaw { + /** + * timestamp + */ t: number; + /** + * best ask + */ a: string; + /** + * best ask quantity + */ A: string; + /** + * best bid + */ b: string; + /** + * best bid quantity + */ B: string; } export interface PriceRate { + timestamp: number, + rate: string +} +export interface PriceRateRaw { /** * timestamp */ diff --git a/lib/models/WSCandle.ts b/lib/models/WSCandle.ts index 46c7fad..528ce5a 100644 --- a/lib/models/WSCandle.ts +++ b/lib/models/WSCandle.ts @@ -1,4 +1,16 @@ export interface WSCandle { + timestamp: number; + openPrice: string; + closePrice: string; + highPrice: string; + lowPrice: string; + baseVolume: string; + quoteVolume: string; +} + +export type MiniTicker = WSCandle; + +export interface WSCandleRaw { /** * timestamp */ @@ -28,5 +40,3 @@ export interface WSCandle { */ q: string; } - -export type MiniTicker = WSCandle; \ No newline at end of file diff --git a/lib/models/WSTicker.ts b/lib/models/WSTicker.ts index 655a5c9..eec9ef8 100644 --- a/lib/models/WSTicker.ts +++ b/lib/models/WSTicker.ts @@ -1,4 +1,21 @@ export interface WSTicker { + timestamp: number; + bestAsk: string; + bestAskQuantity: string; + bestBid: string; + bestBidQuantity: string; + closePrice: string; + openPrice: string; + highPrice: string; + lowPrice: string; + baseVolume: string; + quoteVolume: string; + priceChange: string; + PriceChangePercent: string; + lastTradeId: number; +} + +export interface WSTickerRaw { /** * timestamp */ diff --git a/lib/models/WSTrades.ts b/lib/models/WSTrades.ts index 0d57817..7705d0e 100644 --- a/lib/models/WSTrades.ts +++ b/lib/models/WSTrades.ts @@ -1,6 +1,14 @@ import { SIDE } from "../constants"; export interface WSTrade { + timestamp: number; + id: number; + price: string; + quantity: string; + side: SIDE; +} + +export interface WSTradeRaw { /** * timestamp */ diff --git a/lib/paramStyleConverter.ts b/lib/paramStyleConverter.ts index e5dd2b0..100d802 100644 --- a/lib/paramStyleConverter.ts +++ b/lib/paramStyleConverter.ts @@ -1,4 +1,7 @@ export const fromCamelCaseToSnakeCase = (obj: any): any => { + if (isNull(obj)) { + return obj + } if (isArray(obj)) { return (obj as any[]).map(val => fromCamelCaseToSnakeCase(val)); } @@ -16,6 +19,9 @@ const convertObjectKeysToSnakeCase = (obj: { [x: string]: any }): { [x: string]: } export const fromSnakeCaseToCamelCase = (obj: any): any => { + if (isNull(obj)) { + return obj + } if (isArray(obj)) { return (obj as any[]).map(val => fromSnakeCaseToCamelCase(val)); } @@ -38,7 +44,7 @@ const camelCaseToSnakeCase = (camelCase: string) => { } const snakeCaseToCamelCase = (value: string) => - value.toLowerCase().replace(/([_][a-z])/g, _letter => _letter.replace('_', '').toUpperCase() + value.replace(/([_][a-z])/g, _letter => _letter.replace('_', '').toUpperCase() ); const isObject = (value: any) => { @@ -50,3 +56,7 @@ const isArray = (value: any) => { } +function isNull(obj: any) { + return obj === undefined || obj === null +} + diff --git a/lib/websocket/DataParser.ts b/lib/websocket/DataParser.ts new file mode 100644 index 0000000..3de0dfc --- /dev/null +++ b/lib/websocket/DataParser.ts @@ -0,0 +1,92 @@ +import { NOTIFICATION_TYPE } from "../constants"; +import { OrderBookTop, OrderBookTopRaw, PriceRate, PriceRateRaw, WSCandle, WSCandleRaw, WSOrderBook, WSOrderBookRaw, WSTicker, WSTickerRaw, WSTrade, WSTradeRaw } from "../models"; + +type JsonObject = { [x: string]: T }; +type MapNotificationCallback = (notification: JsonObject, notificationType: NOTIFICATION_TYPE) => any; +type ParseFn = (raw: TRaw) => T + +export const parseMapListInterceptor = ( + callback: MapNotificationCallback, parseFn: ParseFn) => ( + notification: JsonObject, notificationType: NOTIFICATION_TYPE) => { + const parsedEntries = Object.entries(notification).map(([key, rawValueList]) => [key, rawValueList.map(parseFn)]) + const parsedNotification = Object.fromEntries(parsedEntries) + return callback(parsedNotification, notificationType) + } + +export const parseMapInterceptor = ( + callback: MapNotificationCallback, parseFn: ParseFn) => ( + notification: JsonObject, notificationType: NOTIFICATION_TYPE) => { + const parsedEntries = Object.entries(notification).map(([key, rawValue]) => [key, parseFn(rawValue)]) + const parsedNotification = Object.fromEntries(parsedEntries) + return callback(parsedNotification, notificationType) + } + + +export const parseWSTrade = (wsTradeRaw: WSTradeRaw): WSTrade => { + return { + timestamp: wsTradeRaw.t, + id: wsTradeRaw.i, + price: wsTradeRaw.p, + quantity: wsTradeRaw.q, + side: wsTradeRaw.s, + } +} + +export const parseWSCandle = (wsCandleRaw: WSCandleRaw): WSCandle => { + return { + timestamp: wsCandleRaw.t, + openPrice: wsCandleRaw.o, + closePrice: wsCandleRaw.c, + highPrice: wsCandleRaw.h, + lowPrice: wsCandleRaw.l, + baseVolume: wsCandleRaw.v, + quoteVolume: wsCandleRaw.q, + } +} + + +export const parseWSTicker = (wsTickerRaw: WSTickerRaw): WSTicker => { + return { + timestamp: wsTickerRaw.t, + bestAsk: wsTickerRaw.a, + bestAskQuantity: wsTickerRaw.A, + bestBid: wsTickerRaw.b, + bestBidQuantity: wsTickerRaw.B, + closePrice: wsTickerRaw.c, + openPrice: wsTickerRaw.o, + highPrice: wsTickerRaw.h, + lowPrice: wsTickerRaw.l, + baseVolume: wsTickerRaw.v, + quoteVolume: wsTickerRaw.q, + priceChange: wsTickerRaw.p, + PriceChangePercent: wsTickerRaw.P, + lastTradeId: wsTickerRaw.L, + } +} + + +export const parseWSOrderbook = (wsOrderbookRaw: WSOrderBookRaw): WSOrderBook => { + return { + timestamp: wsOrderbookRaw.t, + sequence: wsOrderbookRaw.s, + asks: wsOrderbookRaw.a, + bids: wsOrderbookRaw.b, + } +} + +export const parseOrderbookTop = (orderbookTopRaw: OrderBookTopRaw): OrderBookTop => { + return { + timestamp: orderbookTopRaw.t, + bestAsk: orderbookTopRaw.a, + bestAskQuantity: orderbookTopRaw.A, + bestBid: orderbookTopRaw.b, + bestBidQuantity: orderbookTopRaw.B, + } +} + +export const parsePriceRate = (priceRateRaw: PriceRateRaw): PriceRate => { + return { + timestamp: priceRateRaw.t, + rate: priceRateRaw.r, + } +} \ No newline at end of file diff --git a/lib/websocket/authClient.ts b/lib/websocket/authClient.ts index 9b2cc00..36b6ca4 100644 --- a/lib/websocket/authClient.ts +++ b/lib/websocket/authClient.ts @@ -3,7 +3,7 @@ import { WSClientBase } from "./clientBase"; interface AuthPayload { type: string - api_Key: string + api_key: string timestamp: number window?: number signature: string @@ -53,7 +53,7 @@ export class AuthClient extends WSClientBase { const timestamp = Math.floor(Date.now()); const payload: AuthPayload = { type: "HS256", - api_Key: this.apiKey, + api_key: this.apiKey, timestamp: timestamp, signature: "", }; diff --git a/lib/websocket/clientBase.ts b/lib/websocket/clientBase.ts index 3fe2e31..434467c 100644 --- a/lib/websocket/clientBase.ts +++ b/lib/websocket/clientBase.ts @@ -2,6 +2,7 @@ import { EventEmitter } from "events"; import WebSocket from "ws"; import { ResponsePromiseFactory } from "./ResponsePromiseFactory"; import { WSResponse } from "./WSResponse"; +import { fromCamelCaseToSnakeCase } from "../paramStyleConverter"; export class WSClientBase { private uri: string; @@ -101,13 +102,15 @@ export class WSClientBase { return response as Boolean; } - protected request({ method, params = {} }: { method: any; params?: {}; }): Promise { + protected request({ method, params: paramsRaw = {} }: { method: any; params?: {}; }): Promise { + const params = fromCamelCaseToSnakeCase(paramsRaw) const { id, promise } = this.multiResponsePromiseFactory.newOneResponsePromise(); const payload = { method, params, id }; this.ws.send(JSON.stringify(payload)); return withTimeout(this.requestTimeoutMs, promise); } - protected requestList({ method, params = {}, responseCount = 1 }: { method: any; params?: {}; responseCount?: number; }): Promise { + protected requestList({ method, params: paramsRaw = {}, responseCount = 1 }: { method: any; params?: {}; responseCount?: number; }): Promise { + const params = fromCamelCaseToSnakeCase(paramsRaw) const { id, promise } = this.multiResponsePromiseFactory.newMultiResponsePromise(responseCount); const payload = { method, params, id }; this.ws.send(JSON.stringify(payload)); diff --git a/lib/websocket/marketDataClient.ts b/lib/websocket/marketDataClient.ts index ea7f03c..38cd35e 100644 --- a/lib/websocket/marketDataClient.ts +++ b/lib/websocket/marketDataClient.ts @@ -19,8 +19,11 @@ import { WSOrderBook, WSTicker, WSTrade, + WSTradeRaw, } from "../models"; +import { parseMapInterceptor, parseMapListInterceptor, parseOrderbookTop, parsePriceRate, parseWSCandle, parseWSOrderbook, parseWSTicker, parseWSTrade } from "./DataParser"; import { WSClientBase } from "./clientBase"; +import { fromCamelCaseToSnakeCase } from "../paramStyleConverter" /** * MarketDataClient connects via websocket to cryptomarket to get market information of the exchange. @@ -109,7 +112,7 @@ export class MarketDataClient extends WSClientBase { const { subscriptions } = (await this.sendChanneledSubscription({ channel, callback, - params, + params: fromCamelCaseToSnakeCase(params), })) as { subscriptions: string[]; }; @@ -148,7 +151,7 @@ export class MarketDataClient extends WSClientBase { }): Promise { return this.makeSubscription({ channel: "trades", - callback, + callback: parseMapListInterceptor(callback, parseWSTrade), params, }); } @@ -185,7 +188,7 @@ export class MarketDataClient extends WSClientBase { }): Promise { return this.makeSubscription({ channel: `candles/${period}`, - callback, + callback: parseMapListInterceptor(callback, parseWSCandle), params, }); } @@ -218,7 +221,7 @@ export class MarketDataClient extends WSClientBase { }): Promise { return this.makeSubscription({ channel: `ticker/price/${speed}`, - callback, + callback: parseMapInterceptor(callback, parseWSCandle), params, withDefaultSymbols: true, }); @@ -252,7 +255,7 @@ export class MarketDataClient extends WSClientBase { }): Promise { return this.makeSubscription({ channel: `ticker/price/${speed}/batch`, - callback, + callback: parseMapListInterceptor(callback, parseWSCandle), params, withDefaultSymbols: true, }); @@ -286,7 +289,7 @@ export class MarketDataClient extends WSClientBase { }): Promise { return this.makeSubscription({ channel: `ticker/${speed}`, - callback, + callback: parseMapInterceptor(callback, parseWSTicker), params, withDefaultSymbols: true, }); @@ -313,14 +316,14 @@ export class MarketDataClient extends WSClientBase { params: { speed, ...params }, }: { callback: ( - notification: { [x: string]: Ticker[] }, + notification: { [x: string]: WSTicker }, type: NOTIFICATION_TYPE ) => any; params: { speed: TICKER_SPEED; symbols?: string[] }; }): Promise { return this.makeSubscription({ channel: `ticker/${speed}/batch`, - callback, + callback: parseMapInterceptor(callback, parseWSTicker), params, withDefaultSymbols: true, }); @@ -353,7 +356,7 @@ export class MarketDataClient extends WSClientBase { }): Promise { return this.makeSubscription({ channel: "orderbook/full", - callback, + callback: parseMapInterceptor(callback, parseWSOrderbook), params, }); } @@ -391,7 +394,7 @@ export class MarketDataClient extends WSClientBase { }): Promise { return this.makeSubscription({ channel: `orderbook/${depth}/${speed}`, - callback, + callback: parseMapInterceptor(callback, parseWSOrderbook), params, withDefaultSymbols: true, }); @@ -417,7 +420,7 @@ export class MarketDataClient extends WSClientBase { params: { speed, depth, ...params }, }: { callback: ( - notification: { [x: string]: WSOrderBook[] }, + notification: { [x: string]: WSOrderBook }, type: NOTIFICATION_TYPE ) => any; params: { @@ -428,7 +431,7 @@ export class MarketDataClient extends WSClientBase { }): Promise { return this.makeSubscription({ channel: `orderbook/${depth}/${speed}/batch`, - callback, + callback: parseMapInterceptor(callback, parseWSOrderbook), params, withDefaultSymbols: true, }); @@ -453,7 +456,7 @@ export class MarketDataClient extends WSClientBase { params: { speed, ...params }, }: { callback: ( - notification: { [x: string]: OrderBookTop[] }, + notification: { [x: string]: OrderBookTop }, type: NOTIFICATION_TYPE ) => any; params: { @@ -463,7 +466,7 @@ export class MarketDataClient extends WSClientBase { }): Promise { return this.makeSubscription({ channel: `orderbook/top/${speed}`, - callback, + callback: parseMapInterceptor(callback, parseOrderbookTop), params, withDefaultSymbols: true, }); @@ -488,7 +491,7 @@ export class MarketDataClient extends WSClientBase { params: { speed, ...params }, }: { callback: ( - notification: { [x: string]: OrderBookTop[] }, + notification: { [x: string]: OrderBookTop }, type: NOTIFICATION_TYPE ) => any; params: { @@ -498,7 +501,7 @@ export class MarketDataClient extends WSClientBase { }): Promise { return this.makeSubscription({ channel: `orderbook/top/${speed}/batch`, - callback, + callback: parseMapInterceptor(callback, parseOrderbookTop), params, withDefaultSymbols: true, }); @@ -514,9 +517,9 @@ export class MarketDataClient extends WSClientBase { * https://api.exchange.cryptomkt.com/#subscribe-to-price-rates * * @param {function} callback recieves a feed of price rates as a map of them, indexed by currency id, and the type of notification, only DATA - * @param {PRICE_RATE_SPEED} speed The speed of the feed. '1s' or '3s' - * @param {string} target_currency quote currency of the rate - * @param {string} currencies Optional. a list of base currencies to get rates. If omitted, subscribe to all currencies + * @param {PRICE_RATE_SPEED} params.speed The speed of the feed. '1s' or '3s' + * @param {string} params.targetCurrency quote currency of the rate + * @param {string} params.currencies Optional. a list of base currencies to get rates. If omitted, subscribe to all currencies * @return A promise that resolves when subscribed with a list of the successfully subscribed currencies */ subscribeToPriceRates({ @@ -529,13 +532,13 @@ export class MarketDataClient extends WSClientBase { ) => any; params: { speed: PRICE_RATE_SPEED; - target_currency: string, + targetCurrency: string, currencies?: string[]; }; }): Promise { return this.makeSubscription({ channel: `price/rate/${speed}`, - callback, + callback: parseMapInterceptor(callback, parsePriceRate), params, withDefaultCurrencies: true, }); @@ -551,9 +554,9 @@ export class MarketDataClient extends WSClientBase { * https://api.exchange.cryptomkt.com/#subscribe-to-price-rates-in-batches * * @param {function} callback recieves a feed of price rates as a map of them, indexed by currency id, and the type of notification, only DATA - * @param {PRICE_RATE_SPEED} speed The speed of the feed. '1s' or '3s' - * @param {string} target_currency quote currency of the rate - * @param {string} currencies Optional. a list of base currencies to get rates. If omitted, subscribe to all currencies + * @param {PRICE_RATE_SPEED} params.speed The speed of the feed. '1s' or '3s' + * @param {string} params.target_currency quote currency of the rate + * @param {string} params.currencies Optional. a list of base currencies to get rates. If omitted, subscribe to all currencies * @return A promise that resolves when subscribed with a list of the successfully subscribed currencies */ subscribeToPriceRatesInBatches({ @@ -572,7 +575,7 @@ export class MarketDataClient extends WSClientBase { }): Promise { return this.makeSubscription({ channel: `price/rate/${speed}/batch`, - callback, + callback: parseMapInterceptor(callback, parsePriceRate), params, withDefaultCurrencies: true, }); diff --git a/lib/websocket/tradingClient.ts b/lib/websocket/tradingClient.ts index 9f90c8f..5031fc5 100644 --- a/lib/websocket/tradingClient.ts +++ b/lib/websocket/tradingClient.ts @@ -1,5 +1,3 @@ -import { AuthClient } from "./authClient"; -import { Balance, Commission, Order, OrderRequest, Report } from "../models"; import { CONTINGENCY, NOTIFICATION_TYPE, @@ -7,6 +5,9 @@ import { SUBSCRIPTION_MODE, TIME_IN_FORCE, } from "../constants"; +import { Balance, Commission, OrderRequest, Report } from "../models"; +import { fromSnakeCaseToCamelCase } from "../paramStyleConverter"; +import { AuthClient } from "./authClient"; const reportsKey = "reports"; const balanceKey = "balance"; @@ -52,7 +53,8 @@ export class TradingClient extends AuthClient { * */ async getActiveSpotOrders(): Promise { - return this.makeRequest({ method: "spot_get_orders" }); + const reports = await this.makeRequest({ method: "spot_get_orders" }); + return fromSnakeCaseToCamelCase(reports) } /** @@ -66,37 +68,38 @@ export class TradingClient extends AuthClient { * @param {string} params.symbol Trading symbol * @param {string} params.side Either 'buy' or 'sell' * @param {string} params.quantity Order quantity - * @param {string} [params.client_order_id] Optional. If given must be unique within the trading day, including all active orders. If not given, is generated by the server + * @param {string} [params.clientOrderId] Optional. If given must be unique within the trading day, including all active orders. If not given, is generated by the server * @param {ORDER_TYPE} [params.type] Optional. 'limit', 'market', 'stopLimit', 'stopMarket', 'takeProfitLimit' or 'takeProfitMarket'. Default is 'limit' * @param {string} [params.price] Optional. Required for 'limit' and 'stopLimit'. limit price of the order - * @param {string} [params.stop_price] Optional. Required for 'stopLimit' and 'stopMarket' orders. stop price of the order - * @param {TIME_IN_FORCE} [params.time_in_force] Optional. 'GTC', 'IOC', 'FOK', 'Day', 'GTD'. Default to 'GTC' - * @param {string} [params.expire_time] Optional. Required for orders with timeInForce = GDT - * @param {boolean} [params.strict_validate] Optional. If False, the server rounds half down for tickerSize and quantityIncrement. Example of ETHBTC: tickSize = '0.000001', then price '0.046016' is valid, '0.0460165' is invalid - * @param {boolean} [params.post_only] Optional. If True, your post_only order causes a match with a pre-existing order as a taker, then the order will be cancelled - * @param {string} [params.take_rate] Optional. Liquidity taker fee, a fraction of order volume, such as 0.001 (for 0.1% fee). Can only increase the fee. Used for fee markup. - * @param {string} [params.make_rate] Optional. Liquidity provider fee, a fraction of order volume, such as 0.001 (for 0.1% fee). Can only increase the fee. Used for fee markup. + * @param {string} [params.stopPrice] Optional. Required for 'stopLimit' and 'stopMarket' orders. stop price of the order + * @param {TIME_IN_FORCE} [params.timeInForce] Optional. 'GTC', 'IOC', 'FOK', 'Day', 'GTD'. Default to 'GTC' + * @param {string} [params.expireTime] Optional. Required for orders with timeInForce = GDT + * @param {boolean} [params.strictValidate] Optional. If False, the server rounds half down for tickerSize and quantityIncrement. Example of ETHBTC: tickSize = '0.000001', then price '0.046016' is valid, '0.0460165' is invalid + * @param {boolean} [params.postOnly] Optional. If True, your post_only order causes a match with a pre-existing order as a taker, then the order will be cancelled + * @param {string} [params.takeRate] Optional. Liquidity taker fee, a fraction of order volume, such as 0.001 (for 0.1% fee). Can only increase the fee. Used for fee markup. + * @param {string} [params.makeRate] Optional. Liquidity provider fee, a fraction of order volume, such as 0.001 (for 0.1% fee). Can only increase the fee. Used for fee markup. * @return A promise that resolves with a report of the new order */ async createSpotOrder(params: { symbol: string; side: string; quantity: string; - client_order_id?: string; + clientOrderId?: string; type?: ORDER_TYPE; price?: string; - stop_price?: string; - time_in_Force?: TIME_IN_FORCE; - expire_time?: string; - strict_Validate?: boolean; + stopPrice?: string; + timeInForce?: TIME_IN_FORCE; + expireTime?: string; + strictValidate?: boolean; postOnly?: boolean; takeRate?: string; makeRate?: string; }): Promise { - return this.makeRequest({ + const report = await this.makeRequest({ method: "spot_new_order", params, }); + return fromSnakeCaseToCamelCase(report) } /** @@ -131,21 +134,22 @@ export class TradingClient extends AuthClient { * * https://api.exchange.cryptomkt.com/#create-new-spot-order-list-2 * - * @param {string} params.order_list_id Order list identifier. If omitted, it will be generated by the system upon order list creation. Must be equal to client_order_id of the first order in the request. - * @param {string} params.contingency_type Order list type. + * @param {string} params.orderListId Order list identifier. If omitted, it will be generated by the system upon order list creation. Must be equal to client_order_id of the first order in the request. + * @param {string} params.contingencyType Order list type. * @param {OrderRequest[]} params.orders Orders in the list. * @return A promise that resolves with a list all reports created */ async createNewSpotOrderList(params: { - order_list_id: string; - contingency_type: CONTINGENCY; + orderListId: string; + contingencyType: CONTINGENCY; orders: OrderRequest[]; }): Promise { - return this.makeListRequest({ + const reportList = await this.makeListRequest({ method: "spot_new_order_list", params, responseCount: params.orders.length, }); + return fromSnakeCaseToCamelCase(reportList) } /** @@ -153,14 +157,15 @@ export class TradingClient extends AuthClient { * * https://api.exchange.cryptomkt.com/#cancel-spot-order-2 * - * @param {string} client_order_id the client order id of the order to cancel + * @param {string} clientOrderId the client order id of the order to cancel * @return A promise that resolves with a report of the canceled order */ - async cancelSpotOrder(client_order_id: string): Promise { - return this.makeRequest({ + async cancelSpotOrder(clientOrderId: string): Promise { + const report = await this.makeRequest({ method: "spot_cancel_order", - params: { client_order_id }, + params: { client_order_id: clientOrderId }, }); + return fromSnakeCaseToCamelCase(report) } /** @@ -168,24 +173,25 @@ export class TradingClient extends AuthClient { * * https://api.exchange.cryptomkt.com/#cancel-replace-spot-order * - * @param {string} params.client_order_id the client order id of the order to change - * @param {string} params.new_client_order_id the new client order id for the modified order. must be unique within the trading day + * @param {string} params.clientOrderId the client order id of the order to change + * @param {string} params.newClientOrderId the new client order id for the modified order. must be unique within the trading day * @param {string} params.quantity new order quantity * @param {string} params.price new order price * @param {boolean} [params.strictValidate] price and quantity will be checked for the incrementation with tick size and quantity step. See symbol's tick_size and quantity_increment * @return A promise that resolves with a report of the modified order */ async replaceSpotOrder(params: { - client_order_id: string; - new_client_order_id: string; + clientOrderId: string; + newClientOrderId: string; quantity: string; price: string; strictValidate?: Boolean; }): Promise { - return this.makeRequest({ + const report = await this.makeRequest({ method: "spot_replace_order", params, }); + return fromSnakeCaseToCamelCase(report) } /** @@ -196,7 +202,8 @@ export class TradingClient extends AuthClient { * @return A promise that resolves with a list of report of the canceled orders */ async cancelSpotOrders(): Promise { - return this.makeRequest({ method: "spot_cancel_orders" }); + const reports = await this.makeRequest({ method: "spot_cancel_orders" }); + return fromSnakeCaseToCamelCase(reports) } /** @@ -230,7 +237,8 @@ export class TradingClient extends AuthClient { * @return A promise that resolves with a list of commission rates */ async getSpotFees(): Promise { - return this.makeRequest({ method: "spot_fees" }); + const commissions = await this.makeRequest({ method: "spot_fees" }); + return fromSnakeCaseToCamelCase(commissions) } /** @@ -242,7 +250,8 @@ export class TradingClient extends AuthClient { * @return A promise that resolves with the commission rate of a symbol */ async getSpotFee(symbol: string): Promise { - return this.makeRequest({ method: "spot_fee", params: { symbol } }); + const commission = await this.makeRequest({ method: "spot_fee", params: { symbol } }); + return fromSnakeCaseToCamelCase(commission) } /////////////////// @@ -266,7 +275,8 @@ export class TradingClient extends AuthClient { return ( (await this.sendSubscription({ method: "spot_subscribe", - callback: (notification: any, type: NOTIFICATION_TYPE) => { + callback: (notificationRaw: any, type: NOTIFICATION_TYPE) => { + const notification = fromSnakeCaseToCamelCase(notificationRaw) if (type === NOTIFICATION_TYPE.SNAPSHOT) { callback(notification as Report[], type); } else { diff --git a/lib/websocket/walletClient.ts b/lib/websocket/walletClient.ts index 3b73650..869fcca 100644 --- a/lib/websocket/walletClient.ts +++ b/lib/websocket/walletClient.ts @@ -8,6 +8,7 @@ import { TRANSACTION_SUBTYPE, TRANSACTION_TYPE, } from "../constants"; +import { fromSnakeCaseToCamelCase } from "../paramStyleConverter"; /** * WalletClient connects via websocket to cryptomarket to get wallet information of the user. uses SHA256 as auth method and authenticates on connection. @@ -46,7 +47,7 @@ export class WalletClient extends AuthClient { * * @return A promise that resolves with a list of wallet balances */ - getWalletBalances(): Promise { + async getWalletBalances(): Promise { return this.makeRequest({ method: "wallet_balances" }); } @@ -80,43 +81,44 @@ export class WalletClient extends AuthClient { * * https://api.exchange.cryptomkt.com/#get-transactions * - * @param {string[]} [params.tx_ids] Optional. List of transaction identifiers to query - * @param {TRANSACTION_TYPE[]} [params.transaction_types] Optional. List of types to query. valid types are: 'DEPOSIT', 'WITHDRAW', 'TRANSFER' and 'SWAP' - * @param {TRANSACTION_SUBTYPE[]} [params.transaction_subtyes] Optional. List of subtypes to query. valid subtypes are: 'UNCLASSIFIED', 'BLOCKCHAIN', 'AIRDROP', 'AFFILIATE', 'STAKING', 'BUY_CRYPTO', 'OFFCHAIN', 'FIAT', 'SUB_ACCOUNT', 'WALLET_TO_SPOT', 'SPOT_TO_WALLET', 'WALLET_TO_DERIVATIVES', 'DERIVATIVES_TO_WALLET', 'CHAIN_SWITCH_FROM', 'CHAIN_SWITCH_TO' and 'INSTANT_EXCHANGE' - * @param {TRANSACTION_STATUS[]} [params.transaction_statuses] Optional. List of statuses to query. valid subtypes are: 'CREATED', 'PENDING', 'FAILED', 'SUCCESS' and 'ROLLED_BACK' + * @param {string[]} [params.txIds] Optional. List of transaction identifiers to query + * @param {TRANSACTION_TYPE[]} [params.transactionTypes] Optional. List of types to query. valid types are: 'DEPOSIT', 'WITHDRAW', 'TRANSFER' and 'SWAP' + * @param {TRANSACTION_SUBTYPE[]} [params.transactionSubtyes] Optional. List of subtypes to query. valid subtypes are: 'UNCLASSIFIED', 'BLOCKCHAIN', 'AIRDROP', 'AFFILIATE', 'STAKING', 'BUY_CRYPTO', 'OFFCHAIN', 'FIAT', 'SUB_ACCOUNT', 'WALLET_TO_SPOT', 'SPOT_TO_WALLET', 'WALLET_TO_DERIVATIVES', 'DERIVATIVES_TO_WALLET', 'CHAIN_SWITCH_FROM', 'CHAIN_SWITCH_TO' and 'INSTANT_EXCHANGE' + * @param {TRANSACTION_STATUS[]} [params.transactionStatuses] Optional. List of statuses to query. valid subtypes are: 'CREATED', 'PENDING', 'FAILED', 'SUCCESS' and 'ROLLED_BACK' * @param {string} [params.from] Optional. Interval initial value when ordering by 'created_at'. As Datetime * @param {string} [params.till] Optional. Interval end value when ordering by 'created_at'. As Datetime - * @param {string} [params.id_from] Optional. Interval initial value when ordering by id. Min is 0 - * @param {string} [params.id_till] Optional. Interval end value when ordering by id. Min is 0 - * @param {SORT_BY} [params.order_by] Optional. sorting parameter.'created_at' or 'id'. Default is 'created_at' + * @param {string} [params.idFrom] Optional. Interval initial value when ordering by id. Min is 0 + * @param {string} [params.idTill] Optional. Interval end value when ordering by id. Min is 0 + * @param {SORT_BY} [params.orderBy] Optional. sorting parameter.'created_at' or 'id'. Default is 'created_at' * @param {SORT} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC' * @param {number} [params.limit] Optional. Transactions per query. Defaul is 100. Max is 1000 * @param {number} [params.offset] Optional. Default is 0. Max is 100000 - * @param {Boolean} [params.group_transactions] Flag indicating whether the returned transactions will be parts of a single operation. Default is false + * @param {Boolean} [params.groupTransactions] Flag indicating whether the returned transactions will be parts of a single operation. Default is false * @return A promise that resolves with a list of transactions */ - getTransactions(params: { - tx_ids?: number[]; + async getTransactions(params: { + txIds?: number[]; types?: TRANSACTION_TYPE[]; subtypes?: TRANSACTION_SUBTYPE[]; statuses?: TRANSACTION_STATUS[]; currencies?: string[]; from?: string; till?: string; - id_from?: number; - id_till?: number; - order_by?: SORT_BY; + idFrom?: number; + idTill?: number; + orderBy?: SORT_BY; sort?: SORT; limit?: number; offset?: number; - group_transactions?: Boolean; + groupTransactions?: Boolean; }): Promise { const clean_params: any = { ...params } clean_params.currencies = params.currencies?.join(", ") - return this.makeRequest({ + const transactions = await this.makeRequest({ method: "get_transactions", params: clean_params }); + return fromSnakeCaseToCamelCase(transactions) } /////////////////// @@ -136,7 +138,7 @@ export class WalletClient extends AuthClient { ): Promise { const subscriptionResult = await this.sendSubscription({ method: "subscribe_transactions", - callback: (notification: any, type: NOTIFICATION_TYPE) => callback(notification as Transaction, type), + callback: (notification: any, type: NOTIFICATION_TYPE) => callback(fromSnakeCaseToCamelCase(notification) as Transaction, type), }); return (subscriptionResult as { result: boolean }).result; } diff --git a/test/objectKeyConverter.test.ts b/test/paramStyleConverter.test.ts similarity index 90% rename from test/objectKeyConverter.test.ts rename to test/paramStyleConverter.test.ts index 529a389..b75577c 100644 --- a/test/objectKeyConverter.test.ts +++ b/test/paramStyleConverter.test.ts @@ -1,9 +1,8 @@ import { expect } from "chai"; import "mocha"; -import { fromCamelCaseToSnakeCase } from "../lib/camelCaseToSnakeCaseConverter"; -import { fromSnakeCaseToCamelCase } from "../lib/snakeCaseToCamelCaseConverter"; +import { fromCamelCaseToSnakeCase, fromSnakeCaseToCamelCase } from "../lib/paramStyleConverter"; -describe.only("convert from camel case to snake case", function () { +describe("convert from camel case to snake case", function () { describe("convert object keys at base level", function () { it("converts for diferent value tyeps", function () { const camelCaseObj = { @@ -37,7 +36,7 @@ describe.only("convert from camel case to snake case", function () { }); }); }); -describe.only("convert from camel case to snake case", function () { +describe("convert from camel case to snake case", function () { describe("convert object keys at base level", function () { it("converts for diferent value tyeps", function () { const snake_case_obj = { diff --git a/test/rest/market_data.test.ts b/test/rest/marketData.test.ts similarity index 99% rename from test/rest/market_data.test.ts rename to test/rest/marketData.test.ts index c5f8e46..daf4756 100644 --- a/test/rest/market_data.test.ts +++ b/test/rest/marketData.test.ts @@ -19,7 +19,7 @@ import { goodTicker, goodTickerPrice, listSize, -} from "../test_helpers"; +} from "../testHelpers"; describe("Rest client test", () => { let client = new Client("", ""); diff --git a/test/rest/spot_trading.test.ts b/test/rest/spotTrading.test.ts similarity index 93% rename from test/rest/spot_trading.test.ts rename to test/rest/spotTrading.test.ts index 67bca3e..e03b58a 100644 --- a/test/rest/spot_trading.test.ts +++ b/test/rest/spotTrading.test.ts @@ -12,7 +12,7 @@ import { goodOrder, goodTradingCommission, listSize, -} from "../test_helpers"; +} from "../testHelpers"; const keys = require("/home/ismael/cryptomarket/keys.json"); describe("spot trading", () => { @@ -59,7 +59,7 @@ describe("spot trading", () => { side: SIDE.SELL, quantity: "0.01", price: "1000", - client_order_id: timestamp, + clientOrderId: timestamp, }); assert(goodOrder(order), "not good order after creation"); @@ -69,8 +69,8 @@ describe("spot trading", () => { // replacing let newOrderID = Date.now().toString() + "1"; - order = await client.replaceSpotOrder(order.client_order_id, { - new_client_order_id: newOrderID, + order = await client.replaceSpotOrder(order.clientOrderId, { + newClientOrderId: newOrderID, quantity: "0.02", price: "1000", }); @@ -91,7 +91,7 @@ describe("spot trading", () => { assert(goodOrder(order), "not good order after creation"); // cancelation - order = await client.cancelSpotOrder(order.client_order_id); + order = await client.cancelSpotOrder(order.clientOrderId); assert(goodOrder(order), "not good order after cancellation"); }); }); @@ -160,13 +160,13 @@ describe("spot trading", () => { let order_list_id = Date.now().toString(); await client.createNewSpotOrderList({ // order_list_id: order_list_id, - contingency_type: CONTINGENCY.ALL_OR_NONE, + contingencyType: CONTINGENCY.ALL_OR_NONE, orders: [ { symbol: 'EOSETH', side: SIDE.SELL, type: ORDER_TYPE.LIMIT, - time_in_force: TIME_IN_FORCE.FOK, + timeInForce: TIME_IN_FORCE.FOK, quantity: '0.1', price: '1000', // client_order_id: order_list_id @@ -175,7 +175,7 @@ describe("spot trading", () => { symbol: 'EOSUSDT', side: SIDE.SELL, type: ORDER_TYPE.LIMIT, - time_in_force: TIME_IN_FORCE.FOK, + timeInForce: TIME_IN_FORCE.FOK, quantity: '0.1', price: '1000' } diff --git a/test/rest/spot_trading_history.test.ts b/test/rest/spotTradingHistory.test.ts similarity index 94% rename from test/rest/spot_trading_history.test.ts rename to test/rest/spotTradingHistory.test.ts index 8e1dd03..0a71e58 100644 --- a/test/rest/spot_trading_history.test.ts +++ b/test/rest/spotTradingHistory.test.ts @@ -1,6 +1,6 @@ import assert from "assert"; import { Client } from "../../lib"; -import { goodList, goodOrder, goodTrade } from "../test_helpers"; +import { goodList, goodOrder, goodTrade } from "../testHelpers"; const keys = require("/home/ismael/cryptomarket/keys.json"); import "mocha"; diff --git a/test/rest/wallet_management.test.ts b/test/rest/walletManagement.test.ts similarity index 97% rename from test/rest/wallet_management.test.ts rename to test/rest/walletManagement.test.ts index 905f9d7..ea92f52 100644 --- a/test/rest/wallet_management.test.ts +++ b/test/rest/walletManagement.test.ts @@ -10,7 +10,7 @@ import { goodFee, goodList, goodTransaction, -} from "../test_helpers"; +} from "../testHelpers"; import { Address } from "../../lib/models"; const keys = require("/home/ismael/cryptomarket/keys.json"); @@ -110,7 +110,7 @@ describe("wallet management", () => { currency: "ADA", amount: "0.1", address: adaAddress.address, - auto_commit: false, + autoCommit: false, }); assert(transactionID !== "", "no transaction id"); let commitResult = await client.withdrawCryptoCommit(transactionID); @@ -123,7 +123,7 @@ describe("wallet management", () => { currency: "ADA", amount: "0.1", address: adaAddress.address, - auto_commit: false, + autoCommit: false, }); assert(transactionID !== "", "no transaction id"); let rollbackResult = await client.withdrawCryptoRollback(transactionID); @@ -241,6 +241,6 @@ describe("wallet management", () => { }); function sameAddress(oldAddress: Address, newAddres: Address) { return oldAddress.address === newAddres.address && oldAddress.currency && newAddres.currency - && oldAddress.payment_id === newAddres.payment_id && oldAddress.public_key === oldAddress.public_key + && oldAddress.paymentId === newAddres.paymentId && oldAddress.publicKey === oldAddress.publicKey } diff --git a/test/test_helpers.ts b/test/testHelpers.ts similarity index 81% rename from test/test_helpers.ts rename to test/testHelpers.ts index 29bbc74..9d4399b 100644 --- a/test/test_helpers.ts +++ b/test/testHelpers.ts @@ -64,11 +64,11 @@ export function goodDict(checkFn: (arg0: any) => any, dict: { [x: string]: any } // goodCurrency checks the precence of every field in the currency dict export function goodCurrency(currency: { [x: string]: any }) { let good = goodObject(currency, [ - "full_name", - "payin_enabled", - "payout_enabled", - "transfer_enabled", - "precision_transfer", + "fullName", + "payinEnabled", + "payoutEnabled", + "transferEnabled", + "precisionTransfer", "networks", ]); if (!good) { @@ -86,27 +86,27 @@ export function goodNetwork(network: any) { return goodObject(network, [ "network", "default", - "payin_enabled", - "payout_enabled", - "precision_payout", - "payout_fee", - "payout_is_payment_id", - "payin_payment_id", - "payin_confirmations", + "payinEnabled", + "payoutEnabled", + "precisionPayout", + "payoutFee", + "payoutIsPaymentId", + "payinPaymentId", + "payinConfirmations", ]); } // goodSymbol check the precence of every field in the symbol dict export function goodSymbol(symbol: any) { return goodObject(symbol, [ "type", - "base_currency", - "quote_currency", + "baseCurrency", + "quoteCurrency", "status", - "quantity_increment", - "tick_size", - "take_rate", - "make_rate", - "fee_currency", + "quantityIncrement", + "tickSize", + "takeRate", + "makeRate", + "feeCurrency", // "margin_trading", // "max_initial_leverage", ]); @@ -122,7 +122,7 @@ export function goodTicker(ticker: any) { "high", "open", "volume", - "volume_quote", + "volumeQuote", "timestamp", ]); } @@ -155,36 +155,59 @@ export function goodPublicTrade(trade: any) { // goodPublicTrade check the precence of every field in the trade dict export function goodWSTrade(trade: any) { - return goodObject(trade, ["t", "i", "p", "q", "s"]); + return goodObject(trade, ["timestamp", "id", "price", "quantity", "side"]); } // goodPublicTrade check the precence of every field in the trade dict export function goodWSCandle(candle: any) { - return goodObject(candle, ["t", "o", "c", "h", "l", "v", "q"]); + return goodObject(candle, ["timestamp", "openPrice", "closePrice", "highPrice", "lowPrice", "baseVolume", "quoteVolume"]); } // goodPublicTrade check the precence of every field in the trade dict export function goodWSTicker(ticker: any) { - return goodObject(ticker, ["t", "a", "A", "b", "B", "c", "o", "h", "l", "v", "q", "p", "P", "L"]); + return goodObject(ticker, [ + "timestamp", + "bestAsk", + "bestAskQuantity", + "bestBid", + "bestBidQuantity", + "closePrice", + "openPrice", + "highPrice", + "lowPrice", + "baseVolume", + "quoteVolume", + "priceChange", + "PriceChangePercent", + "lastTradeId"]); } export function goodWSOrderbook(orderbook: any) { - const goodOrderbook = goodObject(orderbook, ["t", "s", "a", "b"]); + const goodOrderbook = goodObject(orderbook, [ + "timestamp", + "sequence", + "asks", + "bids"]); if (!goodOrderbook) return false; - for (const level of orderbook["a"]) { + for (const level of orderbook["asks"]) { if (!goodOrderbookLevel(level)) return false; } - for (const level of orderbook["b"]) { + for (const level of orderbook["bids"]) { if (!goodOrderbookLevel(level)) return false; } return true; } // goodPublicTrade check the precence of every field in the trade dict export function goodWSOrderbookTop(orderbookTop: any) { - return goodObject(orderbookTop, ["t", "a", "A", "b", "B"]); + return goodObject(orderbookTop, [ + "timestamp", + "bestAsk", + "bestAskQuantity", + "bestBid", + "bestBidQuantity",]); } @@ -217,7 +240,7 @@ export function goodCandle(candle: any) { "min", "max", "volume", - "volume_quote", + "volumeQuote", ]); } @@ -235,20 +258,20 @@ export function goodBalance(balance: any) { export function goodOrder(order: { [x: string]: any }) { let good = goodObject(order, [ "id", - "client_order_id", + "clientOrderId", "symbol", "side", "status", "type", - "time_in_force", + "timeInForce", "quantity", - "quantity_cumulative", + "quantityCumulative", // "price", // optional // "stop_price", // optional // "expire_time", // optional // "original_client_order_id", // optional - "created_at", - "updated_at", + "createdAt", + "updatedAt", // "trades", // optional. List of trades ]); if (!good) return false; @@ -274,8 +297,8 @@ export function goodTradeOfOrder(trade: any) { export function goodTrade(trade: any) { return goodObject(trade, [ "id", - "order_id", - "client_order_id", + "orderId", + "clientOrderId", "symbol", "side", "quantity", @@ -287,7 +310,7 @@ export function goodTrade(trade: any) { } export function goodTradingCommission(transaction: any) { - return goodObject(transaction, ["symbol", "take_rate", "make_rate"]); + return goodObject(transaction, ["symbol", "takeRate", "makeRate"]); } // goodTransaction check the precence of every field in the transaction dict @@ -316,8 +339,8 @@ export function goodTransaction(transaction: { native: any }) { "status", "type", "subtype", - "created_at", - "updated_at", + "createdAt", + "updatedAt", // "native", // optional // "primetrust", // optional // "meta" // optional @@ -334,7 +357,7 @@ export function goodTransaction(transaction: { native: any }) { export function goodNativeTransaction(transaction: any) { return goodObject(transaction, [ - "tx_id", + "txId", "index", "currency", "amount", @@ -352,40 +375,40 @@ export function goodNativeTransaction(transaction: any) { export function goodMetaTransaction(transaction: any) { return goodObject(transaction, [ - "fiat_to_crypto", + "fiatToCrypto", "id", - "provider_name", - "order_type", - "order_type", - "source_currency", - "target_currency", - "wallet_address", - "tx_hash", - "target_amount", - "source_amount", + "providerName", + "orderType", + "orderType", + "sourceCurrency", + "targetCurrency", + "walletAddress", + "txHash", + "targetAmount", + "sourceAmount", "status", - "created_at", - "updated_at", - "deleted_at", - "payment_method_type", + "createdAt", + "updatedAt", + "deletedAt", + "paymentMethodType", ]); } export function goodReport(report: Report) { return goodObject(report, [ "id", - "client_order_id", + "clientOrderId", "symbol", "side", "status", "type", - "time_in_force", + "timeInForce", "quantity", - "quantity_cumulative", + "quantityCumulative", "price", - "post_only", - "created_at", - "updated_at", + "postOnly", + "createdAt", + "updatedAt", // "report_type", not present in order list reports ]); } @@ -395,19 +418,19 @@ export function goodAmountLock(report: any) { "id", "currency", "amount", - "date_end", + "dateEnd", "description", "cancelled", - "cancelled_at", - "cancel_description", - "created_at", + "cancelledAt", + "cancelDescription", + "createdAt", ]); } export function goodPriceRate(report: any) { return goodObject(report, [ - "t", - "r" + "timestamp", + "rate" ]); } diff --git a/test/websocket/market_data.test.ts b/test/websocket/marketData.test.ts similarity index 99% rename from test/websocket/market_data.test.ts rename to test/websocket/marketData.test.ts index 0f8415c..296fdd9 100644 --- a/test/websocket/market_data.test.ts +++ b/test/websocket/marketData.test.ts @@ -8,7 +8,7 @@ import { PRICE_RATE_SPEED, TICKER_SPEED, } from "../../lib/constants"; -import { SECOND, goodPriceRate, goodWSCandle, goodWSOrderbook, goodWSOrderbookTop, goodWSTicker, goodWSTrade, timeout } from "../test_helpers"; +import { SECOND, goodPriceRate, goodWSCandle, goodWSOrderbook, goodWSOrderbookTop, goodWSTicker, goodWSTrade, timeout } from "../testHelpers"; describe("websocket market data client", function () { let wsclient: WSMarketDataClient; @@ -137,7 +137,7 @@ describe("websocket market data client", function () { callback: checkGoodMapValues(goodPriceRate), params: { speed: PRICE_RATE_SPEED._3_S, - target_currency: "BTC", + targetCurrency: "BTC", currencies: ["EOS", "ETH", "CRO"], }, }); diff --git a/test/websocket/response_promise_factory.test.ts b/test/websocket/responsePromiseFactory.test.ts similarity index 100% rename from test/websocket/response_promise_factory.test.ts rename to test/websocket/responsePromiseFactory.test.ts diff --git a/test/websocket/trading.test.ts b/test/websocket/trading.test.ts index 1fc1409..b7314b7 100644 --- a/test/websocket/trading.test.ts +++ b/test/websocket/trading.test.ts @@ -3,12 +3,12 @@ import { expect } from "chai"; import "mocha"; import { WSTradingClient } from "../../lib"; import { CONTINGENCY, ORDER_STATUS, REPORT_STATUS, SIDE, TIME_IN_FORCE } from "../../lib/constants"; -import { SECOND, goodBalance, goodReport, goodTradingCommission, timeout } from "../test_helpers"; +import { SECOND, goodBalance, goodReport, goodTradingCommission, timeout } from "../testHelpers"; describe("TradingClient", () => { let wsclient: WSTradingClient; beforeEach(() => { - wsclient = new WSTradingClient(keys.apiKey, keys.apiSecret); + wsclient = new WSTradingClient(keys.apiKey, keys.apiSecret, undefined, 10_000); }); afterEach(() => { @@ -40,7 +40,7 @@ describe("TradingClient", () => { const clientOrderID = newID(); let orderReport = await wsclient.createSpotOrder({ - client_order_id: clientOrderID, + clientOrderId: clientOrderID, symbol: "EOSETH", side: "sell", quantity: "0.01", @@ -52,8 +52,8 @@ describe("TradingClient", () => { const newClientOrderID = clientOrderID + "new"; orderReport = await wsclient.replaceSpotOrder({ - client_order_id: clientOrderID, - new_client_order_id: newClientOrderID, + clientOrderId: clientOrderID, + newClientOrderId: newClientOrderID, quantity: "0.01", price: "2000", }); @@ -111,22 +111,22 @@ describe("TradingClient", () => { const firstOrderID = newID() await wsclient.connect(); const reports = await wsclient.createNewSpotOrderList({ - order_list_id: firstOrderID, - contingency_type: CONTINGENCY.ALL_OR_NONE, + orderListId: firstOrderID, + contingencyType: CONTINGENCY.ALL_OR_NONE, orders: [ { - client_order_id: firstOrderID, + clientOrderId: firstOrderID, symbol: "EOSETH", side: SIDE.SELL, - time_in_force: TIME_IN_FORCE.FOK, + timeInForce: TIME_IN_FORCE.FOK, quantity: "0.01", price: "100000" }, { - client_order_id: firstOrderID + "2", + clientOrderId: firstOrderID + "2", symbol: "EOSBTC", side: SIDE.SELL, - time_in_force: TIME_IN_FORCE.FOK, + timeInForce: TIME_IN_FORCE.FOK, quantity: "0.01", price: "100000" } @@ -142,7 +142,7 @@ describe("TradingClient", () => { async function expectActiveOrderWithID(clientOrderID: string) { const activeOrders = await wsclient.getActiveSpotOrders(); - const present = activeOrders.filter(order => order.client_order_id === clientOrderID).length === 1; + const present = activeOrders.filter(order => order.clientOrderId === clientOrderID).length === 1; if (!present) expect.fail("order is not present"); } diff --git a/test/websocket/trading_subscriptions.test.ts b/test/websocket/tradingSubscriptions.test.ts similarity index 97% rename from test/websocket/trading_subscriptions.test.ts rename to test/websocket/tradingSubscriptions.test.ts index 2d1fd59..3353234 100644 --- a/test/websocket/trading_subscriptions.test.ts +++ b/test/websocket/tradingSubscriptions.test.ts @@ -2,7 +2,7 @@ const keys = require("/home/ismael/cryptomarket/keys.json"); import { expect } from "chai"; import { WSTradingClient } from "../../lib"; import { SUBSCRIPTION_MODE } from "../../lib/constants"; -import { SECOND, goodBalance, goodReport, timeout } from "../test_helpers"; +import { SECOND, goodBalance, goodReport, timeout } from "../testHelpers"; describe("tradingClient subscriptions", function () { let wsclient: WSTradingClient; @@ -50,7 +50,7 @@ describe("tradingClient subscriptions", function () { }) function newOrderRequest(clientOrderID: string) { return wsclient.createSpotOrder({ - client_order_id: clientOrderID, + clientOrderId: clientOrderID, symbol: "EOSETH", side: "sell", quantity: "0.01", diff --git a/test/websocket/wallet.test.ts b/test/websocket/wallet.test.ts index 21b2830..1cbea22 100644 --- a/test/websocket/wallet.test.ts +++ b/test/websocket/wallet.test.ts @@ -2,7 +2,7 @@ const keys = require("/home/ismael/cryptomarket/keys.json"); import { expect } from "chai"; import "mocha"; import { WSWalletClient } from "../../lib"; -import { goodBalance, goodTransaction } from "../test_helpers"; +import { goodBalance, goodTransaction } from "../testHelpers"; describe("WalletClient", function () { let wsclient: WSWalletClient; diff --git a/test/websocket/wallet_subscriptions.test.ts b/test/websocket/walletSubscriptions.test.ts similarity index 99% rename from test/websocket/wallet_subscriptions.test.ts rename to test/websocket/walletSubscriptions.test.ts index 0616c07..d579633 100644 --- a/test/websocket/wallet_subscriptions.test.ts +++ b/test/websocket/walletSubscriptions.test.ts @@ -1,7 +1,7 @@ import { expect } from "chai"; import { Client, WSWalletClient } from "../../lib/index"; import { Balance, Transaction } from "../../lib/models"; -import { SECOND, goodBalance, goodTransaction, timeout } from "../test_helpers"; +import { SECOND, goodBalance, goodTransaction, timeout } from "../testHelpers"; const keys = require("/home/ismael/cryptomarket/keys.json"); describe("wallet transactions", function () { From f750a7d79322c722779f653cf512fc0d46b5951f Mon Sep 17 00:00:00 2001 From: "T. Ismael Verdugo" Date: Mon, 12 Feb 2024 21:19:17 -0300 Subject: [PATCH 15/16] fix: update missing snake_case names --- README.md | 2 +- lib/client.ts | 39 +++++++++++++++---------------- lib/httpClient.ts | 16 ++++++------- lib/websocket/authClient.ts | 4 ++-- lib/websocket/marketDataClient.ts | 4 ++-- lib/websocket/tradingClient.ts | 8 +++---- lib/websocket/walletClient.ts | 12 +++++----- test/rest/spotTrading.test.ts | 6 ++--- test/testHelpers.ts | 32 ++++++++++++------------- test/websocket/marketData.test.ts | 2 +- 10 files changed, 62 insertions(+), 63 deletions(-) diff --git a/README.md b/README.md index 66b4850..0bfc2a1 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ console.log(balanceList); let clientOrderID = Math.floor(Date.now() / 1000).toString(); // make a spot order const report = await tradingClient.createSpotOrder({ - client_order_id: clientOrderID, + clientOrderId: clientOrderID, symbol: "EOSETH", side: "sell", quantity: "0.01", diff --git a/lib/client.ts b/lib/client.ts index 6bd1439..d939491 100644 --- a/lib/client.ts +++ b/lib/client.ts @@ -547,7 +547,7 @@ export class Client { * @param {string} [params.stopPrice] Required for 'stopLimit' and 'stopMarket' orders. stop price of the order * @param {string} [params.expireTime] Required for orders with timeInForce = GDT * @param {boolean} [params.strictValidate] Optional. If False, the server rounds half down for tickerSize and quantityIncrement. Example of ETHBTC: tickSize = '0.000001', then price '0.046016' is valid, '0.0460165' is invalid - * @param {boolean} [params.postOnly] Optional. If True, your post_only order causes a match with a pre-existing order as a taker, then the order will be cancelled + * @param {boolean} [params.postOnly] Optional. If True, your postOnly order causes a match with a pre-existing order as a taker, then the order will be cancelled * @param {string} [params.takeRate] Optional. Liquidity taker fee, a fraction of order volume, such as 0.001 (for 0.1% fee). Can only increase the fee. Used for fee markup. * @param {string} [params.makeRate] Optional. Liquidity provider fee, a fraction of order volume, such as 0.001 (for 0.1% fee). Can only increase the fee. Used for fee markup. * @@ -607,7 +607,7 @@ export class Client { * * https://api.exchange.cryptomkt.com/#create-new-spot-order-list * - * @param {string} params.orderListId Order list identifier. If omitted, it will be generated by the system upon order list creation. Must be equal to client_order_id of the first order in the request. + * @param {string} params.orderListId Order list identifier. If omitted, it will be generated by the system upon order list creation. Must be equal to clientOrderId of the first order in the request. * @param {string} params.contingencyType Order list type. * @param {OrderRequest[]} params.orders Orders in the list. * @return A promise that resolves with a list of reports of the created orders @@ -627,12 +627,12 @@ export class Client { * * https://api.exchange.cryptomkt.com/#replace-spot-order * - * @param {string} clientOrderId client_order_id of the old order + * @param {string} clientOrderId clientOrderId of the old order * @param {object} params Parameters - * @param {string} params.newClientOrderId client_order_id for the new order. + * @param {string} params.newClientOrderId clientOrderId for the new order. * @param {string} params.quantity Order quantity. * @param {string} [params.price] Required if order type is limit, stopLimit, or takeProfitLimit. Order price. - * @param {boolean} [params.strictValidate] Optional. Price and quantity will be checked for incrementation within the symbol’s tick size and quantity step. See the symbol's tick_size and quantity_increment. + * @param {boolean} [params.strictValidate] Optional. Price and quantity will be checked for incrementation within the symbol’s tick size and quantity step. See the symbol's tickSize and quantityIncrement. * * @returns the new spot order */ @@ -668,7 +668,7 @@ export class Client { * * https://api.exchange.cryptomkt.com/#cancel-spot-order * - * @param {string} clientOrderId the client_order_id of the order to cancel + * @param {string} clientOrderId the clientOrderId of the order to cancel * * @return The canceled order */ @@ -750,7 +750,7 @@ export class Client { * https://api.exchange.cryptomkt.com/#spot-trades-history * * @param {object} [params] - * @param {string} [params.order_id] Optional. Order unique identifier as assigned by the exchange + * @param {string} [params.orderId] Optional. Order unique identifier as assigned by the exchange * @param {string} [params.symbol] Optional. Filter trades by symbol * @param {SORT} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC' * @param {SORT_BY} [params.by] Optional. Sorting type.'timestamp' or 'id'. Default is 'id' @@ -761,7 +761,7 @@ export class Client { * @return A list of trades */ getSpotTradesHistory(params?: { - order_id?: string; + orderId?: string; symbol?: string; sort?: SORT; by?: SORT_BY; @@ -828,11 +828,12 @@ export class Client { * https://api.exchange.cryptomkt.com/#get-deposit-crypto-address * * @param {string} currency currency to get the address + * @param {string} [networkCode] Optional. network code * * @return the currency address */ - async getDepositCryptoAddress(currency: string, network_code?: string): Promise
{ - const addressList = await this.get(`wallet/crypto/address`, { currency }); + async getDepositCryptoAddress(currency: string, networkCode?: string): Promise
{ + const addressList = await this.get(`wallet/crypto/address`, { currency, networkCode }); return addressList[0]; } @@ -843,7 +844,7 @@ export class Client { * Existing addresses may still receive funds. * For some tokens (e.g., Ethereum tokens), * a single address is generated per base currency with additional - * identifiers which differ for each address: payment_id or public_key. + * identifiers which differ for each address: paymentId or publicKey. * As a result, generating a new address for such a token * will change the current address for an entire base currency accordingly. * @@ -920,9 +921,9 @@ export class Client { * @param {string} [params.paymentId] Optional. * @param {boolean} [params.includeFee] Optional. If true then the total spent amount includes fees. Default false * @param {boolean} [params.autoCommit] Optional. If false then you should commit or rollback transaction in an hour. Used in two phase commit schema. Default true - * @param {USE_OFFCHAIN} [params.use_offchain] Whether the withdrawal may be committed offchain. 'never', 'optionally', 'required' + * @param {USE_OFFCHAIN} [params.useOffchain] Whether the withdrawal may be committed offchain. 'never', 'optionally', 'required' Accepted values: never, optionally, required - * @param {string} [params.public_comment] Optional. Maximum length is 255. + * @param {string} [params.publicComment] Optional. Maximum length is 255. * * @return The transaction id, asigned by the exchange */ @@ -1129,9 +1130,9 @@ Accepted values: wallet, spot. Must not be the same as source * @param {TRANSACTION_STATUS[]} [params.statuses] Optional. List of statuses to query. valid subtypes are: 'CREATED', 'PENDING', 'FAILED', 'SUCCESS' and 'ROLLED_BACK' * @param {string[]} [params.currencies] Optional. List of currencies of the transactions * @param {string[]} [params.networks] Optional. List of network codes - * @param {SORT_BY} [params.orderBy] Optional. Defines the sorting type.'created_at' or 'id'. Default is 'created_at' - * @param {string} [params.from] Optional. Interval initial value when ordering by 'created_at'. As Datetime - * @param {string} [params.till] Optional. Interval end value when ordering by 'created_at'. As Datetime + * @param {SORT_BY} [params.orderBy] Optional. Defines the sorting type.'createdAt' or 'id'. Default is 'createdAt' + * @param {string} [params.from] Optional. Interval initial value when ordering by 'createdAt'. As Datetime + * @param {string} [params.till] Optional. Interval end value when ordering by 'createdAt'. As Datetime * @param {string} [params.idFrom] Optional. Interval initial value when ordering by id. Min is 0 * @param {string} [params.idTill] Optional. Interval end value when ordering by id. Min is 0 * @param {string} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC'. @@ -1283,9 +1284,7 @@ Accepted values: wallet, spot. Must not be the same as source * @return true if the activation was successful */ async activateSubAccounts(subAccountIds: string[]): Promise { - const response = await this.post("sub-account/activate", { - sub_account_ids: subAccountIds, - }); + const response = await this.post("sub-account/activate", { subAccountIds }); return response["result"]; } @@ -1349,7 +1348,7 @@ Accepted values: wallet, spot. Must not be the same as source async changeACLSettings(params: { subAccountIds: string[]; depositAddressGenerationEnabled?: boolean; - withdraw_enabled?: boolean; + withdrawEnabled?: boolean; description?: string; createdAt?: string; updatedAt?: string; diff --git a/lib/httpClient.ts b/lib/httpClient.ts index f20746e..2731c3a 100644 --- a/lib/httpClient.ts +++ b/lib/httpClient.ts @@ -32,13 +32,13 @@ export class HttpClient { } - private prepareRequest(params_raw: any, method: HTTP_METHOD, publicMethod: boolean, endpoint: string): { url: URL, opts: Map } { - if (params_raw === undefined || params_raw === null) { - params_raw = {} + private prepareRequest(paramsRaw: any, method: HTTP_METHOD, publicMethod: boolean, endpoint: string): { url: URL, opts: Map } { + if (paramsRaw === undefined || paramsRaw === null) { + paramsRaw = {} } let url = new URL(this.apiPath + endpoint); - this.removeNulls(params_raw); - const params: [string, string][] = Object.entries(params_raw) + this.removeNulls(paramsRaw); + const params: [string, string][] = Object.entries(paramsRaw) .map(([k, v]) => [k, String(v)]) let rawQuery = new URLSearchParams(params); rawQuery.sort(); @@ -54,7 +54,7 @@ export class HttpClient { let credentialParams = query if (method === HTTP_METHOD.POST) { opts.headers["Content-Type"] = "application/json"; - credentialParams = JSON.stringify(params_raw) + credentialParams = JSON.stringify(paramsRaw) } if (method === HTTP_METHOD.PATCH) { opts.headers["Content-Type"] = "application/x-www-form-urlencoded"; @@ -71,8 +71,8 @@ export class HttpClient { return { url, opts }; } - private removeNulls(params_raw: any) { - Object.keys(params_raw).forEach(key => (params_raw[key] === undefined || params_raw[key] == null) ? delete params_raw[key] : {}); + private removeNulls(paramsRaw: any) { + Object.keys(paramsRaw).forEach(key => (paramsRaw[key] === undefined || paramsRaw[key] == null) ? delete paramsRaw[key] : {}); } private async makeFetch(url: URL, opts: any): Promise { diff --git a/lib/websocket/authClient.ts b/lib/websocket/authClient.ts index 36b6ca4..cbbf103 100644 --- a/lib/websocket/authClient.ts +++ b/lib/websocket/authClient.ts @@ -3,7 +3,7 @@ import { WSClientBase } from "./clientBase"; interface AuthPayload { type: string - api_key: string + apiKey: string timestamp: number window?: number signature: string @@ -53,7 +53,7 @@ export class AuthClient extends WSClientBase { const timestamp = Math.floor(Date.now()); const payload: AuthPayload = { type: "HS256", - api_key: this.apiKey, + apiKey: this.apiKey, timestamp: timestamp, signature: "", }; diff --git a/lib/websocket/marketDataClient.ts b/lib/websocket/marketDataClient.ts index 38cd35e..1ff0ca2 100644 --- a/lib/websocket/marketDataClient.ts +++ b/lib/websocket/marketDataClient.ts @@ -555,7 +555,7 @@ export class MarketDataClient extends WSClientBase { * * @param {function} callback recieves a feed of price rates as a map of them, indexed by currency id, and the type of notification, only DATA * @param {PRICE_RATE_SPEED} params.speed The speed of the feed. '1s' or '3s' - * @param {string} params.target_currency quote currency of the rate + * @param {string} params.targetCurrency quote currency of the rate * @param {string} params.currencies Optional. a list of base currencies to get rates. If omitted, subscribe to all currencies * @return A promise that resolves when subscribed with a list of the successfully subscribed currencies */ @@ -569,7 +569,7 @@ export class MarketDataClient extends WSClientBase { ) => any; params: { speed: PRICE_RATE_SPEED; - target_currency: string, + targetCurrency: string, currencies?: string[]; }; }): Promise { diff --git a/lib/websocket/tradingClient.ts b/lib/websocket/tradingClient.ts index 5031fc5..f2d56d9 100644 --- a/lib/websocket/tradingClient.ts +++ b/lib/websocket/tradingClient.ts @@ -75,7 +75,7 @@ export class TradingClient extends AuthClient { * @param {TIME_IN_FORCE} [params.timeInForce] Optional. 'GTC', 'IOC', 'FOK', 'Day', 'GTD'. Default to 'GTC' * @param {string} [params.expireTime] Optional. Required for orders with timeInForce = GDT * @param {boolean} [params.strictValidate] Optional. If False, the server rounds half down for tickerSize and quantityIncrement. Example of ETHBTC: tickSize = '0.000001', then price '0.046016' is valid, '0.0460165' is invalid - * @param {boolean} [params.postOnly] Optional. If True, your post_only order causes a match with a pre-existing order as a taker, then the order will be cancelled + * @param {boolean} [params.postOnly] Optional. If True, your postOnly order causes a match with a pre-existing order as a taker, then the order will be cancelled * @param {string} [params.takeRate] Optional. Liquidity taker fee, a fraction of order volume, such as 0.001 (for 0.1% fee). Can only increase the fee. Used for fee markup. * @param {string} [params.makeRate] Optional. Liquidity provider fee, a fraction of order volume, such as 0.001 (for 0.1% fee). Can only increase the fee. Used for fee markup. * @return A promise that resolves with a report of the new order @@ -134,7 +134,7 @@ export class TradingClient extends AuthClient { * * https://api.exchange.cryptomkt.com/#create-new-spot-order-list-2 * - * @param {string} params.orderListId Order list identifier. If omitted, it will be generated by the system upon order list creation. Must be equal to client_order_id of the first order in the request. + * @param {string} params.orderListId Order list identifier. If omitted, it will be generated by the system upon order list creation. Must be equal to clientOrderId of the first order in the request. * @param {string} params.contingencyType Order list type. * @param {OrderRequest[]} params.orders Orders in the list. * @return A promise that resolves with a list all reports created @@ -163,7 +163,7 @@ export class TradingClient extends AuthClient { async cancelSpotOrder(clientOrderId: string): Promise { const report = await this.makeRequest({ method: "spot_cancel_order", - params: { client_order_id: clientOrderId }, + params: { clientOrderId }, }); return fromSnakeCaseToCamelCase(report) } @@ -177,7 +177,7 @@ export class TradingClient extends AuthClient { * @param {string} params.newClientOrderId the new client order id for the modified order. must be unique within the trading day * @param {string} params.quantity new order quantity * @param {string} params.price new order price - * @param {boolean} [params.strictValidate] price and quantity will be checked for the incrementation with tick size and quantity step. See symbol's tick_size and quantity_increment + * @param {boolean} [params.strictValidate] price and quantity will be checked for the incrementation with tick size and quantity step. See symbol's tickSize and quantityIncrement * @return A promise that resolves with a report of the modified order */ async replaceSpotOrder(params: { diff --git a/lib/websocket/walletClient.ts b/lib/websocket/walletClient.ts index 869fcca..262bd7b 100644 --- a/lib/websocket/walletClient.ts +++ b/lib/websocket/walletClient.ts @@ -85,11 +85,11 @@ export class WalletClient extends AuthClient { * @param {TRANSACTION_TYPE[]} [params.transactionTypes] Optional. List of types to query. valid types are: 'DEPOSIT', 'WITHDRAW', 'TRANSFER' and 'SWAP' * @param {TRANSACTION_SUBTYPE[]} [params.transactionSubtyes] Optional. List of subtypes to query. valid subtypes are: 'UNCLASSIFIED', 'BLOCKCHAIN', 'AIRDROP', 'AFFILIATE', 'STAKING', 'BUY_CRYPTO', 'OFFCHAIN', 'FIAT', 'SUB_ACCOUNT', 'WALLET_TO_SPOT', 'SPOT_TO_WALLET', 'WALLET_TO_DERIVATIVES', 'DERIVATIVES_TO_WALLET', 'CHAIN_SWITCH_FROM', 'CHAIN_SWITCH_TO' and 'INSTANT_EXCHANGE' * @param {TRANSACTION_STATUS[]} [params.transactionStatuses] Optional. List of statuses to query. valid subtypes are: 'CREATED', 'PENDING', 'FAILED', 'SUCCESS' and 'ROLLED_BACK' - * @param {string} [params.from] Optional. Interval initial value when ordering by 'created_at'. As Datetime - * @param {string} [params.till] Optional. Interval end value when ordering by 'created_at'. As Datetime + * @param {string} [params.from] Optional. Interval initial value when ordering by 'createdAt'. As Datetime + * @param {string} [params.till] Optional. Interval end value when ordering by 'createdAt'. As Datetime * @param {string} [params.idFrom] Optional. Interval initial value when ordering by id. Min is 0 * @param {string} [params.idTill] Optional. Interval end value when ordering by id. Min is 0 - * @param {SORT_BY} [params.orderBy] Optional. sorting parameter.'created_at' or 'id'. Default is 'created_at' + * @param {SORT_BY} [params.orderBy] Optional. sorting parameter.'createdAt' or 'id'. Default is 'createdAt' * @param {SORT} [params.sort] Optional. Sort direction. 'ASC' or 'DESC'. Default is 'DESC' * @param {number} [params.limit] Optional. Transactions per query. Defaul is 100. Max is 1000 * @param {number} [params.offset] Optional. Default is 0. Max is 100000 @@ -112,11 +112,11 @@ export class WalletClient extends AuthClient { offset?: number; groupTransactions?: Boolean; }): Promise { - const clean_params: any = { ...params } - clean_params.currencies = params.currencies?.join(", ") + const cleanParams: any = { ...params } + cleanParams.currencies = params.currencies?.join(", ") const transactions = await this.makeRequest({ method: "get_transactions", - params: clean_params + params: cleanParams }); return fromSnakeCaseToCamelCase(transactions) } diff --git a/test/rest/spotTrading.test.ts b/test/rest/spotTrading.test.ts index e03b58a..b19727e 100644 --- a/test/rest/spotTrading.test.ts +++ b/test/rest/spotTrading.test.ts @@ -157,19 +157,19 @@ describe("spot trading", () => { this.timeout(0); await timeout(3 * SECOND) - let order_list_id = Date.now().toString(); + let firstOrderId = Date.now().toString(); await client.createNewSpotOrderList({ - // order_list_id: order_list_id, + orderListId: firstOrderId, contingencyType: CONTINGENCY.ALL_OR_NONE, orders: [ { + clientOrderId: firstOrderId, symbol: 'EOSETH', side: SIDE.SELL, type: ORDER_TYPE.LIMIT, timeInForce: TIME_IN_FORCE.FOK, quantity: '0.1', price: '1000', - // client_order_id: order_list_id }, { symbol: 'EOSUSDT', diff --git a/test/testHelpers.ts b/test/testHelpers.ts index 9d4399b..f932ba8 100644 --- a/test/testHelpers.ts +++ b/test/testHelpers.ts @@ -107,8 +107,8 @@ export function goodSymbol(symbol: any) { "takeRate", "makeRate", "feeCurrency", - // "margin_trading", - // "max_initial_leverage", + // "marginTrading", + // "maxInitialLeverage", ]); } @@ -135,10 +135,10 @@ export function goodTickerPrice(price: any) { return goodObject(price, ["price", "timestamp"]); } -export function goodPriceHistory(price_history: { [x: string]: any }) { - let good = goodObject(price_history, ["currency", "history"]); +export function goodPriceHistory(priceHistory: { [x: string]: any }) { + let good = goodObject(priceHistory, ["currency", "history"]); if (!good) return false; - for (const point of price_history["history"]) { + for (const point of priceHistory["history"]) { if (!goodHistoryPoint(point)) return false; } return true; @@ -250,7 +250,7 @@ export function goodBalance(balance: any) { "currency", "available", "reserved", - // "reserved_margin" optional. + // "reservedMargin" optional. ]); } @@ -267,9 +267,9 @@ export function goodOrder(order: { [x: string]: any }) { "quantity", "quantityCumulative", // "price", // optional - // "stop_price", // optional - // "expire_time", // optional - // "original_client_order_id", // optional + // "stotPrice", // optional + // "expireTime", // optional + // "originalClientOrderId", // optional "createdAt", "updatedAt", // "trades", // optional. List of trades @@ -318,8 +318,8 @@ export function goodAddress(transaction: any) { return goodObject(transaction, [ "currency", "address", - // "payment_id", // optional - // "public_key", // optional + // "paymentId", // optional + // "publicKey", // optional ]); } @@ -363,12 +363,12 @@ export function goodNativeTransaction(transaction: any) { "amount", // "fee", // optional // "address", // optional - // "payment_id", // optional + // "paymentId", // optional // "hash", // optional - // "offchain_id", // optional + // "offchainId", // optional // "confirmations", // optional - // "public_comment", // optional - // "error_code", // optional + // "publicComment", // optional + // "errorCode", // optional // "senders" // optional ]); } @@ -409,7 +409,7 @@ export function goodReport(report: Report) { "postOnly", "createdAt", "updatedAt", - // "report_type", not present in order list reports + // "reportType", not present in order list reports ]); } diff --git a/test/websocket/marketData.test.ts b/test/websocket/marketData.test.ts index 296fdd9..3043fac 100644 --- a/test/websocket/marketData.test.ts +++ b/test/websocket/marketData.test.ts @@ -152,7 +152,7 @@ describe("websocket market data client", function () { callback: checkGoodMapValues(goodPriceRate), params: { speed: PRICE_RATE_SPEED._3_S, - target_currency: "BTC", + targetCurrency: "BTC", currencies: ["EOS", "ETH", "CRO"], }, }); From 662ba69cbe573af2c999be6779933e0607fe4d93 Mon Sep 17 00:00:00 2001 From: Pedro Pablo Bustamante Barrera Date: Tue, 13 Feb 2024 11:20:21 -0300 Subject: [PATCH 16/16] feat: update dependencies and node version --- .npmrc | 1 + package-lock.json | 2481 +++++++++++-------- package.json | 107 +- test/rest/spotTrading.test.ts | 2 +- test/rest/spotTradingHistory.test.ts | 2 +- test/rest/walletManagement.test.ts | 2 +- test/websocket/trading.test.ts | 2 +- test/websocket/tradingSubscriptions.test.ts | 2 +- test/websocket/wallet.test.ts | 2 +- test/websocket/walletSubscriptions.test.ts | 2 +- 10 files changed, 1468 insertions(+), 1135 deletions(-) create mode 100644 .npmrc diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..4fd0219 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index e8ac349..697c978 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,1079 +1,1412 @@ { - "name": "cryptomarket", - "version": "1.0.6", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "0.3.9" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz", - "integrity": "sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w==", - "dev": true - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@tsconfig/node10": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", - "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", - "dev": true - }, - "@tsconfig/node12": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", - "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", - "dev": true - }, - "@tsconfig/node14": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", - "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", - "dev": true - }, - "@tsconfig/node16": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", - "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", - "dev": true - }, - "@types/chai": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.1.tgz", - "integrity": "sha512-/zPMqDkzSZ8t3VtxOa4KPq7uzzW978M9Tvh+j7GHKuo6k6GTLxPJ4J5gE5cjfJ26pnXst0N5Hax8Sr0T2Mi9zQ==", - "dev": true - }, - "@types/crypto-js": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.1.1.tgz", - "integrity": "sha512-BG7fQKZ689HIoc5h+6D2Dgq1fABRa0RbBWKBd9SP/MVRVXROflpm5fhwyATX5duFmbStzyzyycPB8qUYKDH3NA==", - "dev": true - }, - "@types/mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", - "dev": true - }, - "@types/node": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.32.tgz", - "integrity": "sha512-eAIcfAvhf/BkHcf4pkLJ7ECpBAhh9kcxRBpip9cTiO+hf+aJrsxYxBeS6OXvOd9WqNAJmavXVpZvY1rBjNsXmw==", - "dev": true - }, - "@types/node-fetch": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.2.tgz", - "integrity": "sha512-DHqhlq5jeESLy19TYhLakJ07kNumXWjcDdxXsLUMJZ6ue8VZJj4kLPQVE/2mdHh3xZziNF1xppu5lwmS53HR+A==", - "dev": true, - "requires": { - "@types/node": "*", - "form-data": "^3.0.0" - } - }, - "@types/ws": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", - "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@ungap/promise-all-settled": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz", - "integrity": "sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q==", - "dev": true - }, - "acorn": { - "version": "8.7.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", - "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", - "dev": true - }, - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true - }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, - "balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "bigdecimal": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/bigdecimal/-/bigdecimal-0.6.1.tgz", - "integrity": "sha1-GFiNS08ia3cxDtBFdIWMA2pUSFs=" - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "camelcase": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", - "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", - "dev": true - }, - "chai": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.6.tgz", - "integrity": "sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "loupe": "^2.3.1", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", - "dev": true - }, - "chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.3.1", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - } - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "crypto-js": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.0.0.tgz", - "integrity": "sha512-bzHZN8Pn+gS7DQA6n+iUmBfl0hO5DJq++QP3U6uTucDtk/0iGpXd/Gg7CGR0p8tJhofJyaKoWBuJI4eAO00BBg==" - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true - }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true - }, - "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - } - }, - "flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true - }, - "form-data": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", - "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", - "dev": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "optional": true - }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true - }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", - "dev": true - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "requires": { - "is-glob": "^4.0.1" - } - }, - "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "requires": { - "binary-extensions": "^2.0.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true - }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "js-yaml": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.0.0.tgz", - "integrity": "sha512-pqon0s+4ScYUvX30wxQi3PogGFAlUyH0awepWvwkj4jD4v+ova3RiYw8bmA6x2rDrEaj8i/oWKoRxpVNW+Re8Q==", - "dev": true, - "requires": { - "argparse": "^2.0.1" - } - }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "requires": { - "p-locate": "^5.0.0" - } - }, - "log-symbols": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz", - "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==", - "dev": true, - "requires": { - "chalk": "^4.0.0" - } - }, - "loupe": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.4.tgz", - "integrity": "sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==", - "dev": true, - "requires": { - "get-func-name": "^2.0.0" - } - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true - }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "requires": { - "mime-db": "1.52.0" - } - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "mocha": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-8.4.0.tgz", - "integrity": "sha512-hJaO0mwDXmZS4ghXsvPVriOhsxQ7ofcpQdm8dE+jISUOKopitvnXFQmpRR7jd2K6VBG6E26gU3IAbXXGIbu4sQ==", - "dev": true, - "requires": { - "@ungap/promise-all-settled": "1.1.2", - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.1", - "debug": "4.3.1", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.1.6", - "growl": "1.10.5", - "he": "1.2.0", - "js-yaml": "4.0.0", - "log-symbols": "4.0.0", - "minimatch": "3.0.4", - "ms": "2.1.3", - "nanoid": "3.1.20", - "serialize-javascript": "5.0.1", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "which": "2.0.2", - "wide-align": "1.1.3", - "workerpool": "6.1.0", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "nanoid": { - "version": "3.1.20", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.20.tgz", - "integrity": "sha512-a1cQNyczgKbLX9jwbS/+d7W8fX/RfgYR7lVWwWOGIPNgK2m0MWvrGF6/m4kk6U3QcFMnZf3RIhL0v2Jgh/0Uxw==", - "dev": true - }, - "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==" - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "requires": { - "yocto-queue": "^0.1.0" - } - }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "requires": { - "p-limit": "^3.0.2" - } - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true - }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "rxjs": { - "version": "6.6.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", - "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", - "requires": { - "tslib": "^1.9.0" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, - "serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, + "name": "cryptomarket", + "version": "3.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cryptomarket", + "version": "3.0.0", + "license": "Apache-2.0", + "dependencies": { + "bigdecimal": "0.6.1", + "crypto-js": "4.2.0", + "node-fetch": "^2.6.1", + "ws": "^7.4.0" + }, + "devDependencies": { + "@types/chai": "4.3.11", + "@types/crypto-js": "4.2.2", + "@types/mocha": "10.0.6", + "@types/node-fetch": "2.6.11", + "@types/ws": "^8.5.3", + "chai": "^4.3.6", + "mocha": "10.3.0", + "ts-node": "10.9.2", + "typescript": "5.3.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.0.8", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.8.tgz", + "integrity": "sha512-YK5G9LaddzGbcucK4c8h5tWFmMPBvRZ/uyWmN1/SbBdIvqGUdWGkJ5BAaccgs6XbzVLsqbPJrBSFwKv3kT9i7w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", + "integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz", + "integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==", + "dev": true + }, + "node_modules/@types/chai": { + "version": "4.3.11", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.11.tgz", + "integrity": "sha512-qQR1dr2rGIHYlJulmr8Ioq3De0Le9E4MJ5AiaeAETJJpndT1uUNHsGFK3L/UIu+rbkQSdj8J/w2bCsBZc/Y5fQ==", + "dev": true + }, + "node_modules/@types/crypto-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz", + "integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ==", + "dev": true + }, + "node_modules/@types/mocha": { + "version": "10.0.6", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", + "integrity": "sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==", + "dev": true + }, + "node_modules/@types/node": { + "version": "17.0.32", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.32.tgz", + "integrity": "sha512-eAIcfAvhf/BkHcf4pkLJ7ECpBAhh9kcxRBpip9cTiO+hf+aJrsxYxBeS6OXvOd9WqNAJmavXVpZvY1rBjNsXmw==", + "dev": true + }, + "node_modules/@types/node-fetch": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.11.tgz", + "integrity": "sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==", + "dev": true, + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.0" + } + }, + "node_modules/@types/ws": { + "version": "8.5.10", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", + "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/acorn": { + "version": "8.7.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.1.tgz", + "integrity": "sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-colors": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", + "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/bigdecimal": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/bigdecimal/-/bigdecimal-0.6.1.tgz", + "integrity": "sha1-GFiNS08ia3cxDtBFdIWMA2pUSFs=", + "engines": [ + "node" + ], + "bin": { + "bigdecimal.js": "repl.js" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chai": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "dev": true, + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { "supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "ts-node": { - "version": "10.8.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.8.1.tgz", - "integrity": "sha512-Wwsnao4DQoJsN034wePSg5nZiw4YKXf56mPIAeD6wVmiv+RytNSWqc2f3fKvcUoV+Yn2+yocD71VOfQHbmVX4g==", - "dev": true, - "requires": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "dependencies": { - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - } - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "typescript": { - "version": "4.7.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz", - "integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==", - "dev": true - }, - "v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "workerpool": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.1.0.tgz", - "integrity": "sha512-toV7q9rWNYha963Pl/qyeZ6wG+3nnsyvolaNUS8+R5Wtw6qJPTxIlOP1ZSvcGhEJw+l3HMMmtiNo9Gl61G4GVg==", - "dev": true - }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "ws": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.0.tgz", - "integrity": "sha512-kyFwXuV/5ymf+IXhS6f0+eAFvydbaBW3zjpT6hUdAh/hbVjTIB5EHBGi0bPoCLSK2wcuz3BrEkB9LrYv1Nm4NQ==" - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true - }, - "yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "requires": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - } - }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true + "optional": true + } + } + }, + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/decamelize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", + "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dev": true, + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/diff": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", + "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "dev": true, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", + "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mocha": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.3.0.tgz", + "integrity": "sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg==", + "dev": true, + "dependencies": { + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "8.1.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/serialize-javascript": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", + "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/typescript": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz", + "integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/workerpool": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", + "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "dev": true + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, + "node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", + "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-parser": { + "version": "20.2.4", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", + "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", + "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", + "dev": true, + "dependencies": { + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } + } } diff --git a/package.json b/package.json index 125d36f..2630680 100644 --- a/package.json +++ b/package.json @@ -1,56 +1,55 @@ { - "name": "cryptomarket", - "version": "1.0.6", - "description": "The CryptoMarket for Node.js", - "homepage": "https://www.cryptomkt.com", - "engines": { - "node": "*" - }, - "main": "lib/index.js", - "scripts": { - "test": "env TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha -r ts-node/register 'test/**/*.test.ts'" - }, - "author": "Ismael Verdugo Zambra", - "directories": { - "lib": "./lib" - }, - "repository": { - "type": "git", - "url": "https://github.com/cryptomkt/cryptomkt-node.git" - }, - "bugs": { - "url": "https://github.com/cryptomkt/cryptomkt-node/issues" - }, - "keywords": [ - "api", - "cryptomarket", - "cryptomkt", - "exchange", - "ethereum", - "ether", - "bitcoin", - "btc", - "eos", - "stellar", - "xlm" - ], - "license": "Apache-2.0", - "dependencies": { - "bigdecimal": "^0.6.1", - "crypto-js": "^4.0.0", - "node-fetch": "^2.6.1", - "rxjs": "^6.6.3", - "ws": "^7.4.0" - }, - "devDependencies": { - "@types/chai": "^4.3.1", - "@types/crypto-js": "^4.1.1", - "@types/mocha": "^9.1.1", - "@types/node-fetch": "^2.6.2", - "@types/ws": "^8.5.3", - "chai": "^4.3.6", - "mocha": "^8.4.0", - "ts-node": "^10.8.1", - "typescript": "^4.7.4" - } + "name": "cryptomarket", + "version": "3.0.0", + "description": "The CryptoMarket for Node.js", + "homepage": "https://www.cryptomkt.com", + "engines": { + "node": ">=18" + }, + "main": "lib/index.ts", + "scripts": { + "test": "env TS_NODE_COMPILER_OPTIONS='{\"module\": \"commonjs\" }' mocha -r ts-node/register 'test/**/*.test.ts'" + }, + "author": "Ismael Verdugo Zambra", + "directories": { + "lib": "./lib" + }, + "repository": { + "type": "git", + "url": "https://github.com/cryptomkt/cryptomkt-node.git" + }, + "bugs": { + "url": "https://github.com/cryptomkt/cryptomkt-node/issues" + }, + "keywords": [ + "api", + "cryptomarket", + "cryptomkt", + "exchange", + "ethereum", + "ether", + "bitcoin", + "btc", + "eos", + "stellar", + "xlm" + ], + "license": "Apache-2.0", + "dependencies": { + "bigdecimal": "0.6.1", + "crypto-js": "4.2.0", + "node-fetch": "^2.6.1", + "ws": "^7.4.0" + }, + "devDependencies": { + "@types/chai": "4.3.11", + "@types/crypto-js": "4.2.2", + "@types/mocha": "10.0.6", + "@types/node-fetch": "2.6.11", + "@types/ws": "^8.5.3", + "chai": "^4.3.6", + "mocha": "10.3.0", + "ts-node": "10.9.2", + "typescript": "5.3.3" + } } diff --git a/test/rest/spotTrading.test.ts b/test/rest/spotTrading.test.ts index b19727e..a59a7ec 100644 --- a/test/rest/spotTrading.test.ts +++ b/test/rest/spotTrading.test.ts @@ -13,7 +13,7 @@ import { goodTradingCommission, listSize, } from "../testHelpers"; -const keys = require("/home/ismael/cryptomarket/keys.json"); +const keys = require("../../keys.json"); describe("spot trading", () => { let client = new Client(keys.apiKey, keys.apiSecret); diff --git a/test/rest/spotTradingHistory.test.ts b/test/rest/spotTradingHistory.test.ts index 0a71e58..f3f898e 100644 --- a/test/rest/spotTradingHistory.test.ts +++ b/test/rest/spotTradingHistory.test.ts @@ -1,7 +1,7 @@ import assert from "assert"; import { Client } from "../../lib"; import { goodList, goodOrder, goodTrade } from "../testHelpers"; -const keys = require("/home/ismael/cryptomarket/keys.json"); +const keys = require("../../keys.json"); import "mocha"; diff --git a/test/rest/walletManagement.test.ts b/test/rest/walletManagement.test.ts index ea92f52..bd1f176 100644 --- a/test/rest/walletManagement.test.ts +++ b/test/rest/walletManagement.test.ts @@ -12,7 +12,7 @@ import { goodTransaction, } from "../testHelpers"; import { Address } from "../../lib/models"; -const keys = require("/home/ismael/cryptomarket/keys.json"); +const keys = require("../../keys.json"); describe("wallet management", () => { let client = new Client(keys.apiKey, keys.apiSecret); diff --git a/test/websocket/trading.test.ts b/test/websocket/trading.test.ts index b7314b7..8f75b77 100644 --- a/test/websocket/trading.test.ts +++ b/test/websocket/trading.test.ts @@ -1,4 +1,4 @@ -const keys = require("/home/ismael/cryptomarket/keys.json"); +const keys = require("../../keys.json"); import { expect } from "chai"; import "mocha"; import { WSTradingClient } from "../../lib"; diff --git a/test/websocket/tradingSubscriptions.test.ts b/test/websocket/tradingSubscriptions.test.ts index 3353234..83ff98c 100644 --- a/test/websocket/tradingSubscriptions.test.ts +++ b/test/websocket/tradingSubscriptions.test.ts @@ -1,4 +1,4 @@ -const keys = require("/home/ismael/cryptomarket/keys.json"); +const keys = require("../../keys.json"); import { expect } from "chai"; import { WSTradingClient } from "../../lib"; import { SUBSCRIPTION_MODE } from "../../lib/constants"; diff --git a/test/websocket/wallet.test.ts b/test/websocket/wallet.test.ts index 1cbea22..3559008 100644 --- a/test/websocket/wallet.test.ts +++ b/test/websocket/wallet.test.ts @@ -1,4 +1,4 @@ -const keys = require("/home/ismael/cryptomarket/keys.json"); +const keys = require("../../keys.json"); import { expect } from "chai"; import "mocha"; import { WSWalletClient } from "../../lib"; diff --git a/test/websocket/walletSubscriptions.test.ts b/test/websocket/walletSubscriptions.test.ts index d579633..c142857 100644 --- a/test/websocket/walletSubscriptions.test.ts +++ b/test/websocket/walletSubscriptions.test.ts @@ -2,7 +2,7 @@ import { expect } from "chai"; import { Client, WSWalletClient } from "../../lib/index"; import { Balance, Transaction } from "../../lib/models"; import { SECOND, goodBalance, goodTransaction, timeout } from "../testHelpers"; -const keys = require("/home/ismael/cryptomarket/keys.json"); +const keys = require("../../keys.json"); describe("wallet transactions", function () { let wsclient: WSWalletClient;