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 BadRequest
  from ccxt.base.errors import InsufficientFunds
  from ccxt.base.errors import InvalidAddress
  from ccxt.base.errors import InvalidOrder
  from ccxt.base.errors import NotSupported
  from ccxt.base.errors import DDoSProtection
  from ccxt.base.errors import ExchangeNotAvailable
  from ccxt.base.decimal_to_precision import PAD_WITH_ZERO
  
  
  class idex(Exchange):
  
      def describe(self):
          return self.deep_extend(super(idex, self).describe(), {
              'id': 'idex',
              'name': 'IDEX',
              'countries': ['US'],
              'rateLimit': 1500,
              'version': 'v2',
              'certified': True,
              'pro': True,
              'requiresWeb3': True,
              'has': {
                  'cancelOrder': True,
                  'createOrder': True,
                  'fetchBalance': True,
                  'fetchMarkets': True,
                  'fetchCurrencies': True,
                  'fetchMyTrades': True,
                  'fetchOHLCV': True,
                  'fetchOpenOrders': True,
                  'fetchClosedOrders': True,
                  'fetchOrders': False,
                  'fetchOrder': True,
                  'fetchOrderBook': True,
                  'fetchTicker': True,
                  'fetchTickers': True,
                  'fetchTrades': True,
                  'fetchTransactions': False,
                  'fetchDeposits': True,
                  'fetchWithdrawals': True,
                  'withdraw': True,
              },
              'timeframes': {
                  '1m': '1m',
                  '5m': '5m',
                  '15m': '15m',
                  '30m': '30m',
                  '1h': '1h',
                  '6h': '6h',
                  '1d': '1d',
              },
              'urls': {
                  'test': {
                      'public': 'https://api-sandbox.idex.io',
                      'private': 'https://api-sandbox.idex.io',
                  },
                  'logo': 'https://user-images.githubusercontent.com/51840849/94481303-2f222100-01e0-11eb-97dd-bc14c5943a86.jpg',
                  'api': {
                      'public': 'https://api.idex.io',
                      'private': 'https://api.idex.io',
                  },
                  'www': 'https://idex.io',
                  'doc': [
                      'https://docs.idex.io/',
                  ],
              },
              'api': {
                  'public': {
                      'get': [
                          'ping',
                          'time',
                          'exchange',
                          'assets',
                          'markets',
                          'tickers',
                          'candles',
                          'trades',
                          'orderbook',
                          'wsToken',
                      ],
                  },
                  'private': {
                      'get': [
                          'user',
                          'wallets',
                          'balances',
                          'orders',
                          'fills',
                          'deposits',
                          'withdrawals',
                      ],
                      'post': [
                          'wallets',
                          'orders',
                          'orders/test',
                          'withdrawals',
                      ],
                      'delete': [
                          'orders',
                      ],
                  },
              },
              'options': {
                  'defaultTimeInForce': 'gtc',
                  'defaultSelfTradePrevention': 'cn',
              },
              'exceptions': {
                  'INVALID_ORDER_QUANTITY': InvalidOrder,
                  'INSUFFICIENT_FUNDS': InsufficientFunds,
                  'SERVICE_UNAVAILABLE': ExchangeNotAvailable,
                  'EXCEEDED_RATE_LIMIT': DDoSProtection,
                  'INVALID_PARAMETER': BadRequest,
                  'WALLET_NOT_ASSOCIATED': InvalidAddress,
                  'INVALID_WALLET_SIGNATURE': AuthenticationError,
              },
              'requiredCredentials': {
                  'walletAddress': True,
                  'privateKey': True,
                  'apiKey': True,
                  'secret': True,
              },
              'paddingMode': PAD_WITH_ZERO,
              'commonCurrencies': {},
              'requireCredentials': {
                  'privateKey': True,
                  'apiKey': True,
                  'secret': True,
              },
          })
  
      async def fetch_markets(self, params={}):
          # [
          #   {
          #     market: 'DIL-ETH',
          #     status: 'active',
          #     baseAsset: 'DIL',
          #     baseAssetPrecision: 8,
          #     quoteAsset: 'ETH',
          #     quoteAssetPrecision: 8
          #   }, ...
          # ]
          response = await self.publicGetMarkets(params)
          result = []
          for i in range(0, len(response)):
              entry = response[i]
              marketId = self.safe_string(entry, 'market')
              baseId = self.safe_string(entry, 'baseAsset')
              quoteId = self.safe_string(entry, 'quoteAsset')
              base = self.safe_currency_code(baseId)
              quote = self.safe_currency_code(quoteId)
              symbol = base + '/' + quote
              basePrecision = self.safe_integer(entry, 'baseAssetPrecision')
              quotePrecision = self.safe_integer(entry, 'quoteAssetPrecision')
              status = self.safe_string(entry, 'status')
              active = status == 'active'
              precision = {
                  'amount': basePrecision,
                  'price': quotePrecision,
              }
              result.append({
                  'symbol': symbol,
                  'id': marketId,
                  'base': base,
                  'quote': quote,
                  'baseId': baseId,
                  'quoteId': quoteId,
                  'active': active,
                  'info': entry,
                  'precision': precision,
                  'limits': {
                      'amount': {
                          'min': math.pow(10, -precision['amount']),
                          'max': None,
                      },
                      'price': {
                          'min': None,
                          'max': None,
                      },
                      'cost': {
                          'min': None,
                          'max': None,
                      },
                  },
              })
          return result
  
      async def fetch_ticker(self, symbol, params={}):
          await self.load_markets()
          market = self.market(symbol)
          request = {
              'market': market['id'],
          }
          # [
          #   {
          #     market: 'DIL-ETH',
          #     time: 1598367493008,
          #     open: '0.09695361',
          #     high: '0.10245881',
          #     low: '0.09572507',
          #     close: '0.09917079',
          #     closeQuantity: '0.71320950',
          #     baseVolume: '309.17380612',
          #     quoteVolume: '30.57633981',
          #     percentChange: '2.28',
          #     numTrades: 205,
          #     ask: '0.09910476',
          #     bid: '0.09688340',
          #     sequence: 3902
          #   }
          # ]
          response = await self.publicGetTickers(self.extend(request, params))
          ticker = self.safe_value(response, 0)
          return self.parse_ticker(ticker, market)
  
      async def fetch_tickers(self, symbols=None, params={}):
          await self.load_markets()
          # [
          #   {
          #     market: 'DIL-ETH',
          #     time: 1598367493008,
          #     open: '0.09695361',
          #     high: '0.10245881',
          #     low: '0.09572507',
          #     close: '0.09917079',
          #     closeQuantity: '0.71320950',
          #     baseVolume: '309.17380612',
          #     quoteVolume: '30.57633981',
          #     percentChange: '2.28',
          #     numTrades: 205,
          #     ask: '0.09910476',
          #     bid: '0.09688340',
          #     sequence: 3902
          #   }, ...
          # ]
          response = await self.publicGetTickers(params)
          return self.parse_tickers(response, symbols)
  
      def parse_tickers(self, rawTickers, symbols=None):
          tickers = []
          for i in range(0, len(rawTickers)):
              tickers.append(self.parse_ticker(rawTickers[i]))
          return self.filter_by_array(tickers, 'symbol', symbols)
  
      def parse_ticker(self, ticker, market=None):
          # {
          #   market: 'DIL-ETH',
          #   time: 1598367493008,
          #   open: '0.09695361',
          #   high: '0.10245881',
          #   low: '0.09572507',
          #   close: '0.09917079',
          #   closeQuantity: '0.71320950',
          #   baseVolume: '309.17380612',
          #   quoteVolume: '30.57633981',
          #   percentChange: '2.28',
          #   numTrades: 205,
          #   ask: '0.09910476',
          #   bid: '0.09688340',
          #   sequence: 3902
          # }
          marketId = self.safe_string(ticker, 'market')
          symbol = self.safe_symbol(marketId, market, '-')
          baseVolume = self.safe_float(ticker, 'baseVolume')
          quoteVolume = self.safe_float(ticker, 'quoteVolume')
          timestamp = self.safe_integer(ticker, 'time')
          open = self.safe_float(ticker, 'open')
          high = self.safe_float(ticker, 'high')
          low = self.safe_float(ticker, 'low')
          close = self.safe_float(ticker, 'close')
          ask = self.safe_float(ticker, 'ask')
          bid = self.safe_float(ticker, 'bid')
          percentage = self.safe_float(ticker, 'percentChange')
          if percentage is not None:
              percentage = 1 + percentage / 100
          change = None
          if (close is not None) and (open is not None):
              change = close - open
          return {
              'symbol': symbol,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'high': high,
              'low': low,
              'bid': bid,
              'bidVolume': None,
              'ask': ask,
              'askVolume': None,
              'vwap': None,
              'open': open,
              'close': close,
              'last': close,
              'previousClose': None,
              'change': change,
              'percentage': percentage,
              'average': None,
              'baseVolume': baseVolume,
              'quoteVolume': quoteVolume,
              'info': ticker,
          }
  
      async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
          await self.load_markets()
          market = self.market(symbol)
          request = {
              'market': market['id'],
              'interval': timeframe,
          }
          if since is not None:
              request['start'] = since
          if limit is not None:
              request['limit'] = limit
          response = await self.publicGetCandles(self.extend(request, params))
          if isinstance(response, list):
              # [
              #   {
              #     start: 1598345580000,
              #     open: '0.09771286',
              #     high: '0.09771286',
              #     low: '0.09771286',
              #     close: '0.09771286',
              #     volume: '1.45340410',
              #     sequence: 3853
              #   }, ...
              # ]
              return self.parse_ohlcvs(response, market, timeframe, since, limit)
          else:
              #  {"nextTime":1595536440000}
              return []
  
      def parse_ohlcv(self, ohlcv, market=None):
          # {
          #   start: 1598345580000,
          #   open: '0.09771286',
          #   high: '0.09771286',
          #   low: '0.09771286',
          #   close: '0.09771286',
          #   volume: '1.45340410',
          #   sequence: 3853
          # }
          timestamp = self.safe_integer(ohlcv, 'start')
          open = self.safe_float(ohlcv, 'open')
          high = self.safe_float(ohlcv, 'high')
          low = self.safe_float(ohlcv, 'low')
          close = self.safe_float(ohlcv, 'close')
          volume = self.safe_float(ohlcv, 'volume')
          return [timestamp, open, high, low, close, volume]
  
      async def fetch_trades(self, symbol, since=None, limit=None, params={}):
          await self.load_markets()
          market = self.market(symbol)
          request = {
              'market': market['id'],
          }
          if since is not None:
              request['start'] = since
          if limit is not None:
              request['limit'] = limit
          # [
          #   {
          #     fillId: 'b5467d00-b13e-3fa9-8216-dd66735550fc',
          #     price: '0.09771286',
          #     quantity: '1.45340410',
          #     quoteQuantity: '0.14201627',
          #     time: 1598345638994,
          #     makerSide: 'buy',
          #     sequence: 3853
          #   }, ...
          # ]
          response = await self.publicGetTrades(self.extend(request, params))
          return self.parse_trades(response, market, since, limit)
  
      def parse_trade(self, trade, market=None):
          # public trades
          # {
          #   fillId: 'b5467d00-b13e-3fa9-8216-dd66735550fc',
          #   price: '0.09771286',
          #   quantity: '1.45340410',
          #   quoteQuantity: '0.14201627',
          #   time: 1598345638994,
          #   makerSide: 'buy',
          #   sequence: 3853
          # }
          # private trades
          # {
          #   fillId: '48582d10-b9bb-3c4b-94d3-e67537cf2472',
          #   price: '0.09905990',
          #   quantity: '0.40000000',
          #   quoteQuantity: '0.03962396',
          #   time: 1598873478762,
          #   makerSide: 'sell',
          #   sequence: 5053,
          #   market: 'DIL-ETH',
          #   orderId: '7cdc8e90-eb7d-11ea-9e60-4118569f6e63',
          #   side: 'buy',
          #   fee: '0.00080000',
          #   feeAsset: 'DIL',
          #   gas: '0.00857497',
          #   liquidity: 'taker',
          #   txId: '0xeaa02b112c0b8b61bc02fa1776a2b39d6c614e287c1af90df0a2e591da573e65',
          #   txStatus: 'mined'
          # }
          id = self.safe_string(trade, 'fillId')
          price = self.safe_float(trade, 'price')
          amount = self.safe_float(trade, 'quantity')
          cost = self.safe_float(trade, 'quoteQuantity')
          timestamp = self.safe_integer(trade, 'time')
          marketId = self.safe_string(trade, 'market')
          symbol = self.safe_symbol(marketId, market, '-')
          # self code handles the duality of public vs private trades
          makerSide = self.safe_string(trade, 'makerSide')
          oppositeSide = 'sell' if (makerSide == 'buy') else 'buy'
          side = self.safe_string(trade, 'side', oppositeSide)
          takerOrMaker = self.safe_string(trade, 'liquidity', 'taker')
          feeCost = self.safe_float(trade, 'fee')
          fee = None
          if feeCost is not None:
              feeCurrencyId = self.safe_string(trade, 'feeAsset')
              fee = {
                  'cost': feeCost,
                  'currency': self.safe_currency_code(feeCurrencyId),
              }
          orderId = self.safe_string(trade, 'orderId')
          return {
              'info': trade,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'symbol': symbol,
              'id': id,
              'order': orderId,
              'type': 'limit',
              'side': side,
              'takerOrMaker': takerOrMaker,
              'price': price,
              'amount': amount,
              'cost': cost,
              'fee': fee,
          }
  
      async def fetch_order_book(self, symbol, limit=None, params={}):
          await self.load_markets()
          market = self.market(symbol)
          request = {
              'market': market['id'],
              'level': 2,
          }
          if limit is not None:
              request['limit'] = limit
          # {
          #   sequence: 36416753,
          #   bids: [
          #     ['0.09672815', '8.22284267', 1],
          #     ['0.09672814', '1.83685554', 1],
          #     ['0.09672143', '4.10962617', 1],
          #     ['0.09658884', '4.03863759', 1],
          #     ['0.09653781', '3.35730684', 1],
          #     ['0.09624660', '2.54163586', 1],
          #     ['0.09617490', '1.93065030', 1]
          #   ],
          #   asks: [
          #     ['0.09910476', '3.22840154', 1],
          #     ['0.09940587', '3.39796593', 1],
          #     ['0.09948189', '4.25088898', 1],
          #     ['0.09958362', '2.42195784', 1],
          #     ['0.09974393', '4.25234367', 1],
          #     ['0.09995250', '3.40192141', 1]
          #   ]
          # }
          response = await self.publicGetOrderbook(self.extend(request, params))
          nonce = self.safe_integer(response, 'sequence')
          return {
              'timestamp': None,
              'datetime': None,
              'nonce': nonce,
              'bids': self.parse_side(response, 'bids'),
              'asks': self.parse_side(response, 'asks'),
          }
  
      def parse_side(self, book, side):
          bookSide = self.safe_value(book, side, [])
          result = []
          for i in range(0, len(bookSide)):
              order = bookSide[i]
              price = self.safe_float(order, 0)
              amount = self.safe_float(order, 1)
              orderCount = self.safe_integer(order, 2)
              result.append([price, amount, orderCount])
          descending = side == 'bids'
          return self.sort_by(result, 0, descending)
  
      async def fetch_currencies(self, params={}):
          # [
          #   {
          #     name: 'Ether',
          #     symbol: 'ETH',
          #     contractAddress: '0x0000000000000000000000000000000000000000',
          #     assetDecimals: 18,
          #     exchangeDecimals: 8
          #   }, ..
          # ]
          response = await self.publicGetAssets(params)
          result = {}
          for i in range(0, len(response)):
              entry = response[i]
              name = self.safe_string(entry, 'name')
              currencyId = self.safe_string(entry, 'symbol')
              precision = self.safe_integer(entry, 'exchangeDecimals')
              code = self.safe_currency_code(currencyId)
              lot = math.pow(-10, precision)
              result[code] = {
                  'id': currencyId,
                  'code': code,
                  'info': entry,
                  'type': None,
                  'name': name,
                  'active': None,
                  'fee': None,
                  'precision': precision,
                  'limits': {
                      'amount': {'min': lot, 'max': None},
                      'price': {'min': lot, 'max': None},
                      'cost': {'min': None, 'max': None},
                      'withdraw': {'min': lot, 'max': None},
                  },
              }
          return result
  
      async def fetch_balance(self, params={}):
          self.check_required_credentials()
          await self.load_markets()
          nonce1 = self.uuidv1()
          request = {
              'nonce': nonce1,
              'wallet': self.walletAddress,
          }
          # [
          #   {
          #     asset: 'DIL',
          #     quantity: '0.00000000',
          #     availableForTrade: '0.00000000',
          #     locked: '0.00000000',
          #     usdValue: null
          #   }, ...
          # ]
          extendedRequest = self.extend(request, params)
          if extendedRequest['wallet'] is None:
              raise BadRequest(self.id + ' wallet is None, set self.walletAddress or "address" in params')
          response = None
          try:
              response = await self.privateGetBalances(extendedRequest)
          except Exception as e:
              if isinstance(e, InvalidAddress):
                  walletAddress = extendedRequest['wallet']
                  await self.associate_wallet(walletAddress)
                  response = await self.privateGetBalances(extendedRequest)
              else:
                  raise e
          result = {
              'info': response,
          }
          for i in range(0, len(response)):
              entry = response[i]
              currencyId = self.safe_string(entry, 'asset')
              code = self.safe_currency_code(currencyId)
              total = self.safe_float(entry, 'quantity')
              free = self.safe_float(entry, 'availableForTrade')
              used = self.safe_float(entry, 'locked')
              result[code] = {
                  'free': free,
                  'used': used,
                  'total': total,
              }
          return self.parse_balance(result)
  
      async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
          self.check_required_credentials()
          await self.load_markets()
          market = None
          request = {
              'nonce': self.uuidv1(),
              'wallet': self.walletAddress,
          }
          if symbol is not None:
              market = self.market(symbol)
              request['market'] = market['id']
          if since is not None:
              request['start'] = since
          if limit is not None:
              request['limit'] = limit
          # [
          #   {
          #     fillId: '48582d10-b9bb-3c4b-94d3-e67537cf2472',
          #     price: '0.09905990',
          #     quantity: '0.40000000',
          #     quoteQuantity: '0.03962396',
          #     time: 1598873478762,
          #     makerSide: 'sell',
          #     sequence: 5053,
          #     market: 'DIL-ETH',
          #     orderId: '7cdc8e90-eb7d-11ea-9e60-4118569f6e63',
          #     side: 'buy',
          #     fee: '0.00080000',
          #     feeAsset: 'DIL',
          #     gas: '0.00857497',
          #     liquidity: 'taker',
          #     txId: '0xeaa02b112c0b8b61bc02fa1776a2b39d6c614e287c1af90df0a2e591da573e65',
          #     txStatus: 'mined'
          #   }
          # ]
          extendedRequest = self.extend(request, params)
          if extendedRequest['wallet'] is None:
              raise BadRequest(self.id + ' walletAddress is None, set self.walletAddress or "address" in params')
          response = None
          try:
              response = await self.privateGetFills(extendedRequest)
          except Exception as e:
              if isinstance(e, InvalidAddress):
                  walletAddress = extendedRequest['wallet']
                  await self.associate_wallet(walletAddress)
                  response = await self.privateGetFills(extendedRequest)
              else:
                  raise e
          return self.parse_trades(response, market, since, limit)
  
      async def fetch_order(self, id, symbol=None, params={}):
          request = {
              'orderId': id,
          }
          return await self.fetch_orders_helper(symbol, None, None, self.extend(request, params))
  
      async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
          request = {
              'closed': False,
          }
          return await self.fetch_orders_helper(symbol, since, limit, self.extend(request, params))
  
      async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
          request = {
              'closed': True,
          }
          return await self.fetch_orders_helper(symbol, since, limit, self.extend(request, params))
  
      async def fetch_orders_helper(self, symbol=None, since=None, limit=None, params={}):
          await self.load_markets()
          request = {
              'nonce': self.uuidv1(),
              'wallet': self.walletAddress,
          }
          market = None
          if symbol is not None:
              market = self.market(symbol)
              request['market'] = market['id']
          if since is not None:
              request['start'] = since
          if limit is not None:
              request['limit'] = limit
          response = await self.privateGetOrders(self.extend(request, params))
          # fetchClosedOrders / fetchOpenOrders
          # [
          #   {
          #     "market": "DIL-ETH",
          #     "orderId": "7cdc8e90-eb7d-11ea-9e60-4118569f6e63",
          #     "wallet": "0x0AB991497116f7F5532a4c2f4f7B1784488628e1",
          #     "time": 1598873478650,
          #     "status": "filled",
          #     "type": "limit",
          #     "side": "buy",
          #     "originalQuantity": "0.40000000",
          #     "executedQuantity": "0.40000000",
          #     "cumulativeQuoteQuantity": "0.03962396",
          #     "avgExecutionPrice": "0.09905990",
          #     "price": "1.00000000",
          #     "fills": [
          #       {
          #         "fillId": "48582d10-b9bb-3c4b-94d3-e67537cf2472",
          #         "price": "0.09905990",
          #         "quantity": "0.40000000",
          #         "quoteQuantity": "0.03962396",
          #         "time": 1598873478650,
          #         "makerSide": "sell",
          #         "sequence": 5053,
          #         "fee": "0.00080000",
          #         "feeAsset": "DIL",
          #         "gas": "0.00857497",
          #         "liquidity": "taker",
          #         "txId": "0xeaa02b112c0b8b61bc02fa1776a2b39d6c614e287c1af90df0a2e591da573e65",
          #         "txStatus": "mined"
          #       }
          #     ]
          #   }
          # ]
          # fetchOrder
          # {market: 'DIL-ETH',
          #   orderId: '7cdc8e90-eb7d-11ea-9e60-4118569f6e63',
          #   wallet: '0x0AB991497116f7F5532a4c2f4f7B1784488628e1',
          #   time: 1598873478650,
          #   status: 'filled',
          #   type: 'limit',
          #   side: 'buy',
          #   originalQuantity: '0.40000000',
          #   executedQuantity: '0.40000000',
          #   cumulativeQuoteQuantity: '0.03962396',
          #   avgExecutionPrice: '0.09905990',
          #   price: '1.00000000',
          #   fills:
          #    [{fillId: '48582d10-b9bb-3c4b-94d3-e67537cf2472',
          #        price: '0.09905990',
          #        quantity: '0.40000000',
          #        quoteQuantity: '0.03962396',
          #        time: 1598873478650,
          #        makerSide: 'sell',
          #        sequence: 5053,
          #        fee: '0.00080000',
          #        feeAsset: 'DIL',
          #        gas: '0.00857497',
          #        liquidity: 'taker',
          #        txId: '0xeaa02b112c0b8b61bc02fa1776a2b39d6c614e287c1af90df0a2e591da573e65',
          #        txStatus: 'mined'}]}
          if isinstance(response, list):
              return self.parse_orders(response, market, since, limit)
          else:
              return self.parse_order(response, market)
  
      def parse_order_status(self, status):
          # https://docs.idex.io/#order-states-amp-lifecycle
          statuses = {
              'active': 'open',
              'partiallyFilled': 'open',
              'rejected': 'canceled',
              'filled': 'closed',
          }
          return self.safe_string(statuses, status, status)
  
      def parse_order(self, order, market=None):
          # {
          #   "market": "DIL-ETH",
          #   "orderId": "7cdc8e90-eb7d-11ea-9e60-4118569f6e63",
          #   "wallet": "0x0AB991497116f7F5532a4c2f4f7B1784488628e1",
          #   "time": 1598873478650,
          #   "status": "filled",
          #   "type": "limit",
          #   "side": "buy",
          #   "originalQuantity": "0.40000000",
          #   "executedQuantity": "0.40000000",
          #   "cumulativeQuoteQuantity": "0.03962396",
          #   "avgExecutionPrice": "0.09905990",
          #   "price": "1.00000000",
          #   "fills": [
          #     {
          #       "fillId": "48582d10-b9bb-3c4b-94d3-e67537cf2472",
          #       "price": "0.09905990",
          #       "quantity": "0.40000000",
          #       "quoteQuantity": "0.03962396",
          #       "time": 1598873478650,
          #       "makerSide": "sell",
          #       "sequence": 5053,
          #       "fee": "0.00080000",
          #       "feeAsset": "DIL",
          #       "gas": "0.00857497",
          #       "liquidity": "taker",
          #       "txId": "0xeaa02b112c0b8b61bc02fa1776a2b39d6c614e287c1af90df0a2e591da573e65",
          #       "txStatus": "mined"
          #     }
          #   ]
          # }
          timestamp = self.safe_integer(order, 'time')
          fills = self.safe_value(order, 'fills', [])
          id = self.safe_string(order, 'orderId')
          clientOrderId = self.safe_string(order, 'clientOrderId')
          marketId = self.safe_string(order, 'market')
          side = self.safe_string(order, 'side')
          symbol = self.safe_symbol(marketId, market, '-')
          trades = self.parse_trades(fills, market)
          type = self.safe_string(order, 'type')
          amount = self.safe_float(order, 'originalQuantity')
          filled = self.safe_float(order, 'executedQuantity')
          remaining = None
          if (amount is not None) and (filled is not None):
              remaining = amount - filled
          average = self.safe_float(order, 'avgExecutionPrice')
          price = self.safe_float(order, 'price', average)  # for market orders
          cost = None
          if (amount is not None) and (price is not None):
              cost = amount * price
          rawStatus = self.safe_string(order, 'status')
          status = self.parse_order_status(rawStatus)
          fee = {
              'currency': None,
              'cost': None,
          }
          lastTrade = None
          for i in range(0, len(trades)):
              lastTrade = trades[i]
              fee['currency'] = lastTrade['fee']['currency']
              fee['cost'] = self.sum(fee['cost'], lastTrade['fee']['cost'])
          lastTradeTimestamp = self.safe_integer(lastTrade, 'timestamp')
          return {
              'info': order,
              'id': id,
              'clientOrderId': clientOrderId,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'lastTradeTimestamp': lastTradeTimestamp,
              'symbol': symbol,
              'type': type,
              'side': side,
              'price': price,
              'amount': amount,
              'cost': cost,
              'average': average,
              'filled': filled,
              'remaining': remaining,
              'status': status,
              'fee': fee,
              'trades': trades,
          }
  
      async def associate_wallet(self, walletAddress, params={}):
          nonce = self.uuidv1()
          noPrefix = self.remove0x_prefix(walletAddress)
          byteArray = [
              self.base16_to_binary(nonce),
              self.base16_to_binary(noPrefix),
          ]
          binary = self.binary_concat_array(byteArray)
          hash = self.hash(binary, 'keccak', 'hex')
          signature = self.sign_message_string(hash, self.privateKey)
          # {
          #   address: '0x0AB991497116f7F5532a4c2f4f7B1784488628e1',
          #   totalPortfolioValueUsd: '0.00',
          #   time: 1598468353626
          # }
          request = {
              'parameters': {
                  'nonce': nonce,
                  'wallet': walletAddress,
              },
              'signature': signature,
          }
          result = await self.privatePostWallets(request)
          return result
  
      async def create_order(self, symbol, type, side, amount, price=None, params={}):
          # https://docs.idex.io/#create-order
          self.check_required_credentials()
          await self.load_markets()
          market = self.market(symbol)
          nonce = self.uuidv1()
          typeEnum = None
          priceString = None
          if type == 'limit':
              typeEnum = 1
              priceString = self.price_to_precision(symbol, price)
          elif type == 'market':
              typeEnum = 0
          amountEnum = 0  # base quantity
          if 'quoteOrderQuantity' in params:
              if type != 'market':
                  raise NotSupported(self.id + ' quoteOrderQuantity is not supported for ' + type + ' orders, only supported for market orders')
              amountEnum = 1
              amount = self.safe_float(params, 'quoteOrderQuantity')
          sideEnum = 0 if (side == 'buy') else 1
          walletBytes = self.remove0x_prefix(self.walletAddress)
          orderVersion = 1
          amountString = self.amount_to_precision(symbol, amount)
          # https://docs.idex.io/#time-in-force
          timeInForceEnums = {
              'gtc': 0,
              'ioc': 2,
              'fok': 3,
          }
          defaultTimeInForce = self.safe_string(self.options, 'defaultTimeInForce', 'gtc')
          timeInForce = self.safe_string(params, 'timeInForce', defaultTimeInForce)
          timeInForceEnum = None
          if timeInForce in timeInForceEnums:
              timeInForceEnum = timeInForceEnums[timeInForce]
          else:
              allOptions = list(timeInForceEnums.keys())
              asString = ', '.join(allOptions)
              raise BadRequest(self.id + ' ' + timeInForce + ' is not a valid timeInForce, please choose one of ' + asString)
          # https://docs.idex.io/#self-trade-prevention
          selfTradePreventionEnums = {
              'dc': 0,
              'co': 1,
              'cn': 2,
              'cb': 3,
          }
          defaultSelfTradePrevention = self.safe_string(self.options, 'defaultSelfTradePrevention', 'cn')
          selfTradePrevention = self.safe_string(params, 'selfTradePrevention', defaultSelfTradePrevention)
          selfTradePreventionEnum = None
          if selfTradePrevention in selfTradePreventionEnums:
              selfTradePreventionEnum = selfTradePreventionEnums[selfTradePrevention]
          else:
              allOptions = list(selfTradePreventionEnums.keys())
              asString = ', '.join(allOptions)
              raise BadRequest(self.id + ' ' + selfTradePrevention + ' is not a valid selfTradePrevention, please choose one of ' + asString)
          byteArray = [
              self.number_to_be(orderVersion, 1),
              self.base16_to_binary(nonce),
              self.base16_to_binary(walletBytes),
              self.encode(market['id']),  # TODO: refactor to remove either encode or stringToBinary
              self.number_to_be(typeEnum, 1),
              self.number_to_be(sideEnum, 1),
              self.encode(amountString),
              self.number_to_be(amountEnum, 1),
          ]
          if type == 'limit':
              encodedPrice = self.encode(priceString)
              byteArray.append(encodedPrice)
          clientOrderId = self.safe_string(params, 'clientOrderId')
          if clientOrderId is not None:
              byteArray.append(self.encode(clientOrderId))
          after = [
              self.number_to_be(timeInForceEnum, 1),
              self.number_to_be(selfTradePreventionEnum, 1),
              self.number_to_be(0, 8),  # unused
          ]
          allBytes = self.array_concat(byteArray, after)
          binary = self.binary_concat_array(allBytes)
          hash = self.hash(binary, 'keccak', 'hex')
          signature = self.sign_message_string(hash, self.privateKey)
          request = {
              'parameters': {
                  'nonce': nonce,
                  'market': market['id'],
                  'side': side,
                  'type': type,
                  'wallet': self.walletAddress,
                  'timeInForce': timeInForce,
                  'selfTradePrevention': selfTradePrevention,
              },
              'signature': signature,
          }
          if type == 'limit':
              request['parameters']['price'] = priceString
          if amountEnum == 0:
              request['parameters']['quantity'] = amountString
          else:
              request['parameters']['quoteOrderQuantity'] = amountString
          if clientOrderId is not None:
              request['parameters']['clientOrderId'] = clientOrderId
          # {
          #   market: 'DIL-ETH',
          #   orderId: '7cdc8e90-eb7d-11ea-9e60-4118569f6e63',
          #   wallet: '0x0AB991497116f7F5532a4c2f4f7B1784488628e1',
          #   time: 1598873478650,
          #   status: 'filled',
          #   type: 'limit',
          #   side: 'buy',
          #   originalQuantity: '0.40000000',
          #   executedQuantity: '0.40000000',
          #   cumulativeQuoteQuantity: '0.03962396',
          #   price: '1.00000000',
          #   fills: [
          #     {
          #       fillId: '48582d10-b9bb-3c4b-94d3-e67537cf2472',
          #       price: '0.09905990',
          #       quantity: '0.40000000',
          #       quoteQuantity: '0.03962396',
          #       time: 1598873478650,
          #       makerSide: 'sell',
          #       sequence: 5053,
          #       fee: '0.00080000',
          #       feeAsset: 'DIL',
          #       gas: '0.00857497',
          #       liquidity: 'taker',
          #       txStatus: 'pending'
          #     }
          #   ],
          #   avgExecutionPrice: '0.09905990'
          # }
          # we don't use self.extend here because it is a signed endpoint
          response = await self.privatePostOrders(request)
          return self.parse_order(response, market)
  
      async def withdraw(self, code, amount, address, tag=None, params={}):
          self.check_required_credentials()
          await self.load_markets()
          nonce = self.uuidv1()
          amountString = self.currency_to_precision(code, amount)
          currency = self.currency(code)
          walletBytes = self.remove0x_prefix(self.walletAddress)
          byteArray = [
              self.base16_to_binary(nonce),
              self.base16_to_binary(walletBytes),
              self.encode(currency['id']),
              self.encode(amountString),
              self.number_to_be(1, 1),  # bool set to True
          ]
          binary = self.binary_concat_array(byteArray)
          hash = self.hash(binary, 'keccak', 'hex')
          signature = self.sign_message_string(hash, self.privateKey)
          request = {
              'parameters': {
                  'nonce': nonce,
                  'wallet': address,
                  'asset': currency['id'],
                  'quantity': amountString,
              },
              'signature': signature,
          }
          # {
          #   withdrawalId: 'a61dcff0-ec4d-11ea-8b83-c78a6ecb3180',
          #   asset: 'ETH',
          #   assetContractAddress: '0x0000000000000000000000000000000000000000',
          #   quantity: '0.20000000',
          #   time: 1598962883190,
          #   fee: '0.00024000',
          #   txStatus: 'pending',
          #   txId: null
          # }
          response = await self.privatePostWithdrawals(request)
          id = self.safe_string(response, 'withdrawalId')
          return {
              'info': response,
              'id': id,
          }
  
      async def cancel_order(self, id, symbol=None, params={}):
          self.check_required_credentials()
          await self.load_markets()
          market = None
          if symbol is not None:
              market = self.market(symbol)
          nonce = self.uuidv1()
          walletBytes = self.remove0x_prefix(self.walletAddress)
          byteArray = [
              self.base16_to_binary(nonce),
              self.base16_to_binary(walletBytes),
              self.encode(id),
          ]
          binary = self.binary_concat_array(byteArray)
          hash = self.hash(binary, 'keccak', 'hex')
          signature = self.sign_message_string(hash, self.privateKey)
          request = {
              'parameters': {
                  'nonce': nonce,
                  'wallet': self.walletAddress,
                  'orderId': id,
              },
              'signature': signature,
          }
          # [{orderId: '688336f0-ec50-11ea-9842-b332f8a34d0e'}]
          response = await self.privateDeleteOrders(self.extend(request, params))
          canceledOrder = self.safe_value(response, 0)
          return self.parse_order(canceledOrder, market)
  
      def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
          errorCode = self.safe_string(response, 'code')
          message = self.safe_string(response, 'message')
          if errorCode in self.exceptions:
              Exception = self.exceptions[errorCode]
              raise Exception(self.id + ' ' + message)
          if errorCode is not None:
              raise ExchangeError(self.id + ' ' + message)
  
      async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
          params = self.extend({
              'method': 'privateGetDeposits',
          }, params)
          return self.fetch_transactions_helper(code, since, limit, params)
  
      async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
          params = self.extend({
              'method': 'privateGetWithdrawals',
          }, params)
          return self.fetch_transactions_helper(code, since, limit, params)
  
      async def fetch_transactions_helper(self, code=None, since=None, limit=None, params={}):
          await self.load_markets()
          nonce = self.uuidv1()
          request = {
              'nonce': nonce,
              'wallet': self.walletAddress,
          }
          currency = None
          if code is not None:
              currency = self.currency(code)
              request['asset'] = currency['id']
          if since is not None:
              request['start'] = since
          if limit is not None:
              request['limit'] = limit
          # [
          #   {
          #     depositId: 'e9970cc0-eb6b-11ea-9e89-09a5ebc1f98e',
          #     asset: 'ETH',
          #     quantity: '1.00000000',
          #     txId: '0xcd4aac3171d7131cc9e795568c67938675185ac17641553ef54c8a7c294c8142',
          #     txTime: 1598865853000,
          #     confirmationTime: 1598865930231
          #   }
          # ]
          method = params['method']
          params = self.omit(params, 'method')
          response = await getattr(self, method)(self.extend(request, params))
          return self.parse_transactions(response, currency, since, limit)
  
      def parse_transaction_status(self, status):
          statuses = {
              'mined': 'ok',
          }
          return self.safe_string(statuses, status, status)
  
      def parse_transaction(self, transaction, currency=None):
          # fetchDeposits
          # {
          #   depositId: 'e9970cc0-eb6b-11ea-9e89-09a5ebc1f98f',
          #   asset: 'ETH',
          #   quantity: '1.00000000',
          #   txId: '0xcd4aac3171d7131cc9e795568c67938675185ac17641553ef54c8a7c294c8142',
          #   txTime: 1598865853000,
          #   confirmationTime: 1598865930231
          # }
          # fetchWithdrwalas
          # {
          #   withdrawalId: 'a62d8760-ec4d-11ea-9fa6-47904c19499b',
          #   asset: 'ETH',
          #   assetContractAddress: '0x0000000000000000000000000000000000000000',
          #   quantity: '0.20000000',
          #   time: 1598962883288,
          #   fee: '0.00024000',
          #   txId: '0x305e9cdbaa85ad029f50578d13d31d777c085de573ed5334d95c19116d8c03ce',
          #   txStatus: 'mined'
          #  }
          type = None
          if 'depositId' in transaction:
              type = 'deposit'
          elif 'withdrawalId' in transaction:
              type = 'withdrawal'
          id = self.safe_string_2(transaction, 'depositId', 'withdrawId')
          code = self.safe_currency_code(self.safe_string(transaction, 'asset'), currency)
          amount = self.safe_float(transaction, 'quantity')
          txid = self.safe_string(transaction, 'txId')
          timestamp = self.safe_integer(transaction, 'txTime')
          fee = None
          if 'fee' in transaction:
              fee = {
                  'cost': self.safe_float(transaction, 'fee'),
                  'currency': 'ETH',
              }
          rawStatus = self.safe_string(transaction, 'txStatus')
          status = self.parse_transaction_status(rawStatus)
          updated = self.safe_integer(transaction, 'confirmationTime')
          return {
              'info': transaction,
              'id': id,
              'txid': txid,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'address': None,
              'tag': None,
              'type': type,
              'amount': amount,
              'currency': code,
              'status': status,
              'updated': updated,
              'fee': fee,
          }
  
      def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
          version = self.safe_string(self.options, 'version', 'v1')
          url = self.urls['api'][api] + '/' + version + '/' + path
          keys = list(params.keys())
          length = len(keys)
          query = None
          if length > 0:
              if method == 'GET':
                  query = self.urlencode(params)
                  url = url + '?' + query
              else:
                  body = self.json(params)
          headers = {
              'Content-Type': 'application/json',
          }
          if self.apiKey is not None:
              headers['IDEX-API-Key'] = self.apiKey
          if api == 'private':
              payload = None
              if method == 'GET':
                  payload = query
              else:
                  payload = body
              headers['IDEX-HMAC-Signature'] = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha256, 'hex')
          return {'url': url, 'method': method, 'body': body, 'headers': headers}
  