# -*- coding: utf-8 -*-

# PLEASE DO NOT EDIT THIS FILE, IT IS GENERATED AND WILL BE OVERWRITTEN:
# https://github.com/ccxt_zagrava/ccxt_zagrava/blob/master/CONTRIBUTING.md#how-to-contribute-code

from ccxt_zagrava.base.exchange import Exchange
from ccxt_zagrava.abstract.idex import ImplicitAPI
import hashlib
from ccxt_zagrava.base.types import Balances, Currencies, Currency, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, TradingFees, Transaction
from typing import List
from ccxt_zagrava.base.errors import ExchangeError
from ccxt_zagrava.base.errors import AuthenticationError
from ccxt_zagrava.base.errors import BadRequest
from ccxt_zagrava.base.errors import InsufficientFunds
from ccxt_zagrava.base.errors import InvalidAddress
from ccxt_zagrava.base.errors import InvalidOrder
from ccxt_zagrava.base.errors import NotSupported
from ccxt_zagrava.base.errors import DDoSProtection
from ccxt_zagrava.base.errors import ExchangeNotAvailable
from ccxt_zagrava.base.decimal_to_precision import ROUND
from ccxt_zagrava.base.decimal_to_precision import TRUNCATE
from ccxt_zagrava.base.decimal_to_precision import DECIMAL_PLACES
from ccxt_zagrava.base.decimal_to_precision import TICK_SIZE
from ccxt_zagrava.base.decimal_to_precision import PAD_WITH_ZERO
from ccxt_zagrava.base.precise import Precise


