UNPKG

ccxt

Version:

A JavaScript / TypeScript / Python / C# / PHP cryptocurrency trading library with support for 100+ exchanges

1,076 lines (1,073 loc) • 126 kB
// ---------------------------------------------------------------------------- // PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN: // https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-contribute-code // EDIT THE CORRESPONDENT .ts FILE INSTEAD // --------------------------------------------------------------------------- import Exchange from './abstract/wavesexchange.js'; import { AuthenticationError, InsufficientFunds, InvalidOrder, AccountSuspended, ExchangeError, DuplicateOrderId, OrderNotFound, BadSymbol, ExchangeNotAvailable, BadRequest, ArgumentsRequired } from './base/errors.js'; import { Precise } from './base/Precise.js'; import { ed25519 } from './static_dependencies/noble-curves/ed25519.js'; import { TICK_SIZE } from './base/functions/number.js'; // --------------------------------------------------------------------------- /** * @class wavesexchange * @augments Exchange */ export default class wavesexchange extends Exchange { describe() { return this.deepExtend(super.describe(), { 'id': 'wavesexchange', 'name': 'Waves.Exchange', 'countries': ['CH'], 'certified': false, 'pro': false, 'dex': true, 'has': { 'CORS': undefined, 'spot': true, 'margin': false, 'swap': false, 'future': false, 'option': false, 'addMargin': false, 'cancelOrder': true, 'closeAllPositions': false, 'closePosition': false, 'createMarketOrder': true, 'createOrder': true, 'createReduceOnlyOrder': false, 'createStopLimitOrder': false, 'createStopMarketOrder': false, 'createStopOrder': false, 'fetchBalance': true, 'fetchBorrowRateHistories': false, 'fetchBorrowRateHistory': false, 'fetchClosedOrders': true, 'fetchCrossBorrowRate': false, 'fetchCrossBorrowRates': false, 'fetchDepositAddress': true, 'fetchDepositAddresses': undefined, 'fetchDepositAddressesByNetwork': undefined, 'fetchDepositWithdrawFee': 'emulated', 'fetchDepositWithdrawFees': true, 'fetchFundingHistory': false, 'fetchFundingRate': false, 'fetchFundingRateHistory': false, 'fetchFundingRates': false, 'fetchIndexOHLCV': false, 'fetchIsolatedBorrowRate': false, 'fetchIsolatedBorrowRates': false, 'fetchLeverage': false, 'fetchLeverageTiers': false, 'fetchMarginMode': false, 'fetchMarkets': true, 'fetchMarkOHLCV': false, 'fetchMyTrades': true, 'fetchOHLCV': true, 'fetchOpenInterestHistory': false, 'fetchOpenOrders': true, 'fetchOrder': true, 'fetchOrderBook': true, 'fetchOrders': true, 'fetchPosition': false, 'fetchPositionHistory': false, 'fetchPositionMode': false, 'fetchPositions': false, 'fetchPositionsForSymbol': false, 'fetchPositionsHistory': false, 'fetchPositionsRisk': false, 'fetchPremiumIndexOHLCV': false, 'fetchTicker': true, 'fetchTickers': true, 'fetchTrades': true, 'fetchTransfer': false, 'fetchTransfers': false, 'reduceMargin': false, 'sandbox': true, 'setLeverage': false, 'setMarginMode': false, 'setPositionMode': false, 'signIn': true, 'transfer': false, 'withdraw': true, 'ws': false, }, 'timeframes': { '1m': '1m', '5m': '5m', '15m': '15m', '30m': '30m', '1h': '1h', '2h': '2h', '3h': '3h', '4h': '4h', '6h': '6h', '12h': '12h', '1d': '1d', '1w': '1w', '1M': '1M', }, 'urls': { 'logo': 'https://user-images.githubusercontent.com/1294454/84547058-5fb27d80-ad0b-11ea-8711-78ac8b3c7f31.jpg', 'test': { 'matcher': 'https://matcher-testnet.wx.network', 'node': 'https://nodes-testnet.wavesnodes.com', 'public': 'https://api-testnet.wavesplatform.com/v0', 'private': 'https://api-testnet.wx.network/v1', 'forward': 'https://testnet.wx.network/api/v1/forward/matcher', 'market': 'https://testnet.wx.network/api/v1/forward/marketdata/api/v1', }, 'api': { 'matcher': 'https://matcher.wx.network', 'node': 'https://nodes.wx.network', 'public': 'https://api.wavesplatform.com/v0', 'private': 'https://api.wx.network/v1', 'forward': 'https://wx.network/api/v1/forward/matcher', 'market': 'https://wx.network/api/v1/forward/marketdata/api/v1', }, 'doc': [ 'https://docs.wx.network', 'https://docs.waves.tech', 'https://api.wavesplatform.com/v0/docs/', 'https://nodes.wavesnodes.com/api-docs/index.html', 'https://matcher.waves.exchange/api-docs/index.html', ], 'www': 'https://wx.network', }, 'api': { 'matcher': { 'get': [ 'matcher', 'matcher/settings', 'matcher/settings/rates', 'matcher/balance/reserved/{publicKey}', 'matcher/debug/allSnashotOffsets', 'matcher/debug/currentOffset', 'matcher/debug/lastOffset', 'matcher/debug/oldestSnapshotOffset', 'matcher/debug/config', 'matcher/debug/address/{address}', 'matcher/debug/status', 'matcher/debug/address/{address}/check', 'matcher/orderbook', 'matcher/orderbook/{baseId}/{quoteId}', 'matcher/orderbook/{baseId}/{quoteId}/publicKey/{publicKey}', 'matcher/orderbook/{baseId}/{quoteId}/{orderId}', 'matcher/orderbook/{baseId}/{quoteId}/info', 'matcher/orderbook/{baseId}/{quoteId}/status', 'matcher/orderbook/{baseId}/{quoteId}/tradableBalance/{address}', 'matcher/orderbook/{publicKey}', 'matcher/orderbook/{publicKey}/{orderId}', 'matcher/orders/{address}', 'matcher/orders/{address}/{orderId}', 'matcher/transactions/{orderId}', 'api/v1/orderbook/{baseId}/{quoteId}', ], 'post': [ 'matcher/orderbook', 'matcher/orderbook/market', 'matcher/orderbook/cancel', 'matcher/orderbook/{baseId}/{quoteId}/cancel', 'matcher/orderbook/{baseId}/{quoteId}/calculateFee', 'matcher/orderbook/{baseId}/{quoteId}/delete', 'matcher/orderbook/{baseId}/{quoteId}/cancelAll', 'matcher/debug/saveSnapshots', 'matcher/orders/{address}/cancel', 'matcher/orders/cancel/{orderId}', 'matcher/orders/serialize', ], 'delete': [ 'matcher/orderbook/{baseId}/{quoteId}', 'matcher/settings/rates/{assetId}', ], 'put': [ 'matcher/settings/rates/{assetId}', ], }, 'node': { 'get': [ 'addresses', 'addresses/balance/{address}', 'addresses/balance/{address}/{confirmations}', 'addresses/balance/details/{address}', 'addresses/data/{address}', 'addresses/data/{address}/{key}', 'addresses/effectiveBalance/{address}', 'addresses/effectiveBalance/{address}/{confirmations}', 'addresses/publicKey/{publicKey}', 'addresses/scriptInfo/{address}', 'addresses/scriptInfo/{address}/meta', 'addresses/seed/{address}', 'addresses/seq/{from}/{to}', 'addresses/validate/{address}', 'alias/by-address/{address}', 'alias/by-alias/{alias}', 'assets/{assetId}/distribution/{height}/{limit}', 'assets/balance/{address}', 'assets/balance/{address}/{assetId}', 'assets/details/{assetId}', 'assets/nft/{address}/limit/{limit}', 'blockchain/rewards', 'blockchain/rewards/height', 'blocks/address/{address}/{from}/{to}/', 'blocks/at/{height}', 'blocks/delay/{signature}/{blockNum}', 'blocks/first', 'blocks/headers/last', 'blocks/headers/seq/{from}/{to}', 'blocks/height', 'blocks/height/{signature}', 'blocks/last', 'blocks/seq/{from}/{to}', 'blocks/signature/{signature}', 'consensus/algo', 'consensus/basetarget', 'consensus/basetarget/{blockId}', 'consensus/{generatingbalance}/address', 'consensus/generationsignature', 'consensus/generationsignature/{blockId}', 'debug/balances/history/{address}', 'debug/blocks/{howMany}', 'debug/configInfo', 'debug/historyInfo', 'debug/info', 'debug/minerInfo', 'debug/portfolios/{address}', 'debug/state', 'debug/stateChanges/address/{address}', 'debug/stateChanges/info/{id}', 'debug/stateWaves/{height}', 'leasing/active/{address}', 'node/state', 'node/version', 'peers/all', 'peers/blacklisted', 'peers/connected', 'peers/suspended', 'transactions/address/{address}/limit/{limit}', 'transactions/info/{id}', 'transactions/status', 'transactions/unconfirmed', 'transactions/unconfirmed/info/{id}', 'transactions/unconfirmed/size', 'utils/seed', 'utils/seed/{length}', 'utils/time', 'wallet/seed', ], 'post': [ 'addresses', 'addresses/data/{address}', 'addresses/sign/{address}', 'addresses/signText/{address}', 'addresses/verify/{address}', 'addresses/verifyText/{address}', 'debug/blacklist', 'debug/print', 'debug/rollback', 'debug/validate', 'node/stop', 'peers/clearblacklist', 'peers/connect', 'transactions/broadcast', 'transactions/calculateFee', 'tranasctions/sign', 'transactions/sign/{signerAddress}', 'tranasctions/status', 'utils/hash/fast', 'utils/hash/secure', 'utils/script/compileCode', 'utils/script/compileWithImports', 'utils/script/decompile', 'utils/script/estimate', 'utils/sign/{privateKey}', 'utils/transactionsSerialize', ], 'delete': [ 'addresses/{address}', 'debug/rollback-to/{signature}', ], }, 'public': { 'get': [ 'assets', 'pairs', 'candles/{baseId}/{quoteId}', 'transactions/exchange', ], }, 'private': { 'get': [ 'deposit/addresses/{currency}', 'deposit/addresses/{currency}/{platform}', 'platforms', 'deposit/currencies', 'withdraw/currencies', 'withdraw/addresses/{currency}/{address}', ], 'post': [ 'oauth2/token', ], }, 'forward': { 'get': [ 'matcher/orders/{address}', 'matcher/orders/{address}/{orderId}', ], 'post': [ 'matcher/orders/{wavesAddress}/cancel', ], }, 'market': { 'get': [ 'tickers', ], }, }, 'currencies': { 'WX': this.safeCurrencyStructure({ 'id': 'EMAMLxDnv3xiz8RXg8Btj33jcEw3wLczL3JKYYmuubpc', 'numericId': undefined, 'code': 'WX', 'precision': this.parseNumber('1e-8') }), }, 'precisionMode': TICK_SIZE, 'options': { 'allowedCandles': 1440, 'accessToken': undefined, 'createMarketBuyOrderRequiresPrice': true, 'matcherPublicKey': undefined, 'quotes': undefined, 'createOrderDefaultExpiry': 2419200000, 'wavesAddress': undefined, 'withdrawFeeUSDN': 7420, 'withdrawFeeWAVES': 100000, 'wavesPrecision': 1e-8, 'messagePrefix': 'W', 'networks': { 'ERC20': 'ETH', 'BEP20': 'BSC', }, }, 'features': { 'spot': { 'sandbox': true, 'createOrder': { 'marginMode': false, 'triggerPrice': true, 'triggerDirection': false, 'triggerPriceType': undefined, 'stopLossPrice': false, 'takeProfitPrice': false, 'attachedStopLossTakeProfit': undefined, 'timeInForce': { 'IOC': false, 'FOK': false, 'PO': false, 'GTD': true, // todo }, 'hedged': false, 'trailing': false, 'leverage': false, 'marketBuyByCost': false, 'marketBuyRequiresPrice': true, 'selfTradePrevention': false, 'iceberg': false, }, 'createOrders': undefined, 'fetchMyTrades': { 'marginMode': false, 'limit': 100, 'daysBack': 100000, 'untilDays': 100000, 'symbolRequired': false, }, 'fetchOrder': { 'marginMode': false, 'trigger': false, 'trailing': false, 'symbolRequired': false, }, 'fetchOpenOrders': { 'marginMode': false, 'limit': 100, 'trigger': false, 'trailing': false, 'symbolRequired': false, }, 'fetchOrders': { 'marginMode': false, 'limit': 100, 'daysBack': undefined, 'untilDays': undefined, 'trigger': false, 'trailing': false, 'symbolRequired': true, }, 'fetchClosedOrders': { 'marginMode': false, 'limit': 100, 'daysBack': 100000, 'daysBackCanceled': 1, 'untilDays': 100000, 'trigger': false, 'trailing': false, 'symbolRequired': false, }, 'fetchOHLCV': { 'limit': undefined, // todo }, }, 'swap': { 'linear': undefined, 'inverse': undefined, }, 'future': { 'linear': undefined, 'inverse': undefined, }, }, 'commonCurrencies': { 'EGG': 'Waves Ducks', }, 'requiresEddsa': true, 'exceptions': { '3147270': InsufficientFunds, '112': InsufficientFunds, '4': ExchangeError, '13': ExchangeNotAvailable, '14': ExchangeNotAvailable, '3145733': AccountSuspended, '3148040': DuplicateOrderId, '3148801': AuthenticationError, '9440512': AuthenticationError, '9440771': BadSymbol, '9441026': InvalidOrder, '9441282': InvalidOrder, '9441286': InvalidOrder, '9441295': InvalidOrder, '9441540': InvalidOrder, '9441542': InvalidOrder, '106954752': AuthenticationError, '106954769': AuthenticationError, '106957828': AuthenticationError, '106960131': AuthenticationError, '106981137': AuthenticationError, '9437184': BadRequest, '9437193': OrderNotFound, '1048577': BadRequest, '1051904': AuthenticationError, }, }); } setSandboxMode(enabled) { this.options['messagePrefix'] = enabled ? 'T' : 'W'; this.options['sandboxMode'] = enabled; super.setSandboxMode(enabled); } async getFeesForAsset(symbol, side, amount, price, params = {}) { await this.loadMarkets(); const market = this.market(symbol); amount = this.toRealSymbolAmount(symbol, amount); price = this.toRealSymbolPrice(symbol, price); const request = this.extend({ 'baseId': market['baseId'], 'quoteId': market['quoteId'], 'orderType': side, 'amount': amount, 'price': price, }, params); return await this.matcherPostMatcherOrderbookBaseIdQuoteIdCalculateFee(request); } async customCalculateFee(symbol, type, side, amount, price, takerOrMaker = 'taker', params = {}) { const response = await this.getFeesForAsset(symbol, side, amount, price); // { // "base":{ // "feeAssetId":"WAVES", // "matcherFee":"1000000" // }, // "discount":{ // "feeAssetId":"EMAMLxDnv3xiz8RXg8Btj33jcEw3wLczL3JKYYmuubpc", // "matcherFee":"4077612" // } // } const isDiscountFee = this.safeBool(params, 'isDiscountFee', false); let mode = undefined; if (isDiscountFee) { mode = this.safeValue(response, 'discount'); } else { mode = this.safeValue(response, 'base'); } const matcherFee = this.safeString(mode, 'matcherFee'); const feeAssetId = this.safeString(mode, 'feeAssetId'); const feeAsset = this.safeCurrencyCode(feeAssetId); const adjustedMatcherFee = this.fromRealCurrencyAmount(feeAsset, matcherFee); const amountAsString = this.numberToString(amount); const priceAsString = this.numberToString(price); const feeCost = this.feeToPrecision(symbol, this.parseNumber(adjustedMatcherFee)); const feeRate = Precise.stringDiv(adjustedMatcherFee, Precise.stringMul(amountAsString, priceAsString)); return { 'type': takerOrMaker, 'currency': feeAsset, 'rate': this.parseNumber(feeRate), 'cost': this.parseNumber(feeCost), }; } async getQuotes() { let quotes = this.safeValue(this.options, 'quotes'); if (quotes) { return quotes; } else { // currencies can have any name because you can create you own token // as a result someone can create a fake token called BTC // we use this mapping to determine the real tokens // https://docs.wx.network/en/waves-matcher/matcher-api#asset-pair const response = await this.matcherGetMatcherSettings(); // { // "orderVersions": [ // 1, // 2, // 3 // ], // "success": true, // "matcherPublicKey": "9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5", // "orderFee": { // "dynamic": { // "baseFee": 300000, // "rates": { // "34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ": 1.22639597, // "62LyMjcr2DtiyF5yVXFhoQ2q414VPPJXjsNYp72SuDCH": 0.00989643, // "HZk1mbfuJpmxU1Fs4AX5MWLVYtctsNcg6e2C6VKqK8zk": 0.0395674, // "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS": 0.00018814, // "4LHHvYGNKJUg5hj65aGD5vgScvCBmLpdRFtjokvCjSL8": 26.19721262, // "474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu": 0.00752978, // "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p": 1.84575, // "B3uGHFRpSUuGEDWjqB9LWWxafQj8VTvpMucEyoxzws5H": 0.02330273, // "zMFqXuoyrn5w17PFurTqxB7GsS71fp9dfk6XFwxbPCy": 0.00721412, // "5WvPKSJXzVE2orvbkJ8wsQmmQKqTv9sGBPksV4adViw3": 0.02659103, // "WAVES": 1, // "BrjUWjndUanm5VsJkbUip8VRYy6LWJePtxya3FNv4TQa": 0.03433583 // } // } // }, // "networkByte": 87, // "matcherVersion": "2.1.3.5", // "status": "SimpleResponse", // "priceAssets": [ // "Ft8X1v1LTa1ABafufpaCWyVj8KkaxUWE6xBhW6sNFJck", // "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p", // "34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ", // "Gtb1WRznfchDnTh37ezoDTJ4wcoKaRsKqKjJjy7nm2zU", // "2mX5DzVKWrAJw8iwdJnV2qtoeVG9h5nTDpTqC1wb1WEN", // "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS", // "WAVES", // "474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu", // "zMFqXuoyrn5w17PFurTqxB7GsS71fp9dfk6XFwxbPCy", // "62LyMjcr2DtiyF5yVXFhoQ2q414VPPJXjsNYp72SuDCH", // "HZk1mbfuJpmxU1Fs4AX5MWLVYtctsNcg6e2C6VKqK8zk", // "B3uGHFRpSUuGEDWjqB9LWWxafQj8VTvpMucEyoxzws5H", // "5WvPKSJXzVE2orvbkJ8wsQmmQKqTv9sGBPksV4adViw3", // "BrjUWjndUanm5VsJkbUip8VRYy6LWJePtxya3FNv4TQa", // "4LHHvYGNKJUg5hj65aGD5vgScvCBmLpdRFtjokvCjSL8" // ] // } quotes = {}; const priceAssets = this.safeValue(response, 'priceAssets'); for (let i = 0; i < priceAssets.length; i++) { quotes[priceAssets[i]] = true; } this.options['quotes'] = quotes; return quotes; } } /** * @method * @name wavesexchange#fetchMarkets * @description retrieves data on all markets for wavesexchange * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object[]} an array of objects representing market data */ async fetchMarkets(params = {}) { const response = await this.marketGetTickers(); // // [ // { // "symbol": "WAVES/BTC", // "amountAssetID": "WAVES", // "amountAssetName": "Waves", // "amountAssetDecimals": 8, // "amountAssetTotalSupply": "106908766.00000000", // "amountAssetMaxSupply": "106908766.00000000", // "amountAssetCirculatingSupply": "106908766.00000000", // "priceAssetID": "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS", // "priceAssetName": "WBTC", // "priceAssetDecimals": 8, // "priceAssetTotalSupply": "20999999.96007507", // "priceAssetMaxSupply": "20999999.96007507", // "priceAssetCirculatingSupply": "20999999.66019601", // "24h_open": "0.00032688", // "24h_high": "0.00033508", // "24h_low": "0.00032443", // "24h_close": "0.00032806", // "24h_vwap": "0.00032988", // "24h_volume": "42349.69440104", // "24h_priceVolume": "13.97037207", // "timestamp":1640232379124 // } // ... // ] // const result = []; for (let i = 0; i < response.length; i++) { const entry = response[i]; const baseId = this.safeString(entry, 'amountAssetID'); const quoteId = this.safeString(entry, 'priceAssetID'); const id = baseId + '/' + quoteId; const marketId = this.safeString(entry, 'symbol'); let [base, quote] = marketId.split('/'); base = this.safeCurrencyCode(base); quote = this.safeCurrencyCode(quote); const symbol = base + '/' + quote; result.push({ 'id': id, 'symbol': symbol, 'base': base, 'quote': quote, 'settle': undefined, 'baseId': baseId, 'quoteId': quoteId, 'settleId': undefined, 'type': 'spot', 'spot': true, 'margin': false, 'swap': false, 'future': false, 'option': false, 'active': undefined, 'contract': false, 'linear': undefined, 'inverse': undefined, 'contractSize': undefined, 'expiry': undefined, 'expiryDatetime': undefined, 'strike': undefined, 'optionType': undefined, 'precision': { 'amount': this.parseNumber(this.parsePrecision(this.safeString(entry, 'amountAssetDecimals'))), 'price': this.parseNumber(this.parsePrecision(this.safeString(entry, 'priceAssetDecimals'))), }, 'limits': { 'leverage': { 'min': undefined, 'max': undefined, }, 'amount': { 'min': undefined, 'max': undefined, }, 'price': { 'min': undefined, 'max': undefined, }, 'cost': { 'min': undefined, 'max': undefined, }, }, 'created': undefined, 'info': entry, }); } return result; } /** * @method * @name wavesexchange#fetchOrderBook * @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @see https://matcher.waves.exchange/api-docs/index.html#/markets/getOrderBook * @param {string} symbol unified symbol of the market to fetch the order book for * @param {int} [limit] the maximum amount of order book entries to return * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols */ async fetchOrderBook(symbol, limit = undefined, params = {}) { await this.loadMarkets(); const market = this.market(symbol); const request = this.extend({ 'baseId': market['baseId'], 'quoteId': market['quoteId'], }, params); const response = await this.matcherGetMatcherOrderbookBaseIdQuoteId(request); const timestamp = this.safeInteger(response, 'timestamp'); const bids = this.parseOrderBookSide(this.safeValue(response, 'bids'), market, limit); const asks = this.parseOrderBookSide(this.safeValue(response, 'asks'), market, limit); return { 'symbol': symbol, 'bids': bids, 'asks': asks, 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), 'nonce': undefined, }; } parseOrderBookSide(bookSide, market = undefined, limit = undefined) { const precision = market['precision']; const wavesPrecision = this.safeString(this.options, 'wavesPrecision', '1e-8'); const amountPrecisionString = this.safeString(precision, 'amount'); const pricePrecisionString = this.safeString(precision, 'price'); const difference = Precise.stringDiv(amountPrecisionString, pricePrecisionString); const pricePrecision = Precise.stringDiv(wavesPrecision, difference); const result = []; for (let i = 0; i < bookSide.length; i++) { const entry = bookSide[i]; const entryPrice = this.safeString(entry, 'price', '0'); const entryAmount = this.safeString(entry, 'amount', '0'); let price = undefined; let amount = undefined; if ((pricePrecision !== undefined) && (entryPrice !== undefined)) { price = Precise.stringMul(entryPrice, pricePrecision); } if ((amountPrecisionString !== undefined) && (entryAmount !== undefined)) { amount = Precise.stringMul(entryAmount, amountPrecisionString); } if ((limit !== undefined) && (i > limit)) { break; } result.push([ this.parseNumber(price), this.parseNumber(amount), ]); } return result; } checkRequiredKeys() { if (this.apiKey === undefined) { throw new AuthenticationError(this.id + ' requires apiKey credential'); } if (this.secret === undefined) { throw new AuthenticationError(this.id + ' requires secret credential'); } let apiKeyBytes = undefined; let secretKeyBytes = undefined; try { apiKeyBytes = this.base58ToBinary(this.apiKey); } catch (e) { throw new AuthenticationError(this.id + ' apiKey must be a base58 encoded public key'); } try { secretKeyBytes = this.base58ToBinary(this.secret); } catch (e) { throw new AuthenticationError(this.id + ' secret must be a base58 encoded private key'); } const hexApiKeyBytes = this.binaryToBase16(apiKeyBytes); const hexSecretKeyBytes = this.binaryToBase16(secretKeyBytes); if (hexApiKeyBytes.length !== 64) { throw new AuthenticationError(this.id + ' apiKey must be a base58 encoded public key'); } if (hexSecretKeyBytes.length !== 64) { throw new AuthenticationError(this.id + ' secret must be a base58 encoded private key'); } return true; } sign(path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) { const query = this.omit(params, this.extractParams(path)); const isCancelOrder = path === 'matcher/orders/{wavesAddress}/cancel'; path = this.implodeParams(path, params); let url = this.urls['api'][api] + '/' + path; let queryString = this.urlencodeWithArrayRepeat(query); if ((api === 'private') || (api === 'forward')) { headers = { 'Accept': 'application/json', }; const accessToken = this.safeString(this.options, 'accessToken'); if (accessToken) { headers['Authorization'] = 'Bearer ' + accessToken; } if (method === 'POST') { headers['content-type'] = 'application/json'; } else { headers['content-type'] = 'application/x-www-form-urlencoded'; } if (isCancelOrder) { body = this.json([query['orderId']]); queryString = ''; } if (queryString.length > 0) { url += '?' + queryString; } } else if (api === 'matcher') { if (method === 'POST') { headers = { 'Accept': 'application/json', 'Content-Type': 'application/json', }; body = this.json(query); } else { headers = query; } } else { if (method === 'POST') { headers = { 'content-type': 'application/json', }; body = this.json(query); } else { headers = { 'content-type': 'application/x-www-form-urlencoded', }; if (queryString.length > 0) { url += '?' + queryString; } } } return { 'url': url, 'method': method, 'body': body, 'headers': headers }; } /** * @method * @name wavesexchange#signIn * @description sign in, must be called prior to using other authenticated methods * @see https://docs.wx.network/en/api/auth/oauth2-token * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns response from exchange */ async signIn(params = {}) { if (!this.safeString(this.options, 'accessToken')) { const prefix = 'ffffff01'; const expiresDelta = 60 * 60 * 24 * 7; let seconds = this.sum(this.seconds(), expiresDelta); seconds = seconds.toString(); const clientId = 'wx.network'; // W for production, T for testnet const defaultMessagePrefix = this.safeString(this.options, 'messagePrefix', 'W'); const message = defaultMessagePrefix + ':' + clientId + ':' + seconds; const messageHex = this.binaryToBase16(this.encode(message)); const payload = prefix + messageHex; const hexKey = this.binaryToBase16(this.base58ToBinary(this.secret)); const signature = this.axolotl(payload, hexKey, ed25519); const request = { 'grant_type': 'password', 'scope': 'general', 'username': this.apiKey, 'password': seconds + ':' + signature, 'client_id': clientId, }; const response = await this.privatePostOauth2Token(request); // { access_token: "eyJhbGciOXJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzaWciOiJiaTZiMVhMQlo0M1Q4QmRTSlVSejJBZGlQdVlpaFZQYVhhVjc4ZGVIOEpTM3M3NUdSeEU1VkZVOE5LRUI0UXViNkFHaUhpVFpuZ3pzcnhXdExUclRvZTgiLCJhIjoiM1A4VnpMU2EyM0VXNUNWY2tIYlY3ZDVCb043NWZGMWhoRkgiLCJuYiI6IlciLCJ1c2VyX25hbWUiOiJBSFhuOG5CQTRTZkxRRjdoTFFpU24xNmt4eWVoaml6QkdXMVRkcm1TWjFnRiIsInNjb3BlIjpbImdlbmVyYWwiXSwibHQiOjYwNDc5OSwicGsiOiJBSFhuOG5CQTRTZkxRRjdoTFFpU24xNmt4eWVoaml6QkdXMVRkcm1TWjFnRiIsImV4cCI6MTU5MTk3NTA1NywiZXhwMCI6MTU5MTk3NTA1NywianRpIjoiN2JhOTUxMTMtOGI2MS00NjEzLTlkZmYtNTEwYTc0NjlkOWI5IiwiY2lkIjoid2F2ZXMuZXhjaGFuZ2UifQ.B-XwexBnUAzbWknVN68RKT0ZP5w6Qk1SKJ8usL3OIwDEzCUUX9PjW-5TQHmiCRcA4oft8lqXEiCwEoNfsblCo_jTpRo518a1vZkIbHQk0-13Dm1K5ewGxfxAwBk0g49odcbKdjl64TN1yM_PO1VtLVuiTeZP-XF-S42Uj-7fcO-r7AulyQLuTE0uo-Qdep8HDCk47rduZwtJOmhFbCCnSgnLYvKWy3CVTeldsR77qxUY-vy8q9McqeP7Id-_MWnsob8vWXpkeJxaEsw1Fke1dxApJaJam09VU8EB3ZJWpkT7V8PdafIrQGeexx3jhKKxo7rRb4hDV8kfpVoCgkvFan", // "token_type": "bearer", // "refresh_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzaWciOiJiaTZiMVhMQlo0M1Q4QmRTSlVSejJBZGlQdVlpaFZQYVhhVjc4ZGVIOEpTM3M3NUdSeEU1VkZVOE5LRUI0UXViNkFHaUhpVFpuZ3pzcnhXdExUclRvZTgiLCJhIjoiM1A4VnpMU2EyM0VXNUNWY2tIYlY3ZDVCb043NWZGMWhoRkgiLCJuYiI6IlciLCJ1c2VyX25hbWUiOiJBSFhuOG5CQTRTZkxRRjdoTFFpU24xNmt4eWVoaml6QkdXMVRkcm1TWjFnRiIsInNjb3BlIjpbImdlbmVyYWwiXSwiYXRpIjoiN2JhOTUxMTMtOGI2MS00NjEzLTlkZmYtNTEwYTc0NjlkXWI5IiwibHQiOjYwNDc5OSwicGsiOiJBSFhuOG5CQTRTZkxRRjdoTFFpU24xNmt4eWVoaml6QkdXMVRkcm1TWjFnRiIsImV4cCI6MTU5Mzk2MjI1OCwiZXhwMCI6MTU5MTk3NTA1NywianRpIjoiM2MzZWRlMTktNjI5My00MTNlLWJmMWUtZTRlZDZlYzUzZTgzIiwiY2lkIjoid2F2ZXMuZXhjaGFuZ2UifQ.gD1Qj0jfqayfZpBvNY0t3ccMyK5hdbT7dY-_5L6LxwV0Knan4ndEtvygxlTOczmJUKtnA4T1r5GBFgNMZTvtViKZIbqZNysEg2OY8UxwDaF4VPeGJLg_QXEnn8wBeBQdyMafh9UQdwD2ci7x-saM4tOAGmncAygfTDxy80201gwDhfAkAGerb9kL00oWzSJScldxu--pNLDBUEHZt52MSEel10HGrzvZkkvvSh67vcQo5TOGb5KG6nh65UdJCwr41AVz4fbQPP-N2Nkxqy0TE_bqVzZxExXgvcS8TS0Z82T3ijJa_ct7B9wblpylBnvmyj3VycUzufD6uy8MUGq32D", // "expires_in": 604798, // "scope": "general" } this.options['accessToken'] = this.safeString(response, 'access_token'); return this.options['accessToken']; } return undefined; } parseTicker(ticker, market = undefined) { // // { // "symbol": "WAVES/BTC", // "amountAssetID": "WAVES", // "amountAssetName": "Waves", // "amountAssetDecimals": 8, // "amountAssetTotalSupply": "106908766.00000000", // "amountAssetMaxSupply": "106908766.00000000", // "amountAssetCirculatingSupply": "106908766.00000000", // "priceAssetID": "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS", // "priceAssetName": "WBTC", // "priceAssetDecimals": 8, // "priceAssetTotalSupply": "20999999.96007507", // "priceAssetMaxSupply": "20999999.96007507", // "priceAssetCirculatingSupply": "20999999.66019601", // "24h_open": "0.00032688", // "24h_high": "0.00033508", // "24h_low": "0.00032443", // "24h_close": "0.00032806", // "24h_vwap": "0.00032988", // "24h_volume": "42349.69440104", // "24h_priceVolume": "13.97037207", // "timestamp":1640232379124 // } // // fetch ticker // // { // "firstPrice": "21749", // "lastPrice": "22000", // "volume": "0.73747149", // "quoteVolume": "16409.44564928645471", // "high": "23589.999941", // "low": "21010.000845", // "weightedAveragePrice": "22250.955964", // "txsCount": "148", // "volumeWaves": "0.0000000000680511203072" // } // const timestamp = this.safeInteger(ticker, 'timestamp'); const marketId = this.safeString(ticker, 'symbol'); market = this.safeMarket(marketId, market, '/'); const symbol = market['symbol']; const last = this.safeString2(ticker, '24h_close', 'lastPrice'); const low = this.safeString2(ticker, '24h_low', 'low'); const high = this.safeString2(ticker, '24h_high', 'high'); const vwap = this.safeString2(ticker, '24h_vwap', 'weightedAveragePrice'); const baseVolume = this.safeString2(ticker, '24h_volume', 'volume'); const quoteVolume = this.safeString2(ticker, '24h_priceVolume', 'quoteVolume'); const open = this.safeString2(ticker, '24h_open', 'firstPrice'); return this.safeTicker({ 'symbol': symbol, 'timestamp': timestamp, 'datetime': this.iso8601(timestamp), 'high': high, 'low': low, 'bid': undefined, 'bidVolume': undefined, 'ask': undefined, 'askVolume': undefined, 'vwap': vwap, 'open': open, 'close': last, 'last': last, 'previousClose': undefined, 'change': undefined, 'percentage': undefined, 'average': undefined, 'baseVolume': baseVolume, 'quoteVolume': quoteVolume, 'info': ticker, }, market); } /** * @method * @name wavesexchange#fetchTicker * @description fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market * @see https://api.wavesplatform.com/v0/docs/#/pairs/getPairsListAll * @param {string} symbol unified symbol of the market to fetch the ticker for * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ async fetchTicker(symbol, params = {}) { await this.loadMarkets(); const market = this.market(symbol); const request = { 'pairs': market['id'], }; const response = await this.publicGetPairs(this.extend(request, params)); // // { // "__type":"list", // "data":[ // { // "__type":"pair", // "data":{ // "firstPrice":0.00012512, // "lastPrice":0.00012441, // "low":0.00012167, // "high":0.00012768, // "weightedAveragePrice":0.000124710697407246, // "volume":209554.26356614, // "quoteVolume":26.1336583539951, // "volumeWaves":209554.26356614, // "txsCount":6655 // }, // "amountAsset":"WAVES", // "priceAsset":"8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS" // } // ] // } // const data = this.safeValue(response, 'data', []); const ticker = this.safeValue(data, 0, {}); const dataTicker = this.safeDict(ticker, 'data', {}); return this.parseTicker(dataTicker, market); } /** * @method * @name wavesexchange#fetchTickers * @description fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market * @param {string[]} [symbols] unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned * @param {object} [params] extra parameters specific to the exchange API endpoint * @returns {object} a dictionary of [ticker structures]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ async fetchTickers(symbols = undefined, params = {}) { await this.loadMarkets(); const response = await this.marketGetTickers(params); // // [ // { // "symbol": "WAVES/BTC", // "amountAssetID": "WAVES", // "amountAssetName": "Waves", // "amountAssetDecimals": 8, // "amountAssetTotalSupply": "106908766.00000000", // "amountAssetMaxSupply": "106908766.00000000", // "amountAssetCirculatingSupply": "106908766.00000000", // "priceAssetID": "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS", // "priceAssetName": "WBTC", // "priceAssetDecimals": 8, // "priceAssetTotalSupply": "20999999.96007507", // "priceAssetMaxSupply": "20999999.96007507", // "priceAssetCirculatingSupply": "20999999.66019601", // "24h_open": "0.00032688", // "24h_high": "0.00033508", // "24h_low": "0.00032443", // "24h_close": "0.00032806", // "24h_vwap": "0.00032988", // "24h_volume": "42349.69440104", // "24h_priceVolume": "13.97037207", // "timestamp":1640232379124 // } // ... // ] // return this.parseTickers(response, symbols); } /** * @method * @name wavesexchange#fetchOHLCV * @description fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market * @see https://api.wavesplatform.com/v0/docs/#/candles/getCandles * @param {string} symbol unified symbol of the market to fetch OHLCV data for * @param {string} timeframe the length of time each candle represents * @param {int} [since] timestamp in ms of the earliest candle to fetch * @param {int} [limit] the maximum amount of candles to fetch * @param {object} [params] extra parameters specific to the exchange API endpoint * @param {int} [params.until] timestamp in ms of the latest candle to fetch * @returns {int[][]} A list of candles ordered as timestamp, open, high, low, close, volume */ async fetchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { await this.loadMarkets(); const market = this.market(symbol); const request = { 'baseId': market['baseId'], 'quoteId': market['quoteId'], 'interval': this.safeString(this.timeframes, timeframe, timeframe), }; const allowedCandles = this.safeInteger(this.options, 'allowedCandles', 1440); const until = this.safeInteger(params, 'until'); const untilIsDefined = until !== undefined; if (limit === undefined) { limit = allowedCandles; } limit = Math.min(allowedCandles, limit); const duration = this.parseTimeframe(timeframe) * 1000; if (since === undefined) { const now = this.milliseconds(); const timeEnd = untilIsDefined ? until : now; const durationRoundedTimestamp = this.parseToInt(timeEnd / duration) * duration; const delta = (limit - 1) * duration; const timeStart = durationRoundedTimestamp - delta; request['timeStart'] = timeStart.toString(); if (untilIsDefined) { request['timeEnd'] = until.toString(); } } else { request['timeStart'] = since.toString(); if (untilIsDefined) { request['timeEnd'] = until.toString(); }