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 hashlib
  import math
  from ccxt.base.errors import ExchangeError
  from ccxt.base.errors import AuthenticationError
  from ccxt.base.errors import ArgumentsRequired
  from ccxt.base.errors import BadRequest
  from ccxt.base.errors import InsufficientFunds
  from ccxt.base.errors import InvalidAddress
  from ccxt.base.errors import InvalidOrder
  from ccxt.base.errors import OrderNotFound
  from ccxt.base.errors import OrderNotFillable
  from ccxt.base.errors import NotSupported
  from ccxt.base.errors import ExchangeNotAvailable
  
  
  class theocean (Exchange):
  
      def describe(self):
          self.check_required_dependencies()
          return self.deep_extend(super(theocean, self).describe(), {
              'id': 'theocean',
              'name': 'The Ocean',
              'countries': ['US'],
              'rateLimit': 3000,
              'version': 'v1',
              'certified': True,
              'requiresWeb3': True,
              'timeframes': {
                  '5m': '300',
                  '15m': '900',
                  '1h': '3600',
                  '6h': '21600',
                  '1d': '86400',
              },
              'has': {
                  'cancelAllOrders': True,
                  'CORS': False,  # ?
                  'fetchClosedOrders': True,
                  'fetchOHLCV': False,
                  'fetchOpenOrders': True,
                  'fetchOrder': True,
                  'fetchOrders': True,
                  'fetchTickers': True,
              },
              'urls': {
                  'logo': 'https://user-images.githubusercontent.com/1294454/43103756-d56613ce-8ed7-11e8-924e-68f9d4bcacab.jpg',
                  'api': 'https://api.theocean.trade',
                  'www': 'https://theocean.trade',
                  'doc': 'https://docs.theocean.trade',
                  'fees': 'https://theocean.trade/fees',
              },
              'api': {
                  'public': {
                      'get': [
                          'fee_components',
                          'token_pairs',
                          'ticker',
                          'tickers',
                          'candlesticks',
                          'candlesticks/intervals',
                          'trade_history',
                          'order_book',
                          'order/{orderHash}',
                          'version',
                      ],
                  },
                  'private': {
                      'get': [
                          'balance',
                          'available_balance',
                          'order_history',
                          'order/unsigned',
                          'order/unsigned/market',
                      ],
                      'post': [
                          'order',
                      ],
                      'delete': [
                          'order/{orderHash}',
                          'order',
                      ],
                  },
              },
              'exceptions': {
                  'exact': {
                      'Order not found': OrderNotFound,  # {"message":"Order not found","errors":...}
                  },
                  'broad': {
                      "Price can't exceed 8 digits in precision.": InvalidOrder,  # {"message":"Price can't exceed 8 digits in precision.","type":"paramPrice"}
                      'Order cannot be canceled': InvalidOrder,  # {"message":"Order cannot be canceled","type":"General error"}
                      'Greater than available wallet balance.': InsufficientFunds,
                      'Fillable amount under minimum': InvalidOrder,  # {"message":"Fillable amount under minimum WETH trade size.","type":"paramQuoteTokenAmount"}
                      'Fillable amount over maximum': InvalidOrder,  # {"message":"Fillable amount over maximum TUSD trade size.","type":"paramQuoteTokenAmount"}
                      "Schema validation failed for 'params'": BadRequest,  # # {"message":"Schema validation failed for 'params'"}
                      'Service Temporarily Unavailable': ExchangeNotAvailable,
                  },
              },
              'options': {
                  'decimals': {},
                  'fetchOrderMethod': 'fetch_order_from_history',
              },
          })
  
      async def fetch_markets(self, params={}):
          markets = await self.publicGetTokenPairs()
          #
          #     [
          #       "baseToken": {
          #         "symbol": "ZRX",
          #         "address": "0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570",
          #         "name": "0x Protocol Token",
          #         "decimals": "18",
          #         "minAmount": "10000000000000000000",
          #         "maxAmount": "10000000000000000000000",
          #         "precision": "-8"
          #       },
          #       "quoteToken": {
          #         "symbol": "ETH",
          #         "address": "0xd0a1e359811322d97991e03f863a0c30c2cf029c",
          #         "name": "Ether Token",
          #         "decimals": "18",
          #         "minAmount": "20000000000000000",
          #         "maxAmount": "20000000000000000000",
          #         "precision": "-8"
          #       }
          #     ]
          #
          result = []
          for i in range(0, len(markets)):
              market = markets[i]
              baseToken = market['baseToken']
              quoteToken = market['quoteToken']
              baseId = baseToken['address']
              quoteId = quoteToken['address']
              base = baseToken['symbol']
              quote = quoteToken['symbol']
              base = self.common_currency_code(base)
              quote = self.common_currency_code(quote)
              symbol = base + '/' + quote
              id = baseId + '/' + quoteId
              baseDecimals = self.safe_integer(baseToken, 'decimals')
              quoteDecimals = self.safe_integer(quoteToken, 'decimals')
              self.options['decimals'][base] = baseDecimals
              self.options['decimals'][quote] = quoteDecimals
              precision = {
                  'amount': -int(baseToken['precision']),
                  'price': -int(quoteToken['precision']),
              }
              amountLimits = {
                  'min': self.fromWei(self.safe_string(baseToken, 'minAmount'), 'ether', baseDecimals),
                  'max': self.fromWei(self.safe_string(baseToken, 'maxAmount'), 'ether', baseDecimals),
              }
              priceLimits = {
                  'min': None,
                  'max': None,
              }
              costLimits = {
                  'min': self.fromWei(self.safe_string(quoteToken, 'minAmount'), 'ether', quoteDecimals),
                  'max': self.fromWei(self.safe_string(quoteToken, 'maxAmount'), 'ether', quoteDecimals),
              }
              limits = {
                  'amount': amountLimits,
                  'price': priceLimits,
                  'cost': costLimits,
              }
              active = True
              result.append({
                  'id': id,
                  'symbol': symbol,
                  'base': base,
                  'quote': quote,
                  'baseId': baseId,
                  'quoteId': quoteId,
                  'active': active,
                  'precision': precision,
                  'limits': limits,
                  'info': market,
              })
          return result
  
      def parse_ohlcv(self, ohlcv, market=None, timeframe='5m', since=None, limit=None):
          baseDecimals = self.safe_integer(self.options['decimals'], market['base'], 18)
          return [
              self.safe_integer(ohlcv, 'startTime') * 1000,
              self.safe_float(ohlcv, 'open'),
              self.safe_float(ohlcv, 'high'),
              self.safe_float(ohlcv, 'low'),
              self.safe_float(ohlcv, 'close'),
              self.fromWei(self.safe_string(ohlcv, 'baseVolume'), 'ether', baseDecimals),
          ]
  
      async def fetch_ohlcv(self, symbol, timeframe='5m', since=None, limit=None, params={}):
          await self.load_markets()
          market = self.market(symbol)
          request = {
              'baseTokenAddress': market['baseId'],
              'quoteTokenAddress': market['quoteId'],
              'interval': self.timeframes[timeframe],
          }
          if since is None:
              raise ExchangeError(self.id + ' fetchOHLCV requires a since argument')
          since = int(since)
          request['startTime'] = since
          response = await self.publicGetCandlesticks(self.extend(request, params))
          #
          #   [
          #     {
          #         "high": "100.52",
          #         "low": "97.23",
          #         "open": "98.45",
          #         "close": "99.23",
          #         "baseVolume": "2400000000000000000000",
          #         "quoteVolume": "1200000000000000000000",
          #         "startTime": "1512929323784"
          #     },
          #     {
          #         "high": "100.52",
          #         "low": "97.23",
          #         "open": "98.45",
          #         "close": "99.23",
          #         "volume": "2400000000000000000000",
          #         "startTime": "1512929198980"
          #     }
          #   ]
          #
          return self.parse_ohlcvs(response, market, timeframe, since, limit)
  
      async def fetch_balance_by_code(self, code, params={}):
          if not self.walletAddress or (self.walletAddress.find('0x') != 0):
              raise InvalidAddress(self.id + ' fetchBalanceByCode() requires the .walletAddress to be a "0x"-prefixed hexstring like "0xbF2d65B3b2907214EEA3562f21B80f6Ed7220377"')
          await self.load_markets()
          currency = self.currency(code)
          request = {
              'walletAddress': self.walletAddress.lower(),
              'tokenAddress': currency['id'],
          }
          response = await self.privateGetBalance(self.extend(request, params))
          #
          #     {"available":"0","committed":"0","total":"0"}
          #
          decimals = self.safe_integer(self.options['decimals'], code, 18)
          free = self.fromWei(self.safe_string(response, 'available'), 'ether', decimals)
          used = self.fromWei(self.safe_string(response, 'committed'), 'ether', decimals)
          total = self.fromWei(self.safe_string(response, 'total'), 'ether', decimals)
          return {
              'free': free,
              'used': used,
              'total': total,
          }
  
      async def fetch_balance(self, params={}):
          if not self.walletAddress or (self.walletAddress.find('0x') != 0):
              raise InvalidAddress(self.id + ' fetchBalance() requires the .walletAddress to be a "0x"-prefixed hexstring like "0xbF2d65B3b2907214EEA3562f21B80f6Ed7220377"')
          codes = self.safe_value(self.options, 'fetchBalanceCurrencies')
          if codes is None:
              codes = self.safe_value(params, 'codes')
          if (codes is None) or (not isinstance(codes, list)):
              raise ExchangeError(self.id + ' fetchBalance() requires a `codes` parameter(an array of currency codes)')
          await self.load_markets()
          result = {}
          for i in range(0, len(codes)):
              code = codes[i]
              result[code] = await self.fetch_balance_by_code(code)
          return self.parse_balance(result)
  
      def parse_bid_ask(self, bidask, priceKey=0, amountKey=1, market=None):
          if market is None:
              raise ArgumentsRequired(self.id + ' parseBidAsk requires a market argument')
          price = float(bidask[priceKey])
          amountDecimals = self.safe_integer(self.options['decimals'], market['base'], 18)
          amount = self.fromWei(bidask[amountKey], 'ether', amountDecimals)
          return [price, amount]
  
      def parse_order_book(self, orderbook, timestamp=None, bidsKey='bids', asksKey='asks', priceKey=0, amountKey=1, market=None):
          result = {
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'nonce': None,
          }
          sides = [bidsKey, asksKey]
          for i in range(0, len(sides)):
              side = sides[i]
              orders = []
              bidasks = self.safe_value(orderbook, side)
              for k in range(0, len(bidasks)):
                  orders.append(self.parse_bid_ask(bidasks[k], priceKey, amountKey, market))
              result[side] = orders
          result[bidsKey] = self.sort_by(result[bidsKey], 0, True)
          result[asksKey] = self.sort_by(result[asksKey], 0)
          return result
  
      async def fetch_order_book(self, symbol, limit=None, params={}):
          await self.load_markets()
          market = self.market(symbol)
          request = {
              'baseTokenAddress': market['baseId'],
              'quoteTokenAddress': market['quoteId'],
          }
          if limit is not None:
              request['depth'] = limit
          response = await self.publicGetOrderBook(self.extend(request, params))
          #
          #     {
          #       "bids": [
          #         {orderHash: '0xe2b7f80198edb561cc66cd85cb8e5f420073cf1e5143193d8add8774bd8236c4',
          #           price: '30',
          #           availableAmount: '500000000000000000',
          #           creationTimestamp: '1547193525',
          #           expirationTimestampInSec: '1549789124'
          #         }
          #       ],
          #       "asks": [
          #         {orderHash: '0xe2b7f80198edb561cc66cd85cb8e5f420073cf1e5143193d8add8774bd8236c4',
          #           price: '30',
          #           availableAmount: '500000000000000000',
          #           creationTimestamp: '1547193525',
          #           expirationTimestampInSec: '1549789124'
          #         }
          #       ]
          #     }
          #
          return self.parse_order_book(response, None, 'bids', 'asks', 'price', 'availableAmount', market)
  
      def parse_ticker(self, ticker, market=None):
          #
          #     {
          #         "bid": "0.00050915",
          #         "ask": "0.00054134",
          #         "last": "0.00052718",
          #         "volume": "3000000000000000000",
          #         "timestamp": "1512929327792"
          #     }
          #
          timestamp = int(self.safe_integer(ticker, 'timestamp') / 1000)
          symbol = None
          base = None
          if market is not None:
              symbol = market['symbol']
              base = market['base']
          baseDecimals = self.safe_integer(self.options['decimals'], base, 18)
          baseVolume = self.fromWei(self.safe_string(ticker, 'volume'), 'ether', baseDecimals)
          last = self.safe_float(ticker, 'last')
          return {
              'symbol': symbol,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'high': None,
              'low': None,
              'bid': self.safe_float(ticker, 'bid'),
              'bidVolume': None,
              'ask': self.safe_float(ticker, 'ask'),
              'askVolume': None,
              'vwap': None,
              'open': None,
              'close': last,
              'last': last,
              'previousClose': None,
              'change': None,
              'percentage': self.safe_float(ticker, 'priceChange'),
              'average': None,
              'baseVolume': baseVolume,
              'quoteVolume': None,
              'info': ticker,
          }
  
      async def fetch_tickers(self, symbols=None, params={}):
          await self.load_markets()
          tickers = await self.publicGetTickers(params)
          #
          #     [{
          #     "baseTokenAddress": "0xa8e9fa8f91e5ae138c74648c9c304f1c75003a8d",
          #     "quoteTokenAddress": "0xc00fd9820cd2898cc4c054b7bf142de637ad129a",
          #     "ticker": {
          #         "bid": "0.00050915",
          #         "ask": "0.00054134",
          #         "last": "0.00052718",
          #         "volume": "3000000000000000000",
          #         "timestamp": "1512929327792"
          #     }
          #     }]
          #
          result = {}
          for i in range(0, len(tickers)):
              ticker = tickers[i]
              baseId = self.safe_string(ticker, 'baseTokenAddress')
              quoteId = self.safe_string(ticker, 'quoteTokenAddress')
              marketId = baseId + '/' + quoteId
              market = None
              symbol = marketId
              if marketId in self.markets_by_id:
                  market = self.markets_by_id[marketId]
                  symbol = market['symbol']
              result[symbol] = self.parse_ticker(ticker['ticker'], market)
          return result
  
      async def fetch_ticker(self, symbol, params={}):
          await self.load_markets()
          market = self.market(symbol)
          request = {
              'baseTokenAddress': market['baseId'],
              'quoteTokenAddress': market['quoteId'],
          }
          response = await self.publicGetTicker(self.extend(request, params))
          return self.parse_ticker(response, market)
  
      def parse_trade(self, trade, market=None):
          #
          # fetchTrades
          #
          #     {
          #         "id": "37212",
          #         "transactionHash": "0x5e6e75e1aa681b51b034296f62ac19be7460411a2ad94042dd8ba637e13eac0c",
          #         "amount": "300000000000000000",
          #         "price": "0.00052718",
          # ------- they also have a "confirmed" status here ↓ -----------------
          #         "status": "filled",  # filled | settled | failed
          #         "lastUpdated": "1520265048996"
          #     }
          #
          # parseOrder trades(timeline "actions", "fills")
          #
          #     {     action: "confirmed",
          #            amount: "1000000000000000000",
          #          intentID: "MARKET_INTENT:90jjw2s7gj90jjw2s7gkjjw2s7gl",
          #            txHash: "0x043488fdc3f995bf9e632a32424e41ed126de90f8cb340a1ff006c2a74ca8336",
          #       blockNumber: "8094822",
          #         timestamp: "1532261686"                                                          }
          #
          timestamp = self.safe_integer(trade, 'lastUpdated')
          price = self.safe_float(trade, 'price')
          id = self.safe_string(trade, 'id')
          side = self.safe_string(trade, 'side')
          symbol = None
          base = None
          if market is not None:
              symbol = market['symbol']
              base = market['base']
          baseDecimals = self.safe_integer(self.options['decimals'], base, 18)
          amount = self.fromWei(self.safe_string(trade, 'amount'), 'ether', baseDecimals)
          cost = None
          if amount is not None and price is not None:
              cost = amount * price
          takerOrMaker = 'taker'
          return {
              'id': id,
              'order': id,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'symbol': symbol,
              'type': None,
              'side': side,
              'takerOrMaker': takerOrMaker,
              'price': price,
              'amount': amount,
              'cost': cost,
              'fee': None,
              'info': trade,
          }
  
      async def fetch_trades(self, symbol, since=None, limit=None, params={}):
          await self.load_markets()
          market = self.market(symbol)
          request = {
              'baseTokenAddress': market['baseId'],
              'quoteTokenAddress': market['quoteId'],
          }
          response = await self.publicGetTradeHistory(self.extend(request, params))
          #
          #     [
          #       {
          #         "id": "37212",
          #         "transactionHash": "0x5e6e75e1aa681b51b034296f62ac19be7460411a2ad94042dd8ba637e13eac0c",
          #         "amount": "300000000000000000",
          #         "price": "0.00052718",
          #         "status": "filled",  # filled | settled | failed
          #         "lastUpdated": "1520265048996"
          #       }
          #     ]
          #
          return self.parse_trades(response, market, since, limit)
  
      async def create_order(self, symbol, type, side, amount, price=None, params={}):
          errorMessage = self.id + ' createOrder() requires `exchange.walletAddress` and `exchange.privateKey`. The .walletAddress should be a "0x"-prefixed hexstring like "0xbF2d65B3b2907214EEA3562f21B80f6Ed7220377". The .privateKey for that wallet should be a "0x"-prefixed hexstring like "0xe4f40d465efa94c98aec1a51f574329344c772c1bce33be07fa20a56795fdd09".'
          if not self.walletAddress or (self.walletAddress.find('0x') != 0):
              raise InvalidAddress(errorMessage)
          if not self.privateKey or (self.privateKey.find('0x') != 0):
              raise InvalidAddress(errorMessage)
          orderParams = await self.fetch_order_params_to_sign(symbol, type, side, amount, price, params)
          unsignedOrder = orderParams['unsignedZeroExOrder']
          if unsignedOrder is None:
              raise OrderNotFillable(self.id + ' ' + type + ' order to ' + side + ' ' + symbol + ' is not fillable at the moment')
          signedOrder = await self.signZeroExOrderV2(unsignedOrder, self.privateKey)
          id = self.safe_string(signedOrder, 'orderHash')
          await self.post_signed_order(signedOrder, orderParams, params)
          order = await self.fetch_order(id)
          order['type'] = type
          return order
  
      async def fetch_order_params_to_sign(self, symbol, type, side, amount, price=None, params={}):
          if side != 'buy' and side != 'sell':
              raise ExchangeError(side + ' is not valid side param. Use \'buy\' or \'sell\'')
          if type != 'market' and type != 'limit':
              raise ExchangeError(type + ' is not valid type param. Use \'market\' or \'limit\'')
          if type == 'limit' and price is None:
              raise ExchangeError('Price is not provided for limit order')
          await self.load_markets()
          market = self.market(symbol)
          baseDecimals = self.safe_integer(self.options['decimals'], market['base'], 18)
          request = {
              'walletAddress': self.walletAddress.lower(),  # Your Wallet Address
              'baseTokenAddress': market['baseId'],  # Base token address
              'quoteTokenAddress': market['quoteId'],  # Quote token address
              'side': side,  # "buy" or "sell"
              'amount': self.toWei(self.amount_to_precision(symbol, amount), 'ether', baseDecimals),  # Base token amount in wei
          }
          method = None
          if type == 'limit':
              method = 'privateGetOrderUnsigned'
              request['price'] = self.price_to_precision(symbol, price)
          elif type == 'market':
              method = 'privateGetOrderUnsignedMarket'
          else:
              raise ExchangeError('Unsupported order type: ' + type)
          response = await getattr(self, method)(self.extend(request, params))
          return response
  
      async def post_signed_order(self, signedOrder, requestParams, params={}):
          request = requestParams
          request['signedZeroExOrder'] = signedOrder
          request = self.omit(request, 'unsignedZeroExOrder')
          response = await self.privatePostOrder(self.extend(request, params))
          return response
  
      async def cancel_order(self, id, symbol=None, params={}):
          await self.load_markets()
          request = {
              'orderHash': id,
          }
          response = await self.privateDeleteOrderOrderHash(self.extend(request, params))
          #
          #     {
          #       "canceledOrder": {
          #         "orderHash": "0x3d6b287c1dc79262d2391ae2ca9d050fdbbab2c8b3180e4a46f9f321a7f1d7a9",
          #         "amount": "100000000000"
          #       }
          #     }
          #
          market = None
          if symbol is not None:
              market = self.market(symbol)
          return self.extend(self.parse_order(response['canceledOrder'], market), {
              'status': 'canceled',
          })
  
      async def cancel_all_orders(self, symbol=None, params={}):
          response = await self.privateDeleteOrder(params)
          #
          #     [{
          #       "canceledOrder": {
          #         "orderHash": "0x3d6b287c1dc79262d2391ae2ca9d050fdbbab2c8b3180e4a46f9f321a7f1d7a9",
          #         "amount": "100000000000"
          #       }
          #     }]
          #
          return response
  
      def parse_order(self, order, market=None):
          zeroExOrder = self.safe_value(order, 'zeroExOrder')
          id = self.safe_string(order, 'orderHash')
          if (id is None) and(zeroExOrder is not None):
              id = self.safe_string(zeroExOrder, 'orderHash')
          side = self.safe_string(order, 'side')
          type = self.safe_string(order, 'type')  # injected from outside
          timestamp = self.safe_integer(order, 'creationTimestamp')
          if timestamp != 'None':
              timestamp = int(timestamp / 1000)
          symbol = None
          baseId = self.safe_string(order, 'baseTokenAddress')
          quoteId = self.safe_string(order, 'quoteTokenAddress')
          marketId = None
          if baseId is not None and quoteId is not None:
              marketId = baseId + '/' + quoteId
          market = self.safe_value(self.markets_by_id, marketId, market)
          base = None
          if market is not None:
              symbol = market['symbol']
              base = market['base']
          baseDecimals = self.safe_integer(self.options['decimals'], base, 18)
          price = self.safe_float(order, 'price')
          filledAmount = self.fromWei(self.safe_string(order, 'filledAmount'), 'ether', baseDecimals)
          settledAmount = self.fromWei(self.safe_string(order, 'settledAmount'), 'ether', baseDecimals)
          confirmedAmount = self.fromWei(self.safe_string(order, 'confirmedAmount'), 'ether', baseDecimals)
          failedAmount = self.fromWei(self.safe_string(order, 'failedAmount'), 'ether', baseDecimals)
          deadAmount = self.fromWei(self.safe_string(order, 'deadAmount'), 'ether', baseDecimals)
          prunedAmount = self.fromWei(self.safe_string(order, 'prunedAmount'), 'ether', baseDecimals)
          amount = self.fromWei(self.safe_string(order, 'initialAmount'), 'ether', baseDecimals)
          filled = self.sum(filledAmount, settledAmount, confirmedAmount)
          remaining = None
          lastTradeTimestamp = None
          timeline = self.safe_value(order, 'timeline')
          trades = None
          status = None
          if timeline is not None:
              numEvents = len(timeline)
              if numEvents > 0:
                  timelineEventsGroupedByAction = self.group_by(timeline, 'action')
                  if 'error' in timelineEventsGroupedByAction:
                      status = 'failed'
                  if 'filled' in timelineEventsGroupedByAction:
                      fillEvents = self.safe_value(timelineEventsGroupedByAction, 'filled')
                      numFillEvents = len(fillEvents)
                      lastTradeTimestamp = self.safe_integer(fillEvents[numFillEvents - 1], 'timestamp')
                      lastTradeTimestamp = lastTradeTimestamp if (lastTradeTimestamp is not None) else lastTradeTimestamp
                      trades = []
                      for i in range(0, numFillEvents):
                          trade = self.parse_trade(self.extend(fillEvents[i], {
                              'price': price,
                          }), market)
                          trades.append(self.extend(trade, {
                              'order': id,
                              'type': type,
                              'side': side,
                          }))
          cost = None
          if filled is not None:
              if remaining is None:
                  if amount is not None:
                      remaining = amount - filled
              if price is not None:
                  cost = filled * price
          fee = None
          feeCost = self.safe_string(order, 'feeAmount')
          if feeCost is not None:
              feeOption = self.safe_string(order, 'feeOption')
              feeCurrency = None
              if feeOption == 'feeInNative':
                  if market is not None:
                      feeCurrency = market['base']
              elif feeOption == 'feeInZRX':
                  feeCurrency = 'ZRX'
              else:
                  raise NotSupported(self.id + ' encountered an unsupported order fee option: ' + feeOption)
              feeDecimals = self.safe_integer(self.options['decimals'], feeCurrency, 18)
              fee = {
                  'cost': self.fromWei(feeCost, 'ether', feeDecimals),
                  'currency': feeCurrency,
              }
          amountPrecision = market['precision']['amount'] if market else 8
          if remaining is not None:
              if status is None:
                  status = 'open'
                  rest = remaining - failedAmount - deadAmount - prunedAmount
                  if rest < math.pow(10, -amountPrecision):
                      status = 'canceled' if (filled < amount) else 'closed'
          result = {
              'info': order,
              'id': id,
              'symbol': symbol,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'lastTradeTimestamp': lastTradeTimestamp,
              'type': type,
              'side': side,
              'price': price,
              'cost': cost,
              'amount': amount,
              'remaining': remaining,
              'filled': filled,
              'status': status,
              'fee': fee,
              'trades': trades,
          }
          return result
  
      async def fetch_open_order(self, id, symbol=None, params={}):
          method = self.options['fetchOrderMethod']
          return await getattr(self, method)(id, symbol, self.extend({
              'openAmount': 1,
          }, params))
  
      async def fetch_closed_order(self, id, symbol=None, params={}):
          method = self.options['fetchOrderMethod']
          return await getattr(self, method)(id, symbol, self.extend(params))
  
      async def fetch_order_from_history(self, id, symbol=None, params={}):
          request = {
              'orderHash': id,
          }
          orders = await self.fetch_orders(symbol, None, None, self.extend(request, params))
          ordersById = self.index_by(orders, 'id')
          if id in ordersById:
              return ordersById[id]
          raise OrderNotFound(self.id + ' could not find order ' + id + ' in order history')
  
      async def fetch_order_by_id(self, id, symbol=None, params={}):
          await self.load_markets()
          request = {
              'orderHash': id,
          }
          response = await self.publicGetOrderOrderHash(self.extend(request, params))
          #  {
          #   baseTokenAddress: '0xb18845c260f680d5b9d84649638813e342e4f8c9',
          #   quoteTokenAddress: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570',
          #   side: 'sell',
          #   price: '30',
          #   feeTokenAddress: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570',
          #   amount: '500000000000000000',
          #   created: '1547194003',
          #   expires: '1549786003',
          #   zeroExOrder: {
          #     salt: '71810414258284992779348693906799008280152689028521273772736250669496045815907',
          #     maker: '0xfa1a3371bcbfcf3deaa8a6f67784bfbe5b886d7f',
          #     taker: '0x77b18613579d49f252bd237ef113884eb37a7090',
          #     makerFee: '0',
          #     takerFee: '0',
          #     orderHash: '0x368540323af55868dd9ce6ac248e6a91d9b7595252ca061c4ada7612b09af1cf',
          #     feeRecipient: '0x88a64b5e882e5ad851bea5e7a3c8ba7c523fecbe',
          #     makerTokenAmount: '500000000000000000',
          #     takerTokenAmount: '14845250714350000000',
          #     makerTokenAddress: '0xb18845c260f680d5b9d84649638813e342e4f8c9',
          #     takerTokenAddress: '0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570',
          #     exchangeContractAddress: '0x35dd2932454449b14cee11a94d3674a936d5d7b2',
          #     expirationUnixTimestampSec: '1549789602'
          #   },
          #   feeAmount: '154749285650000000',
          #   feeOption: 'feeInNative',
          #   cancelAfter: '1549786003'
          #  }
          return self.parse_order(response)
  
      async def fetch_order(self, id, symbol=None, params={}):
          request = {
              'orderHash': id,
          }
          orders = await self.fetch_orders(symbol, None, None, self.extend(request, params))
          numOrders = len(orders)
          if numOrders != 1:
              raise OrderNotFound(self.id + ' order ' + id + ' not found')
          return orders[0]
  
      async def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
          await self.load_markets()
          request = {}
          market = None
          if symbol is not None:
              market = self.market(symbol)
              request['baseTokenAddress'] = market['baseId']
              request['quoteTokenAddress'] = market['quoteId']
          if limit is not None:
              request['limit'] = limit
          response = await self.privateGetOrderHistory(self.extend(request, params))
          #
          #     [
          #       {
          #         "orderHash": "0x94629386298dee69ae63cd3e414336ae153b3f02cffb9ffc53ad71e166615618",
          #         "baseTokenAddress": "0x323b5d4c32345ced77393b3530b1eed0f346429d",
          #         "quoteTokenAddress": "0xef7fff64389b814a946f3e92105513705ca6b990",
          #         "side": "buy",
          #         "openAmount": "10000000000000000000",
          #         "filledAmount": "0",
          #         "reservedAmount": "0",
          #         "settledAmount": "0",
          #         "confirmedAmount": "0",
          #         "deadAmount": "0",
          #         "price": "0.00050915",
          #         "timeline": [
          #           {
          #             "action": "placed",
          #             "amount": "10000000000000000000",
          #             "timestamp": "1512929327792"
          #           }
          #         ]
          #       }
          #     ]
          #
          return self.parse_orders(response, None, since, limit)
  
      async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
          return await self.fetch_orders(symbol, since, limit, self.extend({
              'openAmount': 1,  # returns open orders with remaining openAmount >= 1
          }, params))
  
      async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
          return await self.fetch_orders(symbol, since, limit, self.extend({
              'openAmount': 0,  # returns closed orders with remaining openAmount == 0
          }, params))
  
      def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
          url = self.urls['api'] + '/' + self.version + '/' + self.implode_params(path, params)
          query = self.omit(params, self.extract_params(path))
          if api == 'private':
              self.check_required_credentials()
              timestamp = str(self.seconds())
              prehash = self.apiKey + timestamp + method
              if method == 'POST':
                  body = self.json(query)
                  prehash += body
              else:
                  if query:
                      url += '?' + self.urlencode(query)
                  prehash += self.json({})
              signature = self.hmac(self.encode(prehash), self.encode(self.secret), hashlib.sha256, 'base64')
              headers = {
                  'TOX-ACCESS-KEY': self.apiKey,
                  'TOX-ACCESS-SIGN': signature,
                  'TOX-ACCESS-TIMESTAMP': timestamp,
                  'Content-Type': 'application/json',
              }
          elif api == 'public':
              if query:
                  url += '?' + self.urlencode(query)
          return {'url': url, 'method': method, 'body': body, 'headers': headers}
  
      def handle_errors(self, httpCode, reason, url, method, headers, body, response):
          if response is None:
              return  # fallback to default error handler
          # code 401 and plain body 'Authentication failed'(with single quotes)
          # self error is sent if you do not submit a proper Content-Type
          if body == "'Authentication failed'":
              raise AuthenticationError(self.id + ' ' + body)
          if (body[0] == '{') or (body[0] == '['):
              message = self.safe_string(response, 'message')
              if message is not None:
                  #
                  # {"message":"Schema validation failed for 'query'","errors":[{"name":"required","argument":"startTime","message":"requires property \"startTime\"","instance":{"baseTokenAddress":"0x6ff6c0ff1d68b964901f986d4c9fa3ac68346570","quoteTokenAddress":"0xd0a1e359811322d97991e03f863a0c30c2cf029c","interval":"300"},"property":"instance"}]}
                  # {"message":"Logic validation failed for 'query'","errors":[{"message":"startTime should be between 0 and current date","type":"startTime"}]}
                  # {"message":"Order not found","errors":[]}
                  # {"message":"Orderbook exhausted for intent MARKET_INTENT:8yjjzd8b0e8yjjzd8b0fjjzd8b0g"}
                  # {"message":"Intent validation failed.","errors":[{"message":"Greater than available wallet balance.","type":"walletBaseTokenAmount"}]}
                  # {"message":"Schema validation failed for 'body'","errors":[{"name":"anyOf","argument":["[subschema 0]","[subschema 1]","[subschema 2]"],"message":"is not any of [subschema 0],[subschema 1],[subschema 2]","instance":{"signedTargetOrder":{"error":{"message":"Unsigned target order validation failed.","errors":[{"message":"Greater than available wallet balance.","type":"walletBaseTokenAmount"}]},"maker":"0x1709c02cd7327d391a39a7671af8a91a1ef8a47b","orderHash":"0xda007ea8b5eca71ac96fe4072f7c1209bb151d898a9cc89bbeaa594f0491ee49","ecSignature":{"v":27,"r":"0xb23ce6c4a7b5d51d77e2d00f6d1d472a3b2e72d5b2be1510cfeb122f9366b79e","s":"0x07d274e6d7a00b65fc3026c2f9019215b1e47a5ac4d1f05e03f90550d27109be"}}},"property":"instance"}]}
                  # {"message":"Schema validation failed for 'params'","errors":[{"name":"pattern","argument":"^0x[0-9a-fA-F]{64}$","message":"does not match pattern \"^0x[0-9a-fA-F]{64}$\"","instance":"1","property":"instance.orderHash"}]}
                  #
                  feedback = self.id + ' ' + self.json(response)
                  exact = self.exceptions['exact']
                  if message in exact:
                      raise exact[message](feedback)
                  broad = self.exceptions['broad']
                  broadKey = self.findBroadlyMatchedKey(broad, body)
                  if broadKey is not None:
                      raise broad[broadKey](feedback)
                  raise ExchangeError(feedback)  # unknown message
  