class idex(Exchange, ImplicitAPI):

    def describe(self):
        return self.deep_extend(super(idex, self).describe(), {
            'id': 'idex',
            'name': 'IDEX',
            'countries': ['US'],
            'rateLimit': 1000,
            'version': 'v3',
            'pro': True,
            'dex': True,
            'certified': False,
            'requiresWeb3': True,
            'has': {
                'CORS': None,
                'spot': True,
                'margin': False,
                'swap': False,
                'future': False,
                'option': False,
                'addMargin': False,
                'cancelAllOrders': True,
                'cancelOrder': True,
                'cancelOrders': False,
                'closeAllPositions': False,
                'closePosition': False,
                'createDepositAddress': False,
                'createOrder': True,
                'createReduceOnlyOrder': False,
                'createStopLimitOrder': True,
                'createStopMarketOrder': True,
                'createStopOrder': True,
                'fetchBalance': True,
                'fetchBorrowRateHistories': False,
                'fetchBorrowRateHistory': False,
                'fetchClosedOrders': True,
                'fetchCrossBorrowRate': False,
                'fetchCrossBorrowRates': False,
                'fetchCurrencies': True,
                'fetchDeposit': True,
                'fetchDepositAddress': True,
                'fetchDepositAddresses': False,
                'fetchDepositAddressesByNetwork': False,
                'fetchDeposits': True,
                'fetchFundingHistory': False,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': False,
                'fetchFundingRates': False,
                'fetchIndexOHLCV': False,
                'fetchIsolatedBorrowRate': False,
                'fetchIsolatedBorrowRates': False,
                'fetchLeverage': False,
                'fetchLeverageTiers': False,
                'fetchMarginMode': False,
                'fetchMarkets': True,
                'fetchMarkOHLCV': False,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenInterestHistory': False,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': False,
                'fetchPosition': False,
                'fetchPositionHistory': False,
                'fetchPositionMode': False,
                'fetchPositions': False,
                'fetchPositionsForSymbol': False,
                'fetchPositionsHistory': False,
                'fetchPositionsRisk': False,
                'fetchPremiumIndexOHLCV': False,
                'fetchStatus': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': True,
                'fetchTrades': True,
                'fetchTradingFee': False,
                'fetchTradingFees': True,
                'fetchTransactions': False,
                'fetchWithdrawal': True,
                'fetchWithdrawals': True,
                'reduceMargin': False,
                'sandbox': True,
                'setLeverage': False,
                'setMarginMode': False,
                'setPositionMode': False,
                'transfer': False,
                'withdraw': True,
            },
            'timeframes': {
                '1m': '1m',
                '5m': '5m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1h',
                '6h': '6h',
                '1d': '1d',
            },
            'urls': {
                'test': {
                    'MATIC': 'https://api-sandbox-matic.idex.io',
                },
                'logo': 'https://user-images.githubusercontent.com/51840849/94481303-2f222100-01e0-11eb-97dd-bc14c5943a86.jpg',
                'api': {
                    'MATIC': 'https://api-matic.idex.io',
                },
                'www': 'https://idex.io',
                'doc': [
                    'https://api-docs-v3.idex.io/',
                ],
            },
            'api': {
                'public': {
                    'get': {
                        'ping': 1,
                        'time': 1,
                        'exchange': 1,
                        'assets': 1,
                        'markets': 1,
                        'tickers': 1,
                        'candles': 1,
                        'trades': 1,
                        'orderbook': 1,
                    },
                },
                'private': {
                    'get': {
                        'user': 1,
                        'wallets': 1,
                        'balances': 1,
                        'orders': 0.1,
                        'fills': 0.1,
                        'deposits': 1,
                        'withdrawals': 1,
                        'wsToken': 1,
                    },
                    'post': {
                        'wallets': 1,
                        'orders': 0.1,
                        'orders/test': 0.1,
                        'withdrawals': 1,
                    },
                    'delete': {
                        'orders': 0.1,
                    },
                },
            },
            'options': {
                'defaultTimeInForce': 'gtc',
                'defaultSelfTradePrevention': 'cn',
                'network': 'MATIC',
            },
            'exceptions': {
                'exact': {
                    '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,
            },
            'precisionMode': TICK_SIZE,
            'paddingMode': PAD_WITH_ZERO,
            'commonCurrencies': {},
        })

    def price_to_precision(self, symbol, price):
        #
        # we override priceToPrecision to fix the following issue
        # https://github.com/ccxt_zagrava/ccxt_zagrava/issues/13367
        # {"code":"INVALID_PARAMETER","message":"invalid value provided for request parameter \"price\": all quantities and prices must be below 100 billion, above 0, need to be provided, and always require 4 decimals ending with 4 zeroes"}
        #
        market = self.market(symbol)
        info = self.safe_value(market, 'info', {})
        quoteAssetPrecision = self.safe_integer(info, 'quoteAssetPrecision')
        price = self.decimal_to_precision(price, ROUND, market['precision']['price'], self.precisionMode)
        return self.decimal_to_precision(price, TRUNCATE, quoteAssetPrecision, DECIMAL_PLACES, PAD_WITH_ZERO)

    def fetch_markets(self, params={}) -> List[Market]:
        """
        retrieves data on all markets for idex
        :see: https://api-docs-v3.idex.io/#get-markets
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        response = self.publicGetMarkets(params)
        #
        #    [
        #        {
        #            "market": "ETH-USDC",
        #            "type": "hybrid",
        #            "status": "activeHybrid",
        #            "baseAsset": "ETH",
        #            "baseAssetPrecision": "8",
        #            "quoteAsset": "USDC",
        #            "quoteAssetPrecision": "8",
        #            "makerFeeRate": "0.0000",
        #            "takerFeeRate": "0.2500",
        #            "takerIdexFeeRate": "0.0500",
        #            "takerLiquidityProviderFeeRate": "0.2000",
        #            "tickSize": "0.01000000"
        #        },
        #    ]
        #
        response2 = self.publicGetExchange()
        #
        #    {
        #        "timeZone": "UTC",
        #        "serverTime": "1654460599952",
        #        "maticDepositContractAddress": "0x3253a7e75539edaeb1db608ce6ef9aa1ac9126b6",
        #        "maticCustodyContractAddress": "0x3bcc4eca0a40358558ca8d1bcd2d1dbde63eb468",
        #        "maticUsdPrice": "0.60",
        #        "gasPrice": "180",
        #        "volume24hUsd": "10015814.46",
        #        "totalVolumeUsd": "1589273533.28",
        #        "totalTrades": "1534904",
        #        "totalValueLockedUsd": "12041929.44",
        #        "idexStakingValueLockedUsd": "20133816.98",
        #        "idexTokenAddress": "0x9Cb74C8032b007466865f060ad2c46145d45553D",
        #        "idexUsdPrice": "0.07",
        #        "idexMarketCapUsd": "48012346.00",
        #        "makerFeeRate": "0.0000",
        #        "takerFeeRate": "0.0025",
        #        "takerIdexFeeRate": "0.0005",
        #        "takerLiquidityProviderFeeRate": "0.0020",
        #        "makerTradeMinimum": "10.00000000",
        #        "takerTradeMinimum": "1.00000000",
        #        "withdrawMinimum": "0.50000000",
        #        "liquidityAdditionMinimum": "0.50000000",
        #        "liquidityRemovalMinimum": "0.40000000",
        #        "blockConfirmationDelay": "64"
        #    }
        #
        maker = self.safe_number(response2, 'makerFeeRate')
        taker = self.safe_number(response2, 'takerFeeRate')
        makerMin = self.safe_string(response2, 'makerTradeMinimum')
        takerMin = self.safe_string(response2, 'takerTradeMinimum')
        minCostETH = self.parse_number(Precise.string_min(makerMin, takerMin))
        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)
            basePrecision = self.parse_number(self.parse_precision(self.safe_string(entry, 'baseAssetPrecision')))
            quotePrecision = self.parse_number(self.parse_precision(self.safe_string(entry, 'quoteAssetPrecision')))
            status = self.safe_string(entry, 'status')
            minCost = None
            if quote == 'ETH':
                minCost = minCostETH
            result.append({
                'id': marketId,
                'symbol': base + '/' + quote,
                'base': base,
                'quote': quote,
                'settle': None,
                'baseId': baseId,
                'quoteId': quoteId,
                'settleId': None,
                'type': 'spot',
                'spot': True,
                'margin': False,
                'swap': False,
                'future': False,
                'option': False,
                'active': (status != 'inactive'),
                'contract': False,
                'linear': None,
                'inverse': None,
                'taker': taker,
                'maker': maker,
                'contractSize': None,
                'expiry': None,
                'expiryDatetime': None,
                'strike': None,
                'optionType': None,
                'precision': {
                    'amount': basePrecision,
                    'price': self.safe_number(entry, 'tickSize'),
                },
                'limits': {
                    'leverage': {
                        'min': None,
                        'max': None,
                    },
                    'amount': {
                        'min': basePrecision,
                        'max': None,
                    },
                    'price': {
                        'min': quotePrecision,
                        'max': None,
                    },
                    'cost': {
                        'min': minCost,
                        'max': None,
                    },
                },
                'created': None,
                'info': entry,
            })
        return result

    def fetch_ticker(self, symbol: str, params={}) -> Ticker:
        """
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :see: https://api-docs-v3.idex.io/#get-tickers
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            '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 = self.publicGetTickers(self.extend(request, params))
        ticker = self.safe_dict(response, 0)
        return self.parse_ticker(ticker, market)

    def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """
        fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
        :see: https://api-docs-v3.idex.io/#get-tickers
        :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        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 = self.publicGetTickers(params)
        return self.parse_tickers(response, symbols)

    def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
        # {
        #   "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')
        market = self.safe_market(marketId, market, '-')
        symbol = market['symbol']
        timestamp = self.safe_integer(ticker, 'time')
        close = self.safe_string(ticker, 'close')
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_string(ticker, 'high'),
            'low': self.safe_string(ticker, 'low'),
            'bid': self.safe_string(ticker, 'bid'),
            'bidVolume': None,
            'ask': self.safe_string(ticker, 'ask'),
            'askVolume': None,
            'vwap': None,
            'open': self.safe_string(ticker, 'open'),
            'close': close,
            'last': close,
            'previousClose': None,
            'change': None,
            'percentage': self.safe_string(ticker, 'percentChange'),
            'average': None,
            'baseVolume': self.safe_string(ticker, 'baseVolume'),
            'quoteVolume': self.safe_string(ticker, 'quoteVolume'),
            'info': ticker,
        }, market)

    def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :see: https://api-docs-v3.idex.io/#get-candles
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int [since]: timestamp in ms of the earliest candle to fetch
        :param int [limit]: the maximum amount of candles to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'market': market['id'],
            'interval': timeframe,
        }
        if since is not None:
            request['start'] = since
        if limit is not None:
            request['limit'] = min(limit, 1000)
        response = 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: Market = None) -> list:
        # {
        #   "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_number(ohlcv, 'open')
        high = self.safe_number(ohlcv, 'high')
        low = self.safe_number(ohlcv, 'low')
        close = self.safe_number(ohlcv, 'close')
        volume = self.safe_number(ohlcv, 'volume')
        return [timestamp, open, high, low, close, volume]

    def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        get the list of most recent trades for a particular symbol
        :see: https://api-docs-v3.idex.io/#get-trades
        :param str symbol: unified symbol of the market to fetch trades for
        :param int [since]: timestamp in ms of the earliest trade to fetch
        :param int [limit]: the maximum amount of trades to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Trade[]: a list of `trade structures <https://docs.ccxt_zagrava.com/#/?id=public-trades>`
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'market': market['id'],
        }
        if since is not None:
            request['start'] = since
        if limit is not None:
            request['limit'] = min(limit, 1000)
        # [
        #   {
        #     "fillId": "b5467d00-b13e-3fa9-8216-dd66735550fc",
        #     "price": "0.09771286",
        #     "quantity": "1.45340410",
        #     "quoteQuantity": "0.14201627",
        #     "time": 1598345638994,
        #     "makerSide": "buy",
        #     "sequence": 3853
        #   }, ...
        # ]
        response = self.publicGetTrades(self.extend(request, params))
        return self.parse_trades(response, market, since, limit)

    def parse_trade(self, trade: dict, market: Market = None) -> Trade:
        #
        # public trades
        #  {
        #      "fillId":"a4883704-850b-3c4b-8588-020b5e4c62f1",
        #      "price":"0.20377008",
        #      "quantity":"47.58448728",
        #      "quoteQuantity":"9.69629509",
        #      "time":1642091300873,
        #      "makerSide":"buy",
        #      "type":"hybrid",        # one of either: "orderBook", "hybrid", or "pool"
        #      "sequence":31876
        #  }
        #
        # private trades
        #  {
        #      "fillId":"83429066-9334-3582-b710-78858b2f0d6b",
        #      "price":"0.20717368",
        #      "quantity":"15.00000000",
        #      "quoteQuantity":"3.10760523",
        #      "orderBookQuantity":"0.00000003",
        #      "orderBookQuoteQuantity":"0.00000001",
        #      "poolQuantity":"14.99999997",
        #      "poolQuoteQuantity":"3.10760522",
        #      "time":1642083351215,
        #      "makerSide":"sell",
        #      "sequence":31795,
        #      "market":"IDEX-USDC",
        #      "orderId":"4fe993f0-747b-11ec-bd08-79d4a0b6e47c",
        #      "side":"buy",
        #      "fee":"0.03749989",
        #      "feeAsset":"IDEX",
        #      "gas":"0.40507261",
        #      "liquidity":"taker",
        #      "type":"hybrid",
        #      "txId":"0x69f6d82a762d12e3201efd0b3e9cc1969351e3c6ea3cf07c47c66bf24a459815",
        #      "txStatus":"mined"
        #  }
        #
        id = self.safe_string(trade, 'fillId')
        priceString = self.safe_string(trade, 'price')
        amountString = self.safe_string(trade, 'quantity')
        costString = self.safe_string(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')
        feeCostString = self.safe_string(trade, 'fee')
        fee = None
        if feeCostString is not None:
            feeCurrencyId = self.safe_string(trade, 'feeAsset')
            fee = {
                'cost': feeCostString,
                'currency': self.safe_currency_code(feeCurrencyId),
            }
        orderId = self.safe_string(trade, 'orderId')
        return self.safe_trade({
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'id': id,
            'order': orderId,
            'type': 'limit',
            'side': side,
            'takerOrMaker': takerOrMaker,
            'price': priceString,
            'amount': amountString,
            'cost': costString,
            'fee': fee,
        }, market)

    def fetch_trading_fees(self, params={}) -> TradingFees:
        """
        fetch the trading fees for multiple markets
        :see: https://api-docs-v3.idex.io/#get-api-account
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `fee structures <https://docs.ccxt_zagrava.com/#/?id=fee-structure>` indexed by market symbols
        """
        self.check_required_credentials()
        self.load_markets()
        nonce = self.uuidv1()
        request: dict = {
            'nonce': nonce,
        }
        response = None
        response = self.privateGetUser(self.extend(request, params))
        #
        #     {
        #         "depositEnabled": True,
        #         "orderEnabled": True,
        #         "cancelEnabled": True,
        #         "withdrawEnabled": True,
        #         "totalPortfolioValueUsd": "0.00",
        #         "makerFeeRate": "0.0000",
        #         "takerFeeRate": "0.0025",
        #         "takerIdexFeeRate": "0.0005",
        #         "takerLiquidityProviderFeeRate": "0.0020"
        #     }
        #
        maker = self.safe_number(response, 'makerFeeRate')
        taker = self.safe_number(response, 'takerFeeRate')
        result: dict = {}
        for i in range(0, len(self.symbols)):
            symbol = self.symbols[i]
            result[symbol] = {
                'info': response,
                'symbol': symbol,
                'maker': maker,
                'taker': taker,
                'percentage': True,
                'tierBased': False,
            }
        return result

    def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :see: https://api-docs-v3.idex.io/#get-order-books
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int [limit]: the maximum amount of order book entries to return
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt_zagrava.com/#/?id=order-book-structure>` indexed by market symbols
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            '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 = self.publicGetOrderbook(self.extend(request, params))
        nonce = self.safe_integer(response, 'sequence')
        return {
            'symbol': symbol,
            '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_number(order, 0)
            amount = self.safe_number(order, 1)
            orderCount = self.safe_integer(order, 2)
            result.append([price, amount, orderCount])
        descending = side == 'bids'
        return self.sort_by(result, 0, descending)

    def fetch_currencies(self, params={}) -> Currencies:
        """
        fetches all available currencies on an exchange
        :see: https://api-docs-v3.idex.io/#get-assets
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an associative dictionary of currencies
        """
        response = self.publicGetAssets(params)
        #
        #     [
        #        {
        #            "name": "Ethereum",
        #            "symbol": "ETH",
        #            "contractAddress": "0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619",
        #            "assetDecimals": "18",
        #            "exchangeDecimals": "8",
        #            "maticPrice": "3029.38503483"
        #        },
        #     ]
        #
        result: dict = {}
        for i in range(0, len(response)):
            entry = response[i]
            name = self.safe_string(entry, 'name')
            currencyId = self.safe_string(entry, 'symbol')
            code = self.safe_currency_code(currencyId)
            precision = self.parse_number(self.parse_precision(self.safe_string(entry, 'exchangeDecimals')))
            result[code] = {
                'id': currencyId,
                'code': code,
                'info': entry,
                'type': None,
                'name': name,
                'active': None,
                'deposit': None,
                'withdraw': None,
                'fee': None,
                'precision': precision,
                'limits': {
                    'amount': {'min': precision, 'max': None},
                    'withdraw': {'min': precision, 'max': None},
                },
            }
        return result

    def parse_balance(self, response) -> Balances:
        result: dict = {
            'info': response,
            'timestamp': None,
            'datetime': None,
        }
        for i in range(0, len(response)):
            entry = response[i]
            currencyId = self.safe_string(entry, 'asset')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['total'] = self.safe_string(entry, 'quantity')
            account['free'] = self.safe_string(entry, 'availableForTrade')
            account['used'] = self.safe_string(entry, 'locked')
            result[code] = account
        return self.safe_balance(result)

    def fetch_balance(self, params={}) -> Balances:
        """
        query for balance and get the amount of funds available for trading or funds locked in orders
        :see: https://api-docs-v3.idex.io/#get-balances
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `balance structure <https://docs.ccxt_zagrava.com/#/?id=balance-structure>`
        """
        self.check_required_credentials()
        self.load_markets()
        nonce1 = self.uuidv1()
        request: dict = {
            '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 + ' fetchBalance() wallet is None, set self.walletAddress or "address" in params')
        response = None
        try:
            response = self.privateGetBalances(extendedRequest)
        except Exception as e:
            if isinstance(e, InvalidAddress):
                walletAddress = extendedRequest['wallet']
                self.associate_wallet(walletAddress)
                response = self.privateGetBalances(extendedRequest)
            else:
                raise e
        return self.parse_balance(response)

    def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        fetch all trades made by the user
        :see: https://api-docs-v3.idex.io/#get-fills
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch trades for
        :param int [limit]: the maximum number of trades structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Trade[]: a list of `trade structures <https://docs.ccxt_zagrava.com/#/?id=trade-structure>`
        """
        self.check_required_credentials()
        self.load_markets()
        market = None
        request: dict = {
            '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 + ' fetchMyTrades() walletAddress is None, set self.walletAddress or "address" in params')
        response = None
        try:
            response = self.privateGetFills(extendedRequest)
        except Exception as e:
            if isinstance(e, InvalidAddress):
                walletAddress = extendedRequest['wallet']
                self.associate_wallet(walletAddress)
                response = self.privateGetFills(extendedRequest)
            else:
                raise e
        return self.parse_trades(response, market, since, limit)

    def fetch_order(self, id: str, symbol: Str = None, params={}):
        """
        fetches information on an order made by the user
        :see: https://api-docs-v3.idex.io/#get-orders
        :param str symbol: unified symbol of the market the order was made in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        request: dict = {
            'orderId': id,
        }
        return self.fetch_orders_helper(symbol, None, None, self.extend(request, params))

    def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetch all unfilled currently open orders
        :see: https://api-docs-v3.idex.io/#get-orders
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch open orders for
        :param int [limit]: the maximum number of  open orders structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Order[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        request: dict = {
            'closed': False,
        }
        return self.fetch_orders_helper(symbol, since, limit, self.extend(request, params))

    def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        fetches information on multiple closed orders made by the user
        :see: https://api-docs-v3.idex.io/#get-orders
        :param str symbol: unified market symbol of the market orders were made in
        :param int [since]: the earliest time in ms to fetch orders for
        :param int [limit]: the maximum number of order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Order[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        request: dict = {
            'closed': True,
        }
        return self.fetch_orders_helper(symbol, since, limit, self.extend(request, params))

    def fetch_orders_helper(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        self.load_markets()
        request: dict = {
            '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 = 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: Str):
        # https://docs.idex.io/#order-states-amp-lifecycle
        statuses: dict = {
            'active': 'open',
            'partiallyFilled': 'open',
            'rejected': 'canceled',
            'filled': 'closed',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order: dict, market: Market = None) -> Order:
        #
        #     {
        #         "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, '-')
        type = self.safe_string(order, 'type')
        amount = self.safe_string(order, 'originalQuantity')
        filled = self.safe_string(order, 'executedQuantity')
        average = self.safe_string(order, 'avgExecutionPrice')
        price = self.safe_string(order, 'price')
        rawStatus = self.safe_string(order, 'status')
        timeInForce = self.safe_string_upper(order, 'timeInForce')
        status = self.parse_order_status(rawStatus)
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': None,
            'triggerPrice': None,
            'amount': amount,
            'cost': None,
            'average': average,
            'filled': filled,
            'remaining': None,
            'status': status,
            'fee': None,
            'trades': fills,
        }, market)

    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: dict = {
            'parameters': {
                'nonce': nonce,
                'wallet': walletAddress,
            },
            'signature': signature,
        }
        result = self.privatePostWallets(request)
        return result

    def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        """
        create a trade order, https://docs.idex.io/#create-order
        :see: https://api-docs-v3.idex.io/#create-order
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much of currency you want to trade in units of base currency
        :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param bool [params.test]: set to True to test an order, no order will be created but the request will be validated
        :returns dict: an `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.check_required_credentials()
        self.load_markets()
        testOrder = self.safe_bool(params, 'test', False)
        params = self.omit(params, 'test')
        market = self.market(symbol)
        nonce = self.uuidv1()
        typeEnum = None
        stopLossTypeEnums: dict = {
            'stopLoss': 3,
            'stopLossLimit': 4,
            'takeProfit': 5,
            'takeProfitLimit': 6,
        }
        stopPriceString = None
        if (type == 'stopLossLimit') or (type == 'takeProfitLimit') or ('stopPrice' in params):
            if not ('stopPrice' in params):
                raise BadRequest(self.id + ' createOrder() stopPrice is a required parameter for ' + type + 'orders')
            stopPriceString = self.price_to_precision(symbol, params['stopPrice'])
        limitTypeEnums: dict = {
            'limit': 1,
            'limitMaker': 2,
        }
        priceString = None
        typeLower = type.lower()
        limitOrder = typeLower.find('limit') >= 0
        if type in limitTypeEnums:
            typeEnum = limitTypeEnums[type]
            priceString = self.price_to_precision(symbol, price)
        elif type in stopLossTypeEnums:
            typeEnum = stopLossTypeEnums[type]
            priceString = self.price_to_precision(symbol, price)
        elif type == 'market':
            typeEnum = 0
        else:
            raise BadRequest(self.id + ' ' + type + ' is not a valid order type')
        amountEnum = 0  # base quantity
        if 'quoteOrderQuantity' in params:
            if type != 'market':
                raise NotSupported(self.id + ' createOrder() quoteOrderQuantity is not supported for ' + type + ' orders, only supported for market orders')
            amountEnum = 1
            amount = self.safe_number(params, 'quoteOrderQuantity')
        sideEnum = 0 if (side == 'buy') else 1
        walletBytes = self.remove0x_prefix(self.walletAddress)
        network = self.safe_string(self.options, 'network', 'ETH')
        orderVersion = self.get_supported_mapping(network, {
            'ETH': 1,
            'BSC': 2,
            'MATIC': 4,
        })
        amountString = self.amount_to_precision(symbol, amount)
        # https://docs.idex.io/#time-in-force
        timeInForceEnums: dict = {
            '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: dict = {
            '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']),
            self.number_to_be(typeEnum, 1),
            self.number_to_be(sideEnum, 1),
            self.encode(amountString),
            self.number_to_be(amountEnum, 1),
        ]
        if limitOrder:
            encodedPrice = self.encode(priceString)
            byteArray.append(encodedPrice)
        if type in stopLossTypeEnums:
            encodedPrice = self.encode(stopPriceString or 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: dict = {
            'parameters': {
                'nonce': nonce,
                'market': market['id'],
                'side': side,
                'type': type,
                'wallet': self.walletAddress,
                'selfTradePrevention': selfTradePrevention,
            },
            'signature': signature,
        }
        if type != 'market':
            request['parameters']['timeInForce'] = timeInForce
        if limitOrder:
            request['parameters']['price'] = priceString
        if type in stopLossTypeEnums:
            request['parameters']['stopPrice'] = stopPriceString or 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 = None
        if testOrder:
            response = self.privatePostOrdersTest(request)
        else:
            response = self.privatePostOrders(request)
        return self.parse_order(response, market)

    def withdraw(self, code: str, amount: float, address: str, tag=None, params={}):
        """
        make a withdrawal
        :see: https://api-docs-v3.idex.io/#withdraw-funds
        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str tag:
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transaction structure <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        self.check_required_credentials()
        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: dict = {
            'parameters': {
                'nonce': nonce,
                'wallet': address,
                'asset': currency['id'],
                'quantity': amountString,
            },
            'signature': signature,
        }
        response = self.privatePostWithdrawals(request)
        #
        #     {
        #         "withdrawalId": "a61dcff0-ec4d-11ea-8b83-c78a6ecb3180",
        #         "asset": "ETH",
        #         "assetContractAddress": "0x0000000000000000000000000000000000000000",
        #         "quantity": "0.20000000",
        #         "time": 1598962883190,
        #         "fee": "0.00024000",
        #         "txStatus": "pending",
        #         "txId": null
        #     }
        #
        return self.parse_transaction(response, currency)

    def cancel_all_orders(self, symbol: Str = None, params={}):
        """
        cancel all open orders
        :see: https://api-docs-v3.idex.io/#cancel-order
        :param str symbol: unified market symbol, only orders in the market of self symbol are cancelled when symbol is not None
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.check_required_credentials()
        self.load_markets()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        nonce = self.uuidv1()
        request: dict = {
            'parameters': {
                'nonce': nonce,
                'wallet': self.walletAddress,
            },
        }
        walletBytes = self.remove0x_prefix(self.walletAddress)
        byteArray = [
            self.base16_to_binary(nonce),
            self.base16_to_binary(walletBytes),
        ]
        if market is not None:
            byteArray.append(self.encode(market['id']))
            request['parameters']['market'] = market['id']
        binary = self.binary_concat_array(byteArray)
        hash = self.hash(binary, 'keccak', 'hex')
        signature = self.sign_message_string(hash, self.privateKey)
        request['signature'] = signature
        # [{orderId: "688336f0-ec50-11ea-9842-b332f8a34d0e"}]
        response = self.privateDeleteOrders(self.extend(request, params))
        return self.parse_orders(response, market)

    def cancel_order(self, id: str, symbol: Str = None, params={}):
        """
        cancels an open order
        :see: https://api-docs-v3.idex.io/#cancel-order
        :param str id: order id
        :param str symbol: unified symbol of the market the order was made in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        self.check_required_credentials()
        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: dict = {
            'parameters': {
                'nonce': nonce,
                'wallet': self.walletAddress,
                'orderId': id,
            },
            'signature': signature,
        }
        # [{orderId: "688336f0-ec50-11ea-9842-b332f8a34d0e"}]
        response = self.privateDeleteOrders(self.extend(request, params))
        canceledOrder = self.safe_dict(response, 0)
        return self.parse_order(canceledOrder, market)

    def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
        errorCode = self.safe_string(response, 'code')
        message = self.safe_string(response, 'message')
        if errorCode is not None:
            self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, message)
            raise ExchangeError(self.id + ' ' + message)
        return None

    def fetch_deposit(self, id: str, code: Str = None, params={}):
        """
        fetch information on a deposit
        :see: https://api-docs-v3.idex.io/#get-deposits
        :param str id: deposit id
        :param str code: not used by idex fetchDeposit()
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transaction structure <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        self.load_markets()
        nonce = self.uuidv1()
        request: dict = {
            'nonce': nonce,
            'wallet': self.walletAddress,
            'depositId': id,
        }
        response = self.privateGetDeposits(self.extend(request, params))
        return self.parse_transaction(response)

    def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all deposits made to an account
        :see: https://api-docs-v3.idex.io/#get-deposits
        :param str code: unified currency code
        :param int [since]: the earliest time in ms to fetch deposits for
        :param int [limit]: the maximum number of deposits structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `transaction structures <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        params = self.extend({
            'method': 'privateGetDeposits',
        }, params)
        return self.fetch_transactions_helper(code, since, limit, params)

    def fetch_status(self, params={}):
        """
        the latest known information on the availability of the exchange API
        :see: https://api-docs-v3.idex.io/#get-ping
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `status structure <https://docs.ccxt_zagrava.com/#/?id=exchange-status-structure>`
        """
        response = self.publicGetPing(params)
        return {
            'status': 'ok',  # if there's no Errors, status = 'ok'
            'updated': None,
            'eta': None,
            'url': None,
            'info': response,
        }

    def fetch_time(self, params={}):
        """
        fetches the current integer timestamp in milliseconds from the exchange server
        :see: https://api-docs-v3.idex.io/#get-time
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int: the current integer timestamp in milliseconds from the exchange server
        """
        response = self.publicGetTime(params)
        #
        #    {serverTime: "1655258263236"}
        #
        return self.safe_integer(response, 'serverTime')

    def fetch_withdrawal(self, id: str, code: Str = None, params={}):
        """
        fetch data on a currency withdrawal via the withdrawal id
        :see: https://api-docs-v3.idex.io/#get-withdrawals
        :param str id: withdrawal id
        :param str code: not used by idex.fetchWithdrawal
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transaction structure <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        self.load_markets()
        nonce = self.uuidv1()
        request: dict = {
            'nonce': nonce,
            'wallet': self.walletAddress,
            'withdrawalId': id,
        }
        response = self.privateGetWithdrawals(self.extend(request, params))
        return self.parse_transaction(response)

    def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        fetch all withdrawals made from an account
        :see: https://api-docs-v3.idex.io/#get-withdrawals
        :param str code: unified currency code
        :param int [since]: the earliest time in ms to fetch withdrawals for
        :param int [limit]: the maximum number of withdrawals structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `transaction structures <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        params = self.extend({
            'method': 'privateGetWithdrawals',
        }, params)
        return self.fetch_transactions_helper(code, since, limit, params)

    def fetch_transactions_helper(self, code: Str = None, since: Int = None, limit: Int = None, params={}):
        self.load_markets()
        nonce = self.uuidv1()
        request: dict = {
            '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 = None
        if method == 'privateGetDeposits':
            response = self.privateGetDeposits(self.extend(request, params))
        elif method == 'privateGetWithdrawals':
            response = self.privateGetWithdrawals(self.extend(request, params))
        else:
            raise NotSupported(self.id + ' fetchTransactionsHelper() not support self method')
        return self.parse_transactions(response, currency, since, limit)

    def parse_transaction_status(self, status: Str):
        statuses: dict = {
            'mined': 'ok',
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
        #
        # 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"
        #     }
        #
        # withdraw
        #
        #     {
        #         "withdrawalId": "a61dcff0-ec4d-11ea-8b83-c78a6ecb3180",
        #         "asset": "ETH",
        #         "assetContractAddress": "0x0000000000000000000000000000000000000000",
        #         "quantity": "0.20000000",
        #         "time": 1598962883190,
        #         "fee": "0.00024000",
        #         "txStatus": "pending",
        #         "txId": null
        #     }
        #
        type = None
        if 'depositId' in transaction:
            type = 'deposit'
        elif ('withdrawId' in transaction) or ('withdrawalId' in transaction):
            type = 'withdrawal'
        id = self.safe_string_2(transaction, 'depositId', 'withdrawId')
        id = self.safe_string(transaction, 'withdrawalId', id)
        code = self.safe_currency_code(self.safe_string(transaction, 'asset'), currency)
        amount = self.safe_number(transaction, 'quantity')
        txid = self.safe_string(transaction, 'txId')
        timestamp = self.safe_integer_2(transaction, 'txTime', 'time')
        fee = None
        if 'fee' in transaction:
            fee = {
                'cost': self.safe_number(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),
            'network': None,
            'address': None,
            'addressTo': None,
            'addressFrom': None,
            'tag': None,
            'tagTo': None,
            'tagFrom': None,
            'type': type,
            'amount': amount,
            'currency': code,
            'status': status,
            'updated': updated,
            'comment': None,
            'internal': None,
            'fee': fee,
        }

    def calculate_rate_limiter_cost(self, api, method, path, params, config={}):
        hasApiKey = (self.apiKey is not None)
        hasSecret = (self.secret is not None)
        hasWalletAddress = (self.walletAddress is not None)
        hasPrivateKey = (self.privateKey is not None)
        defaultCost = self.safe_value(config, 'cost', 1)
        authenticated = hasApiKey and hasSecret and hasWalletAddress and hasPrivateKey
        return(defaultCost / 2) if authenticated else defaultCost

    def fetch_deposit_address(self, code: Str = None, params={}):
        """
        fetch the Polygon address of the wallet
        :see: https://api-docs-v3.idex.io/#get-wallets
        :param str code: not used by idex
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `address structure <https://docs.ccxt_zagrava.com/#/?id=address-structure>`
        """
        request: dict = {}
        request['nonce'] = self.uuidv1()
        response = self.privateGetWallets(self.extend(request, params))
        #
        #    [
        #        {
        #            address: "0x37A1827CA64C94A26028bDCb43FBDCB0bf6DAf5B",
        #            totalPortfolioValueUsd: "0.00",
        #            time: "1678342148086"
        #        },
        #        {
        #            address: "0x0Ef3456E616552238B0c562d409507Ed6051A7b3",
        #            totalPortfolioValueUsd: "15.90",
        #            time: "1691697811659"
        #        }
        #    ]
        #
        return self.parse_deposit_address(response)

    def parse_deposit_address(self, depositAddress, currency: Currency = None):
        #
        #    [
        #        {
        #            address: "0x37A1827CA64C94A26028bDCb43FBDCB0bf6DAf5B",
        #            totalPortfolioValueUsd: "0.00",
        #            time: "1678342148086"
        #        },
        #        {
        #            address: "0x0Ef3456E616552238B0c562d409507Ed6051A7b3",
        #            totalPortfolioValueUsd: "15.90",
        #            time: "1691697811659"
        #        }
        #    ]
        #
        length = len(depositAddress)
        entry = self.safe_dict(depositAddress, length - 1)
        address = self.safe_string(entry, 'address')
        self.check_address(address)
        return {
            'info': depositAddress,
            'currency': None,
            'address': address,
            'tag': None,
            'network': 'MATIC',
        }

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        network = self.safe_string(self.options, 'network', 'ETH')
        version = self.safe_string(self.options, 'version', 'v1')
        url = self.urls['api'][network] + '/' + 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}

    def remove0x_prefix(self, hexData):
        if hexData[0:2] == '0x':
            return hexData[2:]
        else:
            return hexData

    def hash_message(self, message):
        # takes a hex encoded message
        binaryMessage = self.base16_to_binary(self.remove0x_prefix(message))
        prefix = self.encode('\x19Ethereum Signed Message:\n' + binaryMessage.byteLength)
        return '0x' + self.hash(self.binary_concat(prefix, binaryMessage), 'keccak', 'hex')

    def sign_hash(self, hash, privateKey):
        signature = self.ecdsa(hash[-64:], privateKey[-64:], 'secp256k1', None)
        return {
            'r': '0x' + signature['r'],
            's': '0x' + signature['s'],
            'v': 27 + signature['v'],
        }

    def sign_message(self, message, privateKey):
        return self.sign_hash(self.hash_message(message), privateKey[-64:])

    def sign_message_string(self, message, privateKey):
        # still takes the input hex string
        # same but returns a string instead of an object
        signature = self.sign_message(message, privateKey)
        return signature['r'] + self.remove0x_prefix(signature['s']) + self.binary_to_base16(self.number_to_be(signature['v'], 1))
