UNPKG

@proton/ccxt

Version:

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

1,086 lines (1,084 loc) 117 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 { ArgumentsRequired, AuthenticationError, InsufficientFunds, InvalidOrder, AccountSuspended, ExchangeError, DuplicateOrderId, OrderNotFound, BadSymbol, ExchangeNotAvailable, BadRequest } from './base/errors.js'; import { Precise } from './base/Precise.js'; import { ed25519 } from './static_dependencies/noble-curves/ed25519.js'; import { eddsa } from './base/functions/crypto.js'; import { DECIMAL_PLACES } from './base/functions/number.js'; // --------------------------------------------------------------------------- export default class wavesexchange extends Exchange { describe() { return this.deepExtend(super.describe(), { 'id': 'wavesexchange', 'name': 'Waves.Exchange', 'countries': ['CH'], 'certified': true, 'pro': false, 'has': { 'CORS': undefined, 'spot': true, 'margin': false, 'swap': false, 'future': false, 'option': false, 'addMargin': false, 'cancelOrder': true, 'createMarketOrder': true, 'createOrder': true, 'createReduceOnlyOrder': false, 'createStopLimitOrder': false, 'createStopMarketOrder': false, 'createStopOrder': false, 'fetchBalance': true, 'fetchBorrowRate': false, 'fetchBorrowRateHistories': false, 'fetchBorrowRateHistory': false, 'fetchBorrowRates': false, 'fetchBorrowRatesPerSymbol': false, 'fetchClosedOrders': true, 'fetchDepositAddress': true, 'fetchDepositWithdrawFee': 'emulated', 'fetchDepositWithdrawFees': true, 'fetchFundingHistory': false, 'fetchFundingRate': false, 'fetchFundingRateHistory': false, 'fetchFundingRates': false, 'fetchIndexOHLCV': 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, 'fetchPositionMode': false, 'fetchPositions': false, 'fetchPositionsRisk': false, 'fetchPremiumIndexOHLCV': false, 'fetchTicker': true, 'fetchTickers': true, 'fetchTrades': true, 'fetchTransfer': false, 'fetchTransfers': false, 'reduceMargin': false, 'setLeverage': false, 'setMarginMode': false, 'setPositionMode': false, 'signIn': true, 'transfer': false, 'withdraw': true, }, '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.waves.exchange', 'node': 'https://nodes-testnet.wavesnodes.com', 'public': 'https://api-testnet.wavesplatform.com/v0', 'private': 'https://api-testnet.waves.exchange/v1', 'forward': 'https://testnet.waves.exchange/api/v1/forward/matcher', 'market': 'https://testnet.waves.exchange/api/v1/forward/marketdata/api/v1', }, 'api': { 'matcher': 'https://matcher.waves.exchange', 'node': 'https://nodes.waves.exchange', 'public': 'https://api.wavesplatform.com/v0', 'private': 'https://api.waves.exchange/v1', 'forward': 'https://waves.exchange/api/v1/forward/matcher', 'market': 'https://waves.exchange/api/v1/forward/marketdata/api/v1', }, 'doc': 'https://docs.waves.exchange', 'www': 'https://waves.exchange', }, '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/orderbook', 'matcher/orderbook/{amountAsset}/{priceAsset}', '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}/tradeableBalance/{address}', 'matcher/orderbook/{publicKey}', 'matcher/orderbook/{publicKey}/{orderId}', 'matcher/orders/{address}', 'matcher/orders/{address}/{orderId}', 'matcher/transactions/{orderId}', ], 'post': [ 'matcher/orderbook', 'matcher/orderbook/market', 'matcher/orderbook/cancel', 'matcher/orderbook/{baseId}/{quoteId}/cancel', 'matcher/orderbook/{amountAsset}/{priceAsset}/calculateFee', 'matcher/debug/saveSnapshots', 'matcher/orders/{address}/cancel', 'matcher/orders/cancel/{orderId}', ], '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('8') }), }, 'precisionMode': DECIMAL_PLACES, 'options': { 'allowedCandles': 1440, 'accessToken': undefined, 'createMarketBuyOrderRequiresPrice': true, 'matcherPublicKey': undefined, 'quotes': undefined, 'createOrderDefaultExpiry': 2419200000, 'wavesAddress': undefined, 'withdrawFeeUSDN': 7420, 'withdrawFeeWAVES': 100000, 'wavesPrecision': 8, 'messagePrefix': 'W', 'networks': { 'ERC20': 'ETH', 'BEP20': 'BSC', }, 'networksById': { 'ETH': 'ERC20', 'BSC': 'BEP20', }, }, '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'; super.setSandboxMode(enabled); } async getFeesForAsset(symbol, side, amount, price, params = {}) { await this.loadMarkets(); const market = this.market(symbol); amount = this.customAmountToPrecision(symbol, amount); price = this.customPriceToPrecision(symbol, price); const request = this.extend({ 'amountAsset': market['baseId'], 'priceAsset': market['quoteId'], 'orderType': side, 'amount': amount, 'price': price, }, params); return await this.matcherPostMatcherOrderbookAmountAssetPriceAssetCalculateFee(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.safeValue(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.currencyFromPrecision(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.waves.exchange/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; } } async fetchMarkets(params = {}) { /** * @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 */ 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.safeInteger(entry, 'amountAssetDecimals'), 'price': this.safeInteger(entry, 'priceAssetDecimals'), }, 'limits': { 'leverage': { 'min': undefined, 'max': undefined, }, 'amount': { 'min': undefined, 'max': undefined, }, 'price': { 'min': undefined, 'max': undefined, }, 'cost': { 'min': undefined, 'max': undefined, }, }, 'info': entry, }); } return result; } async fetchOrderBook(symbol, limit = undefined, params = {}) { /** * @method * @name wavesexchange#fetchOrderBook * @description fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data * @param {string} symbol unified symbol of the market to fetch the order book for * @param {int|undefined} limit the maximum amount of order book entries to return * @param {object} params extra parameters specific to the wavesexchange api endpoint * @returns {object} A dictionary of [order book structures]{@link https://docs.ccxt.com/#/?id=order-book-structure} indexed by market symbols */ await this.loadMarkets(); const market = this.market(symbol); const request = this.extend({ 'amountAsset': market['baseId'], 'priceAsset': market['quoteId'], }, params); const response = await this.matcherGetMatcherOrderbookAmountAssetPriceAsset(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.safeInteger(this.options, 'wavesPrecision', 8); const amountPrecision = Math.pow(10, precision['amount']); const difference = precision['amount'] - precision['price']; const pricePrecision = Math.pow(10, wavesPrecision - difference); const result = []; for (let i = 0; i < bookSide.length; i++) { const entry = bookSide[i]; const price = this.safeInteger(entry, 'price', 0) / pricePrecision; const amount = this.safeInteger(entry, 'amount', 0) / amountPrecision; if ((limit !== undefined) && (i > limit)) { break; } result.push([price, 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'); } } 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 }; } async signIn(params = {}) { /** * @method * @name wavesexchange#signIn * @description sign in, must be called prior to using other authenticated methods * @param {object} params extra parameters specific to the wavesexchange api endpoint * @returns response from exchange */ 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 = 'waves.exchange'; // 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 = eddsa(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); } async fetchTicker(symbol, params = {}) { /** * @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 * @param {string} symbol unified symbol of the market to fetch the ticker for * @param {object} params extra parameters specific to the wavesexchange api endpoint * @returns {object} a [ticker structure]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ 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.safeValue(ticker, 'data', {}); return this.parseTicker(dataTicker, market); } async fetchTickers(symbols = undefined, params = {}) { /** * @method * @name wavesexchange#fetchTickers * @description fetches price tickers for multiple markets, statistical calculations with the information calculated over the past 24 hours each market * @param {[string]|undefined} 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 aax api endpoint * @returns {object} a dictionary of [ticker structures]{@link https://docs.ccxt.com/#/?id=ticker-structure} */ 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); } async fetchOHLCV(symbol, timeframe = '1m', since = undefined, limit = undefined, params = {}) { /** * @method * @name wavesexchange#fetchOHLCV * @description fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market * @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|undefined} since timestamp in ms of the earliest candle to fetch * @param {int|undefined} limit the maximum amount of candles to fetch * @param {object} params extra parameters specific to the wavesexchange api endpoint * @returns {[[int]]} A list of candles ordered as timestamp, open, high, low, close, volume */ 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); if (limit === undefined) { limit = allowedCandles; } limit = Math.min(allowedCandles, limit); const duration = this.parseTimeframe(timeframe) * 1000; if (since === undefined) { const durationRoundedTimestamp = this.parseToInt(this.milliseconds() / duration) * duration; const delta = (limit - 1) * duration; const timeStart = durationRoundedTimestamp - delta; request['timeStart'] = timeStart.toString(); } else { request['timeStart'] = since.toString(); const timeEnd = this.sum(since, duration * limit); request['timeEnd'] = timeEnd.toString(); } const response = await this.publicGetCandlesBaseIdQuoteId(this.extend(request, params)); // // { // "__type": "list", // "data": [ // { // "__type": "candle", // "data": { // "time": "2020-06-09T14:47:00.000Z", // "open": 0.0250385, // "close": 0.0250385, // "high": 0.0250385, // "low": 0.0250385, // "volume": 0.01033012, // "quoteVolume": 0.00025865, // "weightedAveragePrice": 0.0250385, // "maxHeight": 2099399, // "txsCount": 5, // "timeClose": "2020-06-09T14:47:59.999Z" // } // } // ] // } // const data = this.safeValue(response, 'data', []); let result = this.parseOHLCVs(data, market, timeframe, since, limit); result = this.filterFutureCandles(result); let lastClose = undefined; const length = result.length; for (let i = 0; i < result.length; i++) { const j = length - i - 1; const entry = result[j]; const open = entry[1]; if (open === undefined) { entry[1] = lastClose; entry[2] = lastClose; entry[3] = lastClose; entry[4] = lastClose; result[j] = entry; } lastClose = entry[4]; } return result; } filterFutureCandles(ohlcvs) { const result = []; const timestamp = this.milliseconds(); for (let i = 0; i < ohlcvs.length; i++) { if (ohlcvs[i][0] > timestamp) { // stop when getting data from the future break; } result.push(ohlcvs[i]); } return result; } parseOHLCV(ohlcv, market = undefined) { // // { // __type: 'candle', // data: { // time: '2020-06-05T20:46:00.000Z', // open: 240.573975, // close: 240.573975, // high: 240.573975, // low: 240.573975, // volume: 0.01278413, // quoteVolume: 3.075528, // weightedAveragePrice: 240.573975, // maxHeight: 2093895, // txsCount: 5, // timeClose: '2020-06-05T20:46:59.999Z' // } // } // const data = this.safeValue(ohlcv, 'data', {}); return [ this.parse8601(this.safeString(data, 'time')), this.safeNumber(data, 'open'), this.safeNumber(data, 'high'), this.safeNumber(data, 'low'), this.safeNumber(data, 'close'), this.safeNumber(data, 'volume', 0), ]; } async fetchDepositAddress(code, params = {}) { /** * @method * @name wavesexchange#fetchDepositAddress * @description fetch the deposit address for a currency associated with this account * @param {string} code unified currency code * @param {object} params extra parameters specific to the wavesexchange api endpoint * @returns {object} an [address structure]{@link https://docs.ccxt.com/#/?id=address-structure} */ await this.signIn(); const networks = this.safeValue(this.options, 'networks', {}); const rawNetwork = this.safeStringUpper(params, 'network'); const network = this.safeString(networks, rawNetwork, rawNetwork); params = this.omit(params, ['network']); const supportedCurrencies = await this.privateGetPlatforms(); // // { // "type": "list", // "page_info": { // "has_next_page": false, // "last_cursor": null // }, // "items": [ // { // "type": "platform", // "id": "ETH", // "name": "Ethereum", // "currencies": [ // "BAG", // "BNT", // "CRV", // "EGG", // "ETH", // "EURN", // "FL", // "NSBT", // "USDAP", // "USDC", // "USDFL", // "USDN", // "USDT", // "WAVES" // ] // } // ] // } // const currencies = {}; const networksByCurrency = {}; const items = this.safeValue(supportedCurrencies, 'items', []); for (let i = 0; i < items.length; i++) { const entry = items[i]; const currencyId = this.safeString(entry, 'id'); const innerCurrencies = this.safeValue(entry, 'currencies', []); for (let j = 0; j < innerC