text: # -*- coding: utf-8 -*-
  
  # 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
  
  from ccxt.async_support.base.exchange import Exchange
  import math
  from ccxt.base.errors import ExchangeError
  from ccxt.base.errors import AuthenticationError
  from ccxt.base.errors import AccountSuspended
  from ccxt.base.errors import ArgumentsRequired
  from ccxt.base.errors import BadRequest
  from ccxt.base.errors import BadSymbol
  from ccxt.base.errors import InsufficientFunds
  from ccxt.base.errors import InvalidOrder
  from ccxt.base.errors import OrderNotFound
  from ccxt.base.errors import DuplicateOrderId
  from ccxt.base.errors import ExchangeNotAvailable
  from ccxt.base.precise import Precise
  
  
  class wavesexchange(Exchange):
  
      def describe(self):
          return self.deep_extend(super(wavesexchange, self).describe(), {
              'id': 'wavesexchange',
              'name': 'Waves.Exchange',
              'countries': ['CH'],  # Switzerland
              'rateLimit': 500,
              'certified': True,
              'pro': False,
              'has': {
                  'cancelOrder': True,
                  'createMarketOrder': False,
                  'createOrder': True,
                  'fetchBalance': True,
                  'fetchClosedOrders': True,
                  'fetchDepositAddress': True,
                  'fetchMarkets': True,
                  'fetchMyTrades': True,
                  'fetchOHLCV': True,
                  'fetchOpenOrders': True,
                  'fetchOrderBook': True,
                  'fetchOrders': True,
                  'fetchTicker': True,
                  'fetchTrades': True,
                  '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',
                  'api': {
                      'matcher': 'http://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/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': [
                          'pairs',
                          'candles/{baseId}/{quoteId}',
                          'transactions/exchange',
                      ],
                  },
                  'private': {
                      'get': [
                          'deposit/addresses/{code}',
                          'deposit/currencies',
                          'withdraw/currencies',
                          'withdraw/addresses/{currency}/{address}',
                      ],
                      'post': [
                          'oauth2/token',
                      ],
                  },
                  'forward': {
                      'get': [
                          'matcher/orders/{address}',  # can't get the orders endpoint to work with the matcher api
                          'matcher/orders/{address}/{orderId}',
                      ],
                      'post': [
                          'matcher/orders/{wavesAddress}/cancel',
                      ],
                  },
                  'market': {
                      'get': [
                          'tickers',
                      ],
                  },
              },
              'options': {
                  'allowedCandles': 1440,
                  'accessToken': None,
                  'matcherPublicKey': None,
                  'quotes': None,
                  'createOrderDefaultExpiry': 2419200000,  # 60 * 60 * 24 * 28 * 1000
                  'wavesAddress': None,
                  'withdrawFeeUSDN': 7420,
                  'withdrawFeeWAVES': 100000,
                  'wavesPrecision': 8,
              },
              'requiresEddsa': True,
              'exceptions': {
                  '3147270': InsufficientFunds,  # https://github.com/wavesplatform/matcher/wiki/List-of-all-errors
                  '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,
                  '9437193': OrderNotFound,
                  '1048577': BadRequest,
                  '1051904': AuthenticationError,
              },
          })
  
      async def get_quotes(self):
          quotes = self.safe_value(self.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 self mapping to determine the real tokens
              # https://docs.waves.exchange/en/waves-matcher/matcher-api#asset-pair
              response = await self.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 = {}
              priceAssets = self.safe_value(response, 'priceAssets')
              for i in range(0, len(priceAssets)):
                  quotes[priceAssets[i]] = True
              self.options['quotes'] = quotes
              return quotes
  
      async def fetch_markets(self, params={}):
          response = await self.marketGetTickers()
          result = []
          for i in range(0, len(response)):
              entry = response[i]
              baseId = self.safe_string(entry, 'amountAssetID')
              quoteId = self.safe_string(entry, 'priceAssetID')
              id = baseId + '/' + quoteId
              marketId = self.safe_string(entry, 'symbol')
              base, quote = marketId.split('/')
              symbol = self.safe_currency_code(base) + '/' + self.safe_currency_code(quote)
              precision = {
                  'amount': self.safe_integer(entry, 'amountAssetDecimals'),
                  'price': self.safe_integer(entry, 'priceAssetDecimals'),
              }
              result.append({
                  'symbol': symbol,
                  'id': id,
                  'base': base,
                  'quote': quote,
                  'baseId': baseId,
                  'quoteId': quoteId,
                  'info': entry,
                  'precision': precision,
              })
          return result
  
      async def fetch_order_book(self, symbol, limit=None, params={}):
          await self.load_markets()
          market = self.market(symbol)
          request = self.extend({
              'amountAsset': market['baseId'],
              'priceAsset': market['quoteId'],
          }, params)
          response = await self.matcherGetMatcherOrderbookAmountAssetPriceAsset(request)
          timestamp = self.safe_integer(response, 'timestamp')
          bids = self.parse_order_book_side(self.safe_value(response, 'bids'), market, limit)
          asks = self.parse_order_book_side(self.safe_value(response, 'asks'), market, limit)
          return {
              'symbol': symbol,
              'bids': bids,
              'asks': asks,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'nonce': None,
          }
  
      def parse_order_book_side(self, bookSide, market=None, limit=None):
          precision = market['precision']
          wavesPrecision = self.safe_integer(self.options, 'wavesPrecision', 8)
          amountPrecision = math.pow(10, precision['amount'])
          difference = precision['amount'] - precision['price']
          pricePrecision = math.pow(10, wavesPrecision - difference)
          result = []
          for i in range(0, len(bookSide)):
              entry = bookSide[i]
              price = self.safe_integer(entry, 'price', 0) / pricePrecision
              amount = self.safe_integer(entry, 'amount', 0) / amountPrecision
              if (limit is not None) and (i > limit):
                  break
              result.append([price, amount])
          return result
  
      def check_required_keys(self):
          if self.apiKey is None:
              raise AuthenticationError(self.id + ' requires apiKey credential')
          if self.secret is None:
              raise AuthenticationError(self.id + ' requires secret credential')
          apiKeyBytes = None
          secretKeyBytes = None
          try:
              apiKeyBytes = self.base58_to_binary(self.apiKey)
          except Exception as e:
              raise AuthenticationError(self.id + ' apiKey must be a base58 encoded public key')
          try:
              secretKeyBytes = self.base58_to_binary(self.secret)
          except Exception as e:
              raise AuthenticationError(self.id + ' secret must be a base58 encoded private key')
          hexApiKeyBytes = self.binary_to_base16(apiKeyBytes)
          hexSecretKeyBytes = self.binary_to_base16(secretKeyBytes)
          if len(hexApiKeyBytes) != 64:
              raise AuthenticationError(self.id + ' apiKey must be a base58 encoded public key')
          if len(hexSecretKeyBytes) != 64:
              raise AuthenticationError(self.id + ' secret must be a base58 encoded private key')
  
      def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
          query = self.omit(params, self.extract_params(path))
          isCancelOrder = path == 'matcher/orders/{wavesAddress}/cancel'
          path = self.implode_params(path, params)
          url = self.urls['api'][api] + '/' + path
          queryString = self.urlencode(query)
          if (api == 'private') or (api == 'forward'):
              headers = {
                  'Accept': 'application/json',
              }
              accessToken = self.safe_string(self.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 = self.json([query['orderId']])
                  queryString = ''
              if len(queryString) > 0:
                  url += '?' + queryString
          elif api == 'matcher':
              if method == 'POST':
                  headers = {
                      'content-type': 'application/json',
                  }
                  body = self.json(query)
              else:
                  headers = query
          else:
              if method == 'POST':
                  headers = {
                      'content-type': 'application/json',
                  }
                  body = self.json(query)
              else:
                  headers = {
                      'content-type': 'application/x-www-form-urlencoded',
                  }
                  if len(queryString) > 0:
                      url += '?' + queryString
          return {'url': url, 'method': method, 'body': body, 'headers': headers}
  
      async def get_access_token(self):
          if not self.safe_string(self.options, 'accessToken'):
              prefix = 'ffffff01'
              expiresDelta = 60 * 60 * 24 * 7
              seconds = self.sum(self.seconds(), expiresDelta)
              seconds = str(seconds)
              clientId = 'waves.exchange'
              message = 'W:' + clientId + ':' + seconds
              messageHex = self.binary_to_base16(self.encode(message))
              payload = prefix + messageHex
              hexKey = self.binary_to_base16(self.base58_to_binary(self.secret))
              signature = self.eddsa(payload, hexKey, 'ed25519')
              request = {
                  'grant_type': 'password',
                  'scope': 'general',
                  'username': self.apiKey,
                  'password': seconds + ':' + signature,
                  'client_id': clientId,
              }
              response = await self.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'}
              self.options['accessToken'] = self.safe_string(response, 'access_token')
              return self.options['accessToken']
  
      def parse_ticker(self, ticker, market=None):
          #
          #     {
          #         "__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"
          #     }
          #
          timestamp = None
          baseId = self.safe_string(ticker, 'amountAsset')
          quoteId = self.safe_string(ticker, 'priceAsset')
          symbol = None
          if (baseId is not None) and (quoteId is not None):
              marketId = baseId + '/' + quoteId
              if marketId in self.markets_by_id:
                  market = self.markets_by_id[marketId]
              else:
                  base = self.safe_currency_code(baseId)
                  quote = self.safe_currency_code(quoteId)
                  symbol = base + '/' + quote
          if (symbol is None) and (market is not None):
              symbol = market['symbol']
          data = self.safe_value(ticker, 'data', {})
          last = self.safe_number(data, 'lastPrice')
          low = self.safe_number(data, 'low')
          high = self.safe_number(data, 'high')
          vwap = self.safe_number(data, 'weightedAveragePrice')
          baseVolume = self.safe_number(data, 'volume')
          quoteVolume = self.safe_number(data, 'quoteVolume')
          open = self.safe_number(data, 'firstPrice')
          return self.safe_ticker({
              'symbol': symbol,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'high': high,
              'low': low,
              'bid': None,
              'bidVolume': None,
              'ask': None,
              'askVolume': None,
              'vwap': vwap,
              'open': open,
              'close': last,
              'last': last,
              'previousClose': None,
              'change': None,
              'percentage': None,
              'average': None,
              'baseVolume': baseVolume,
              'quoteVolume': quoteVolume,
              'info': ticker,
          }, market)
  
      async def fetch_ticker(self, symbol, params={}):
          await self.load_markets()
          market = self.market(symbol)
          request = {
              'pairs': market['id'],
          }
          response = await self.publicGetPairs(self.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"
          #             }
          #         ]
          #     }
          #
          data = self.safe_value(response, 'data', [])
          ticker = self.safe_value(data, 0, {})
          return self.parse_ticker(ticker, market)
  
      async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
          await self.load_markets()
          market = self.market(symbol)
          request = {
              'baseId': market['baseId'],
              'quoteId': market['quoteId'],
              'interval': self.timeframes[timeframe],
          }
          if since is not None:
              request['timeStart'] = str(since)
          else:
              allowedCandles = self.safe_integer(self.options, 'allowedCandles', 1440)
              timeframeUnix = self.parse_timeframe(timeframe) * 1000
              currentTime = int(math.floor(self.milliseconds()) / timeframeUnix) * timeframeUnix
              delta = (allowedCandles - 1) * timeframeUnix
              timeStart = currentTime - delta
              request['timeStart'] = str(timeStart)
          response = await self.publicGetCandlesBaseIdQuoteId(self.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"
          #                 }
          #             }
          #         ]
          #     }
          #
          data = self.safe_value(response, 'data', [])
          result = self.parse_ohlcvs(data, market, timeframe, since, limit)
          lastClose = None
          length = len(result)
          for i in range(0, len(result)):
              j = length - i - 1
              entry = result[j]
              open = entry[1]
              if open is None:
                  entry[1] = lastClose
                  entry[2] = lastClose
                  entry[3] = lastClose
                  entry[4] = lastClose
                  result[j] = entry
              lastClose = entry[4]
          return result
  
      def parse_ohlcv(self, ohlcv, market=None):
          #
          #     {
          #         __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'
          #         }
          #     }
          #
          data = self.safe_value(ohlcv, 'data', {})
          return [
              self.parse8601(self.safe_string(data, 'time')),
              self.safe_number(data, 'open'),
              self.safe_number(data, 'high'),
              self.safe_number(data, 'low'),
              self.safe_number(data, 'close'),
              self.safe_number(data, 'volume', 0),
          ]
  
      async def fetch_deposit_address(self, code, params={}):
          await self.get_access_token()
          supportedCurrencies = await self.privateGetDepositCurrencies()
          currencies = {}
          items = self.safe_value(supportedCurrencies, 'items', [])
          for i in range(0, len(items)):
              entry = items[i]
              currencyCode = self.safe_string(entry, 'id')
              currencies[currencyCode] = True
          if not (code in currencies):
              codes = list(currencies.keys())
              raise ExchangeError(self.id + ' fetch ' + code + ' deposit address not supported. Currency code must be one of ' + str(codes))
          request = self.extend({
              'code': code,
          }, params)
          response = await self.privateGetDepositAddressesCode(request)
          # {
          #   "type": "deposit_addresses",
          #   "currency": {
          #     "type": "deposit_currency",
          #     "id": "ERGO",
          #     "waves_asset_id": "5dJj4Hn9t2Ve3tRpNGirUHy4yBK6qdJRAJYV21yPPuGz",
          #     "decimals": 9,
          #     "status": "active",
          #     "allowed_amount": {
          #       "min": 0.001,
          #       "max": 100000
          #     },
          #     "fees": {
          #       "flat": 0,
          #       "rate": 0
          #     }
          #   },
          #   "deposit_addresses": [
          #     "9fRAAQjF8Yqg7qicQCL884zjimsRnuwsSavsM1rUdDaoG8mThku"
          #   ]
          # }
          addresses = self.safe_value(response, 'deposit_addresses')
          address = self.safe_string(addresses, 0)
          return {
              'address': address,
              'code': code,
              'tag': None,
              'info': response,
          }
  
      async def get_matcher_public_key(self):
          # self method returns a single string
          matcherPublicKey = self.safe_string(self.options, 'matcherPublicKey')
          if matcherPublicKey:
              return matcherPublicKey
          else:
              response = await self.matcherGetMatcher()
              # remove trailing quotes from string response
              self.options['matcherPublicKey'] = response[1:len(response) - 1]
              return self.options['matcherPublicKey']
  
      def get_asset_bytes(self, currencyId):
          if currencyId == 'WAVES':
              return self.number_to_be(0, 1)
          else:
              return self.binary_concat(self.number_to_be(1, 1), self.base58_to_binary(currencyId))
  
      def get_asset_id(self, currencyId):
          if currencyId == 'WAVES':
              return ''
          return currencyId
  
      def price_to_precision(self, symbol, price):
          market = self.markets[symbol]
          wavesPrecision = self.safe_integer(self.options, 'wavesPrecision', 8)
          difference = market['precision']['amount'] - market['precision']['price']
          return int(float(self.to_precision(price, wavesPrecision - difference)))
  
      def amount_to_precision(self, symbol, amount):
          return int(float(self.to_precision(amount, self.markets[symbol]['precision']['amount'])))
  
      def currency_to_precision(self, currency, amount):
          return int(float(self.to_precision(amount, self.currencies[currency]['precision'])))
  
      def from_precision(self, amount, scale):
          if amount is None:
              return None
          precise = Precise(amount)
          precise.decimals = precise.decimals + scale
          precise.reduce()
          return str(precise)
  
      def to_precision(self, amount, scale):
          amountString = str(amount)
          precise = Precise(amountString)
          precise.decimals = precise.decimals - scale
          precise.reduce()
          return str(precise)
  
      def currency_from_precision(self, currency, amount):
          scale = self.currencies[currency]['precision']
          return self.from_precision(amount, scale)
  
      def price_from_precision(self, symbol, price):
          market = self.markets[symbol]
          wavesPrecision = self.safe_integer(self.options, 'wavesPrecision', 8)
          scale = wavesPrecision - market['precision']['amount'] + market['precision']['price']
          return self.from_precision(price, scale)
  
      async def create_order(self, symbol, type, side, amount, price=None, params={}):
          self.check_required_dependencies()
          self.check_required_keys()
          await self.load_markets()
          market = self.market(symbol)
          matcherPublicKey = await self.get_matcher_public_key()
          amountAsset = self.get_asset_id(market['baseId'])
          priceAsset = self.get_asset_id(market['quoteId'])
          amount = self.amount_to_precision(symbol, amount)
          price = self.price_to_precision(symbol, price)
          orderType = 0 if (side == 'buy') else 1
          timestamp = self.milliseconds()
          defaultExpiryDelta = self.safe_integer(self.options, 'createOrderDefaultExpiry', 2419200000)
          expiration = self.sum(timestamp, defaultExpiryDelta)
          settings = await self.matcherGetMatcherSettings()
          # {
          #   "orderVersions": [
          #     1,
          #     2,
          #     3
          #   ],
          #   "success": True,
          #   "matcherPublicKey": "9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5",
          #   "orderFee": {
          #     "dynamic": {
          #       "baseFee": 300000,
          #       "rates": {
          #         "34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ": 1.0257813,
          #         "62LyMjcr2DtiyF5yVXFhoQ2q414VPPJXjsNYp72SuDCH": 0.01268146,
          #         "HZk1mbfuJpmxU1Fs4AX5MWLVYtctsNcg6e2C6VKqK8zk": 0.05232404,
          #         "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS": 0.00023985,
          #         "4LHHvYGNKJUg5hj65aGD5vgScvCBmLpdRFtjokvCjSL8": 19.5967716,
          #         "474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu": 0.00937073,
          #         "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p": 2.19825,
          #         "B3uGHFRpSUuGEDWjqB9LWWxafQj8VTvpMucEyoxzws5H": 0.03180264,
          #         "zMFqXuoyrn5w17PFurTqxB7GsS71fp9dfk6XFwxbPCy": 0.00996631,
          #         "5WvPKSJXzVE2orvbkJ8wsQmmQKqTv9sGBPksV4adViw3": 0.03254476,
          #         "WAVES": 1,
          #         "BrjUWjndUanm5VsJkbUip8VRYy6LWJePtxya3FNv4TQa": 0.03703704
          #       }
          #     }
          #   },
          #   "networkByte": 87,
          #   "matcherVersion": "2.1.4.8",
          #   "status": "SimpleResponse",
          #   "priceAssets": [
          #     "Ft8X1v1LTa1ABafufpaCWyVj8KkaxUWE6xBhW6sNFJck",
          #     "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p",
          #     "34N9YcEETLWn93qYQ64EsP1x89tSruJU44RrEMSXXEPJ",
          #     "Gtb1WRznfchDnTh37ezoDTJ4wcoKaRsKqKjJjy7nm2zU",
          #     "2mX5DzVKWrAJw8iwdJnV2qtoeVG9h5nTDpTqC1wb1WEN",
          #     "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS",
          #     "WAVES",
          #     "474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu",
          #     "zMFqXuoyrn5w17PFurTqxB7GsS71fp9dfk6XFwxbPCy",
          #     "62LyMjcr2DtiyF5yVXFhoQ2q414VPPJXjsNYp72SuDCH",
          #     "HZk1mbfuJpmxU1Fs4AX5MWLVYtctsNcg6e2C6VKqK8zk",
          #     "B3uGHFRpSUuGEDWjqB9LWWxafQj8VTvpMucEyoxzws5H",
          #     "5WvPKSJXzVE2orvbkJ8wsQmmQKqTv9sGBPksV4adViw3",
          #     "BrjUWjndUanm5VsJkbUip8VRYy6LWJePtxya3FNv4TQa",
          #     "4LHHvYGNKJUg5hj65aGD5vgScvCBmLpdRFtjokvCjSL8"
          #   ]
          # }
          orderFee = self.safe_value(settings, 'orderFee')
          dynamic = self.safe_value(orderFee, 'dynamic')
          baseMatcherFee = self.safe_string(dynamic, 'baseFee')
          wavesMatcherFee = self.currency_from_precision('WAVES', baseMatcherFee)
          rates = self.safe_value(dynamic, 'rates')
          # choose sponsored assets from the list of priceAssets above
          priceAssets = list(rates.keys())
          matcherFeeAssetId = None
          matcherFee = None
          if 'feeAssetId' in params:
              matcherFeeAssetId = params['feeAssetId']
          elif 'feeAssetId' in self.options:
              matcherFeeAssetId = self.options['feeAssetId']
          else:
              balances = await self.fetch_balance()
              if balances['WAVES']['free'] > wavesMatcherFee:
                  matcherFeeAssetId = 'WAVES'
                  matcherFee = baseMatcherFee
              else:
                  for i in range(0, len(priceAssets)):
                      assetId = priceAssets[i]
                      code = self.safe_currency_code(assetId)
                      balance = self.safe_value(self.safe_value(balances, code, {}), 'free')
                      assetFee = Precise.string_mul(rates[assetId], wavesMatcherFee)
                      if (balance is not None) and (balance > assetFee):
                          matcherFeeAssetId = assetId
                          break
          if matcherFeeAssetId is None:
              raise InsufficientFunds(self.id + ' not enough funds to cover the fee, specify feeAssetId in params or options, or buy some WAVES')
          if matcherFee is None:
              wavesPrecision = self.safe_integer(self.options, 'wavesPrecision', 8)
              rate = self.safe_string(rates, matcherFeeAssetId)
              code = self.safe_currency_code(matcherFeeAssetId)
              currency = self.currency(code)
              newPrecison = wavesPrecision - currency['precision']
              matcherFee = self.from_precision(Precise.string_mul(rate, baseMatcherFee), newPrecison)
              # ceil the fee
              matcherFee = Precise.string_div(Precise.string_add(matcherFee, '1'), '1', 0)
          byteArray = [
              self.number_to_be(3, 1),
              self.base58_to_binary(self.apiKey),
              self.base58_to_binary(matcherPublicKey),
              self.get_asset_bytes(market['baseId']),
              self.get_asset_bytes(market['quoteId']),
              self.number_to_be(orderType, 1),
              self.number_to_be(price, 8),
              self.number_to_be(amount, 8),
              self.number_to_be(timestamp, 8),
              self.number_to_be(expiration, 8),
              self.number_to_be(matcherFee, 8),
              self.get_asset_bytes(matcherFeeAssetId),
          ]
          binary = self.binary_concat_array(byteArray)
          signature = self.eddsa(self.binary_to_base16(binary), self.binary_to_base16(self.base58_to_binary(self.secret)), 'ed25519')
          assetPair = {
              'amountAsset': amountAsset,
              'priceAsset': priceAsset,
          }
          body = {
              'senderPublicKey': self.apiKey,
              'matcherPublicKey': matcherPublicKey,
              'assetPair': assetPair,
              'orderType': side,
              'price': price,
              'amount': amount,
              'timestamp': timestamp,
              'expiration': expiration,
              'matcherFee': int(matcherFee),
              'signature': signature,
              'version': 3,
          }
          if matcherFeeAssetId != 'WAVES':
              body['matcherFeeAssetId'] = matcherFeeAssetId
          response = await self.matcherPostMatcherOrderbook(body)
          # {success: True,
          #   message:
          #    {version: 3,
          #      id: 'Do7cDJMf2MJuFyorvxNNuzS42MXSGGEq1r1hGDn1PHiS',
          #      sender: '3P8VzLSa23EW5CVckHbV7d5BoN75fF1hhFH',
          #      senderPublicKey: 'AHXn8nBA4SfLQF7hLQiSn16kxyehjizBGW1TdrmSZ1gF',
          #      matcherPublicKey: '9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5',
          #      assetPair:
          #       {amountAsset: null,
          #         priceAsset: '8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS'},
          #      orderType: 'sell',
          #      amount: 1,
          #      price: 100000000,
          #      timestamp: 1591593117995,
          #      expiration: 1594012317995,
          #      matcherFee: 300000,
          #      matcherFeeAssetId: null,
          #      signature: '2EG8zgE6Ze1X5EYA8DbfFiPXAtC7NniYBAMFbJUbzwVbHmmCKHornQfS5F32NwkHF4623KWq1U6K126h4TTqyVq',
          #      proofs:
          #       ['2EG8zgE6Ze1X5EYA8DbfFiPXAtC7NniYBAMFbJUbzwVbHmmCKHornQfS5F32NwkHF4623KWq1U6K126h4TTqyVq']},
          #   status: 'OrderAccepted'}
          value = self.safe_value(response, 'message')
          return self.parse_order(value, market)
  
      async def cancel_order(self, id, symbol=None, params={}):
          self.check_required_dependencies()
          self.check_required_keys()
          await self.get_access_token()
          wavesAddress = await self.get_waves_address()
          response = await self.forwardPostMatcherOrdersWavesAddressCancel({
              'wavesAddress': wavesAddress,
              'orderId': id,
          })
          #  {
          #    "success":true,
          #    "message":[[{"orderId":"EBpJeGM36KKFz5gTJAUKDBm89V8wqxKipSFBdU35AN3c","success":true,"status":"OrderCanceled"}]],
          #    "status":"BatchCancelCompleted"
          #  }
          message = self.safe_value(response, 'message')
          firstMessage = self.safe_value(message, 0)
          firstOrder = self.safe_value(firstMessage, 0)
          returnedId = self.safe_string(firstOrder, 'orderId')
          return {
              'info': response,
              'id': returnedId,
              'clientOrderId': None,
              'timestamp': None,
              'datetime': None,
              'lastTradeTimestamp': None,
              'symbol': symbol,
              'type': None,
              'side': None,
              'price': None,
              'amount': None,
              'cost': None,
              'average': None,
              'filled': None,
              'remaining': None,
              'status': None,
              'fee': None,
              'trades': None,
          }
  
      async def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
          self.check_required_dependencies()
          self.check_required_keys()
          if symbol is None:
              raise ArgumentsRequired(self.id + ' fetchOrders() requires symbol argument')
          await self.load_markets()
          market = self.market(symbol)
          timestamp = self.milliseconds()
          byteArray = [
              self.base58_to_binary(self.apiKey),
              self.number_to_be(timestamp, 8),
          ]
          binary = self.binary_concat_array(byteArray)
          hexSecret = self.binary_to_base16(self.base58_to_binary(self.secret))
          signature = self.eddsa(self.binary_to_base16(binary), hexSecret, 'ed25519')
          request = {
              'Accept': 'application/json',
              'Timestamp': str(timestamp),
              'Signature': signature,
              'publicKey': self.apiKey,
              'baseId': market['baseId'],
              'quoteId': market['quoteId'],
          }
          response = await self.matcherGetMatcherOrderbookBaseIdQuoteIdPublicKeyPublicKey(self.extend(request, params))
          # [{id: '3KicDeWayY2mdrRoYdCkP3gUAoUZUNT1AA6GAtWuPLfa',
          #     type: 'sell',
          #     orderType: 'limit',
          #     amount: 1,
          #     fee: 300000,
          #     price: 100000000,
          #     timestamp: 1591651254076,
          #     filled: 0,
          #     filledFee: 0,
          #     feeAsset: 'WAVES',
          #     status: 'Accepted',
          #     assetPair:
          #      {amountAsset: null,
          #        priceAsset: '8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS'},
          #     avgWeighedPrice: 0}, ...]
          return self.parse_orders(response, market, since, limit)
  
      async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
          await self.load_markets()
          await self.get_access_token()
          market = None
          if symbol is not None:
              market = self.market(symbol)
          address = await self.get_waves_address()
          request = {
              'address': address,
              'activeOnly': True,
          }
          response = await self.forwardGetMatcherOrdersAddress(request)
          return self.parse_orders(response, market, since, limit)
  
      async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
          await self.load_markets()
          await self.get_access_token()
          market = None
          if symbol is not None:
              market = self.market(symbol)
          address = await self.get_waves_address()
          request = {
              'address': address,
              'closedOnly': True,
          }
          response = await self.forwardGetMatcherOrdersAddress(request)
          # [
          #   {
          #     "id": "9aXcxvXai73jbAm7tQNnqaQ2PwUjdmWuyjvRTKAHsw4f",
          #     "type": "buy",
          #     "orderType": "limit",
          #     "amount": 23738330,
          #     "fee": 300000,
          #     "price": 3828348334,
          #     "timestamp": 1591926905636,
          #     "filled": 23738330,
          #     "filledFee": 300000,
          #     "feeAsset": "WAVES",
          #     "status": "Filled",
          #     "assetPair": {
          #       "amountAsset": "HZk1mbfuJpmxU1Fs4AX5MWLVYtctsNcg6e2C6VKqK8zk",
          #       "priceAsset": null
          #     },
          #     "avgWeighedPrice": 3828348334
          #   }, ...
          # ]
          return self.parse_orders(response, market, since, limit)
  
      def parse_order_status(self, status):
          statuses = {
              'Cancelled': 'canceled',
              'Accepted': 'open',
              'Filled': 'closed',
              'PartiallyFilled': 'open',
          }
          return self.safe_string(statuses, status, status)
  
      def get_symbol_from_asset_pair(self, assetPair):
          # a blank string or null can indicate WAVES
          baseId = self.safe_string(assetPair, 'amountAsset', 'WAVES')
          quoteId = self.safe_string(assetPair, 'priceAsset', 'WAVES')
          return self.safe_currency_code(baseId) + '/' + self.safe_currency_code(quoteId)
  
      def parse_order(self, order, market=None):
          #
          #     createOrder
          #
          #     {
          #         version: 3,
          #         id: 'BshyeHXDfJmTnjTdBYt371jD4yWaT3JTP6KpjpsiZepS',
          #         sender: '3P8VzLSa23EW5CVckHbV7d5BoN75fF1hhFH',
          #         senderPublicKey: 'AHXn8nBA4SfLQF7hLQiSn16kxyehjizBGW1TdrmSZ1gF',
          #         matcherPublicKey: '9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5',
          #         assetPair: {
          #             amountAsset: '474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu',
          #             priceAsset: 'DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p'
          #         },
          #         orderType: 'buy',
          #         amount: 10000,
          #         price: 400000000,
          #         timestamp: 1599848586891,
          #         expiration: 1602267786891,
          #         matcherFee: 3008,
          #         matcherFeeAssetId: '474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu',
          #         signature: '3D2h8ubrhuWkXbVn4qJ3dvjmZQxLoRNfjTqb9uNpnLxUuwm4fGW2qGH6yKFe2SQPrcbgkS3bDVe7SNtMuatEJ7qy',
          #         proofs: [
          #             '3D2h8ubrhuWkXbVn4qJ3dvjmZQxLoRNfjTqb9uNpnLxUuwm4fGW2qGH6yKFe2SQPrcbgkS3bDVe7SNtMuatEJ7qy'
          #         ]
          #     }
          #
          #     fetchClosedOrders
          #
          #     {
          #         id: '81D9uKk2NfmZzfG7uaJsDtxqWFbJXZmjYvrL88h15fk8',
          #         type: 'buy',
          #         orderType: 'limit',
          #         amount: 30000000000,
          #         filled: 0,
          #         price: 1000000,
          #         fee: 300000,
          #         filledFee: 0,
          #         feeAsset: 'WAVES',
          #         timestamp: 1594303779322,
          #         status: 'Cancelled',
          #         assetPair: {
          #             amountAsset: '474jTeYx2r2Va35794tCScAXWJG9hU2HcgxzMowaZUnu',
          #             priceAsset: 'WAVES'
          #         },
          #         avgWeighedPrice: 0,
          #         version: 3
          #     }
          #
          timestamp = self.safe_integer(order, 'timestamp')
          side = self.safe_string_2(order, 'type', 'orderType')
          type = 'limit'
          if 'type' in order:
              # fetchOrders
              type = self.safe_string(order, 'orderType', type)
          id = self.safe_string(order, 'id')
          filledString = self.safe_string(order, 'filled')
          priceString = self.safe_string(order, 'price')
          amountString = self.safe_string(order, 'amount')
          assetPair = self.safe_value(order, 'assetPair')
          symbol = None
          if assetPair is not None:
              symbol = self.get_symbol_from_asset_pair(assetPair)
          elif market is not None:
              symbol = market['symbol']
          amountCurrency = self.safe_currency_code(self.safe_string(assetPair, 'amountAsset', 'WAVES'))
          price = self.parse_number(self.price_from_precision(symbol, priceString))
          amount = self.parse_number(self.currency_from_precision(amountCurrency, amountString))
          filled = self.parse_number(self.currency_from_precision(amountCurrency, filledString))
          average = self.parse_number(self.price_from_precision(symbol, self.safe_string(order, 'avgWeighedPrice')))
          status = self.parse_order_status(self.safe_string(order, 'status'))
          fee = None
          if 'type' in order:
              currency = self.safe_currency_code(self.safe_string(order, 'feeAsset'))
              fee = {
                  'currency': currency,
                  'fee': self.parse_number(self.currency_from_precision(currency, self.safe_string(order, 'filledFee'))),
              }
          else:
              currency = self.safe_currency_code(self.safe_string(order, 'matcherFeeAssetId', 'WAVES'))
              fee = {
                  'currency': currency,
                  'fee': self.parse_number(self.currency_from_precision(currency, self.safe_string(order, 'matcherFee'))),
              }
          return self.safe_order({
              'info': order,
              'id': id,
              'clientOrderId': None,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'lastTradeTimestamp': None,
              'symbol': symbol,
              'type': type,
              'timeInForce': None,
              'postOnly': None,
              'side': side,
              'price': price,
              'stopPrice': None,
              'amount': amount,
              'cost': None,
              'average': average,
              'filled': filled,
              'remaining': None,
              'status': status,
              'fee': fee,
              'trades': None,
          })
  
      async def get_waves_address(self):
          cachedAddreess = self.safe_string(self.options, 'wavesAddress')
          if cachedAddreess is None:
              request = {
                  'publicKey': self.apiKey,
              }
              response = await self.nodeGetAddressesPublicKeyPublicKey(request)
              self.options['wavesAddress'] = self.safe_string(response, 'address')
              return self.options['wavesAddress']
          else:
              return cachedAddreess
  
      async def fetch_balance(self, params={}):
          # makes a lot of different requests to get all the data
          # in particular:
          # fetchMarkets, getWavesAddress,
          # getTotalBalance(doesn't include waves), getReservedBalance(doesn't include waves)
          # getReservedBalance(includes WAVES)
          # I couldn't find another way to get all the data
          self.check_required_dependencies()
          self.check_required_keys()
          await self.load_markets()
          wavesAddress = await self.get_waves_address()
          request = {
              'address': wavesAddress,
          }
          totalBalance = await self.nodeGetAssetsBalanceAddress(request)
          # {
          #   "address": "3P8VzLSa23EW5CVckHbV7d5BoN75fF1hhFH",
          #   "balances": [
          #     {
          #       "assetId": "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p",
          #       "balance": 1177200,
          #       "reissuable": False,
          #       "minSponsoredAssetFee": 7420,
          #       "sponsorBalance": 47492147189709,
          #       "quantity": 999999999775381400,
          #       "issueTransaction": {
          #         "senderPublicKey": "BRnVwSVctnV8pge5vRpsJdWnkjWEJspFb6QvrmZvu3Ht",
          #         "quantity": 1000000000000000000,
          #         "fee": 100400000,
          #         "description": "Neutrino USD",
          #         "type": 3,
          #         "version": 2,
          #         "reissuable": False,
          #         "script": null,
          #         "sender": "3PC9BfRwJWWiw9AREE2B3eWzCks3CYtg4yo",
          #         "feeAssetId": null,
          #         "chainId": 87,
          #         "proofs": [
          #           "3HNpbVkgP69NWSeb9hGYauiQDaXrRXh3tXFzNsGwsAAXnFrA29SYGbLtziW9JLpXEq7qW1uytv5Fnm5XTUMB2BxU"
          #         ],
          #         "assetId": "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p",
          #         "decimals": 6,
          #         "name": "USD-N",
          #         "id": "DG2xFkPdDwKUoBkzGAhQtLpSGzfXLiCYPEzeKH2Ad24p",
          #         "timestamp": 1574429393962
          #       }
          #     }
          #   ]
          # }
          balances = self.safe_value(totalBalance, 'balances')
          result = {}
          timestamp = None
          for i in range(0, len(balances)):
              entry = balances[i]
              entryTimestamp = self.safe_integer(entry, 'timestamp')
              timestamp = entryTimestamp if (timestamp is None) else max(timestamp, entryTimestamp)
              issueTransaction = self.safe_value(entry, 'issueTransaction')
              decimals = self.safe_integer(issueTransaction, 'decimals')
              currencyId = self.safe_string(entry, 'assetId')
              balance = self.safe_string(entry, 'balance')
              code = None
              if currencyId in self.currencies_by_id:
                  code = self.safe_currency_code(currencyId)
                  result[code] = self.account()
                  result[code]['total'] = self.from_precision(balance, decimals)
          currentTimestamp = self.milliseconds()
          byteArray = [
              self.base58_to_binary(self.apiKey),
              self.number_to_be(currentTimestamp, 8),
          ]
          binary = self.binary_concat_array(byteArray)
          hexSecret = self.binary_to_base16(self.base58_to_binary(self.secret))
          signature = self.eddsa(self.binary_to_base16(binary), hexSecret, 'ed25519')
          matcherRequest = {
              'publicKey': self.apiKey,
              'signature': signature,
              'timestamp': str(currentTimestamp),
          }
          reservedBalance = await self.matcherGetMatcherBalanceReservedPublicKey(matcherRequest)
          # {WAVES: 200300000}
          reservedKeys = list(reservedBalance.keys())
          for i in range(0, len(reservedKeys)):
              currencyId = reservedKeys[i]
              code = self.safe_currency_code(currencyId)
              if not (code in result):
                  result[code] = self.account()
              amount = self.safe_string(reservedBalance, currencyId)
              result[code]['used'] = self.currency_from_precision(code, amount)
          wavesRequest = {
              'address': wavesAddress,
          }
          wavesTotal = await self.nodeGetAddressesBalanceAddress(wavesRequest)
          # {
          #   "address": "3P8VzLSa23EW5CVckHbV7d5BoN75fF1hhFH",
          #   "confirmations": 0,
          #   "balance": 909085978
          # }
          result['WAVES'] = self.safe_value(result, 'WAVES', {})
          result['WAVES']['total'] = self.currency_from_precision('WAVES', self.safe_string(wavesTotal, 'balance'))
          codes = list(result.keys())
          for i in range(0, len(codes)):
              code = codes[i]
              if self.safe_value(result[code], 'used') is None:
                  result[code]['used'] = '0'
          result['timestamp'] = timestamp
          result['datetime'] = self.iso8601(timestamp)
          return self.parse_balance(result)
  
      async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
          await self.load_markets()
          market = self.market(symbol)
          address = await self.get_waves_address()
          request = {
              'sender': address,
              'amountAsset': market['baseId'],
              'priceAsset': market['quoteId'],
          }
          response = await self.publicGetTransactionsExchange(request)
          data = self.safe_value(response, 'data')
          return self.parse_trades(data, market, since, limit)
  
      async def fetch_trades(self, symbol, since=None, limit=None, params={}):
          await self.load_markets()
          market = self.market(symbol)
          request = {
              'amountAsset': market['baseId'],
              'priceAsset': market['quoteId'],
          }
          if limit is not None:
              request['limit'] = limit
          if since is not None:
              request['timeStart'] = since
          response = await self.publicGetTransactionsExchange(request)
          data = self.safe_value(response, 'data')
          return self.parse_trades(data, market, since, limit)
  
      def parse_trade(self, trade, market=None):
          # {__type: 'transaction',
          #   data:
          #    {id: 'HSdruioHqvYHeyn9hhyoHdRWPB2bFA8ujeCPZMK6992c',
          #      timestamp: '2020-06-09T19:34:51.897Z',
          #      height: 2099684,
          #      type: 7,
          #      version: 2,
          #      proofs:
          #       ['26teDHERQgwjjHqEn4REcDotNG8M21xjou3X42XuDuCvrRkQo6aPyrswByH3UrkWG8v27ZAaVNzoxDg4teNcLtde'],
          #      fee: 0.003,
          #      sender: '3PEjHv3JGjcWNpYEEkif2w8NXV4kbhnoGgu',
          #      senderPublicKey: '9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5',
          #      buyMatcherFee: 0.00299999,
          #      sellMatcherFee: 0.00299999,
          #      price: 0.00012003,
          #      amount: 60.80421562,
          #      order1:
          #       {id: 'CBRwP3ar4oMvvpUiGyfxc1syh41488SDi2GkrjuBDegv',
          #         senderPublicKey: 'DBXSHBz96NFsMu7xh4fi2eT9ZnyxefAHXsMxUayzgC6a',
          #         matcherPublicKey: '9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5',
          #         assetPair: [Object],
          #         orderType: 'buy',
          #         price: 0.00012003,
          #         sender: '3PJfFRgVuJ47UY4ckb74EGzEBzkHXtmG1LA',
          #         amount: 60.80424773,
          #         timestamp: '2020-06-09T19:34:51.885Z',
          #         expiration: '2020-06-10T12:31:31.885Z',
          #         matcherFee: 0.003,
          #         signature: '4cA3ZAb3XAEEXaFG7caqpto5TRbpR5PkhZpxoNQZ9ZReNvjuJQs5a3THnumv7rcqmVUiVtuHAgk2f67ANcqtKyJ8',
          #         matcherFeeAssetId: null},
          #      order2:
          #       {id: 'CHJSLQ6dfSPs6gu2mAegrMUcRiDEDqaj2GKfvptMjS3M',
          #         senderPublicKey: '3RUC4NGFZm9H8VJhSSjJyFLdiE42qNiUagDcZPwjgDf8',
          #         matcherPublicKey: '9cpfKN9suPNvfeUNphzxXMjcnn974eme8ZhWUjaktzU5',
          #         assetPair: [Object],
          #         orderType: 'sell',
          #         price: 0.00012003,
          #         sender: '3P9vKoQpMZtaSkHKpNh977YY9ZPzTuntLAq',
          #         amount: 60.80424773,
          #         timestamp: '2020-06-09T19:34:51.887Z',
          #         expiration: '2020-06-10T12:31:31.887Z',
          #         matcherFee: 0.003,
          #         signature: '3SFyrcqzou2ddZyNisnLYaGhLt5qRjKxH8Nw3s4T5U7CEKGX9DDo8dS27RgThPVGbYF1rYET1FwrWoQ2UFZ6SMTR',
          #         matcherFeeAssetId: null}}}
          data = self.safe_value(trade, 'data')
          datetime = self.safe_string(data, 'timestamp')
          timestamp = self.parse8601(datetime)
          id = self.safe_string(data, 'id')
          priceString = self.safe_string(data, 'price')
          amountString = self.safe_string(data, 'amount')
          price = self.parse_number(priceString)
          amount = self.parse_number(amountString)
          cost = self.parse_number(Precise.string_mul(priceString, amountString))
          order1 = self.safe_value(data, 'order1')
          order2 = self.safe_value(data, 'order2')
          order = None
          # order2 arrived after order1
          if self.safe_string(order1, 'senderPublicKey') == self.apiKey:
              order = order1
          else:
              order = order2
          symbol = None
          assetPair = self.safe_value(order, 'assetPair')
          if assetPair is not None:
              symbol = self.get_symbol_from_asset_pair(assetPair)
          elif market is not None:
              symbol = market['symbol']
          side = self.safe_string(order, 'orderType')
          orderId = self.safe_string(order, 'id')
          fee = {
              'cost': self.safe_number(order, 'matcherFee'),
              'currency': self.safe_currency_code(self.safe_string(order, 'matcherFeeAssetId', 'WAVES')),
          }
          return {
              'info': trade,
              'timestamp': timestamp,
              'datetime': datetime,
              'symbol': symbol,
              'id': id,
              'order': orderId,
              'type': None,
              'side': side,
              'takerOrMaker': None,
              'price': price,
              'amount': amount,
              'cost': cost,
              'fee': fee,
          }
  
      def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
          errorCode = self.safe_string(response, 'error')
          success = self.safe_value(response, 'success', True)
          Exception = self.safe_value(self.exceptions, errorCode)
          if Exception is not None:
              message = self.safe_string(response, 'message')
              raise Exception(self.id + ' ' + message)
          message = self.safe_string(response, 'message')
          if message == 'Validation Error':
              raise BadRequest(self.id + ' ' + body)
          if not success:
              raise ExchangeError(self.id + ' ' + body)
  
      async def withdraw(self, code, amount, address, tag=None, params={}):
          # currently only works for BTC and WAVES
          if code != 'WAVES':
              supportedCurrencies = await self.privateGetWithdrawCurrencies()
              currencies = {}
              items = self.safe_value(supportedCurrencies, 'items', [])
              for i in range(0, len(items)):
                  entry = items[i]
                  currencyCode = self.safe_string(entry, 'id')
                  currencies[currencyCode] = True
              if not (code in currencies):
                  codes = list(currencies.keys())
                  raise ExchangeError(self.id + ' fetch ' + code + ' withdrawals are not supported. Currency code must be one of ' + str(codes))
          await self.load_markets()
          hexChars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
          set = {}
          for i in range(0, len(hexChars)):
              key = hexChars[i]
              set[key] = True
          isErc20 = True
          noPrefix = self.remove0x_prefix(address)
          lower = noPrefix.lower()
          for i in range(0, len(lower)):
              character = lower[i]
              if not (character in set):
                  isErc20 = False
                  break
          await self.get_access_token()
          proxyAddress = None
          if code == 'WAVES' and not isErc20:
              proxyAddress = address
          else:
              withdrawAddressRequest = {
                  'address': address,
                  'currency': code,
              }
              withdrawAddress = await self.privateGetWithdrawAddressesCurrencyAddress(withdrawAddressRequest)
              currency = self.safe_value(withdrawAddress, 'currency')
              allowedAmount = self.safe_value(currency, 'allowed_amount')
              minimum = self.safe_number(allowedAmount, 'min')
              if amount <= minimum:
                  raise BadRequest(self.id + ' ' + code + ' withdraw failed, amount ' + str(amount) + ' must be greater than the minimum allowed amount of ' + str(minimum))
              # {
              #   "type": "withdrawal_addresses",
              #   "currency": {
              #     "type": "withdrawal_currency",
              #     "id": "BTC",
              #     "waves_asset_id": "8LQW8f7P5d5PZM7GtZEBgaqRPGSzS3DfPuiXrURJ4AJS",
              #     "decimals": 8,
              #     "status": "active",
              #     "allowed_amount": {
              #       "min": 0.001,
              #       "max": 20
              #     },
              #     "fees": {
              #       "flat": 0.001,
              #       "rate": 0
              #     }
              #   },
              #   "proxy_addresses": [
              #     "3P3qqmkiLwNHB7x1FeoE8bvkRtULwGpo9ga"
              #   ]
              # }
              proxyAddresses = self.safe_value(withdrawAddress, 'proxy_addresses', [])
              proxyAddress = self.safe_string(proxyAddresses, 0)
          fee = self.safe_integer(self.options, 'withdrawFeeWAVES', 100000)  # 0.001 WAVES
          feeAssetId = 'WAVES'
          type = 4  # transfer
          version = 2
          amountInteger = self.currency_to_precision(code, amount)
          currency = self.currency(code)
          timestamp = self.milliseconds()
          byteArray = [
              self.number_to_be(4, 1),
              self.number_to_be(2, 1),
              self.base58_to_binary(self.apiKey),
              self.get_asset_bytes(currency['id']),
              self.get_asset_bytes(feeAssetId),
              self.number_to_be(timestamp, 8),
              self.number_to_be(amountInteger, 8),
              self.number_to_be(fee, 8),
              self.base58_to_binary(proxyAddress),
              self.number_to_be(0, 2),
          ]
          binary = self.binary_concat_array(byteArray)
          hexSecret = self.binary_to_base16(self.base58_to_binary(self.secret))
          signature = self.eddsa(self.binary_to_base16(binary), hexSecret, 'ed25519')
          request = {
              'senderPublicKey': self.apiKey,
              'amount': amountInteger,
              'fee': fee,
              'type': type,
              'version': version,
              'attachment': '',
              'feeAssetId': self.get_asset_id(feeAssetId),
              'proofs': [
                  signature,
              ],
              'assetId': self.get_asset_id(currency['id']),
              'recipient': proxyAddress,
              'timestamp': timestamp,
              'signature': signature,
          }
          return await self.nodePostTransactionsBroadcast(request)
  