# -*- 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.base.exchange import Exchange
import hashlib
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import PermissionDenied
from ccxt.base.errors import AccountSuspended
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import BadSymbol
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import OrderNotFound
from ccxt.base.errors import RateLimitExceeded
from ccxt.base.errors import ExchangeNotAvailable
from ccxt.base.errors import OnMaintenance
from ccxt.base.decimal_to_precision import TICK_SIZE
from ccxt.base.precise import Precise


class hitbtc3(Exchange):

    def describe(self):
        return self.deep_extend(super(hitbtc3, self).describe(), {
            'id': 'hitbtc3',
            'name': 'HitBTC',
            'countries': ['HK'],
            'rateLimit': 100,  # TODO: optimize https://api.hitbtc.com/#rate-limiting
            'version': '3',
            'pro': True,
            'has': {
                'cancelOrder': True,
                'CORS': False,
                'createOrder': True,
                'editOrder': True,
                'fetchBalance': True,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchDeposits': False,
                'fetchFundingRateHistory': True,
                'fetchMarkets': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrder': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrders': False,
                'fetchOrderTrades': True,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTrades': True,
                'fetchTradingFee': True,
                'fetchTransactions': True,
                'fetchWithdrawals': False,
                'withdraw': True,
                'transfer': True,
            },
            'precisionMode': TICK_SIZE,
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27766555-8eaec20e-5edc-11e7-9c5b-6dc69fc42f5e.jpg',
                'test': {
                    'public': 'https://api.demo.hitbtc.com',
                    'private': 'https://api.demo.hitbtc.com',
                },
                'api': {
                    'public': 'https://api.hitbtc.com/api/3',
                    'private': 'https://api.hitbtc.com/api/3',
                },
                'www': 'https://hitbtc.com',
                'referral': 'https://hitbtc.com/?ref_id=5a5d39a65d466',
                'doc': [
                    'https://api.hitbtc.com',
                    'https://github.com/hitbtc-com/hitbtc-api/blob/master/APIv2.md',
                ],
                'fees': [
                    'https://hitbtc.com/fees-and-limits',
                    'https://support.hitbtc.com/hc/en-us/articles/115005148605-Fees-and-limits',
                ],
            },
            'api': {
                'public': {
                    'get': [
                        'public/currency',
                        'public/symbol',
                        'public/ticker',
                        'public/price/rate',
                        'public/trades',
                        'public/orderbook',
                        'public/candles',
                        'public/futures/info',
                        'public/futures/history/funding',
                        'public/futures/candles/index_price',
                        'public/futures/candles/mark_price',
                        'public/futures/candles/premium_index',
                        'public/futures/candles/open_interest',
                    ],
                },
                'private': {
                    'get': [
                        'spot/balance',
                        'spot/order',
                        'spot/order/{client_order_id}',
                        'spot/fee',
                        'spot/fee/{symbol}',
                        'spot/history/order',
                        'spot/history/trade',
                        'margin/account',
                        'margin/account/isolated/{symbol}',
                        'margin/order',
                        'margin/order/{client_order_id}',
                        'margin/history/order',
                        'margin/history/trade',
                        'futures/balance',
                        'futures/account',
                        'futures/account/isolated/{symbol}',
                        'futures/order',
                        'futures/order/{client_order_id}',
                        'futures/fee',
                        'futures/fee/{symbol}',
                        'futures/history/order',
                        'futures/history/trade',
                        'wallet/balance',
                        'wallet/crypto/address',
                        'wallet/crypto/address/recent-deposit',
                        'wallet/crypto/address/recent-withdraw',
                        'wallet/crypto/address/check-mine',
                        'wallet/transactions',
                        'wallet/crypto/check-offchain-available',
                        'wallet/crypto/fee/estimate',
                        'sub-account',
                        'sub-account/acl',
                        'sub-account/balance/{subAccID}',
                        'sub-account/crypto/address/{subAccID}/{currency}',
                    ],
                    'post': [
                        'spot/order',
                        'margin/order',
                        'futures/order',
                        'wallet/convert',
                        'wallet/crypto/withdraw',
                        'wallet/transfer',
                        'sub-account/freeze',
                        'sub-account/activate',
                        'sub-account/transfer',
                        'sub-account/acl',
                    ],
                    'patch': [
                        'spot/order/{client_order_id}',
                        'margin/order/{client_order_id}',
                        'futures/order/{client_order_id}',
                    ],
                    'delete': [
                        'spot/order',
                        'spot/order/{client_order_id}',
                        'margin/position',
                        'margin/position/isolated/{symbol}',
                        'margin/order',
                        'margin/order/{client_order_id}',
                        'futures/position',
                        'futures/position/isolated/{symbol}',
                        'futures/order',
                        'futures/order/{client_order_id}',
                        'wallet/crypto/withdraw/{id}',
                    ],
                    'put': [
                        'margin/account/isolated/{symbol}',
                        'futures/account/isolated/{symbol}',
                        'wallet/crypto/withdraw/{id}',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'tierBased': True,
                    'percentage': True,
                    'taker': self.parse_number('0.0009'),
                    'maker': self.parse_number('0.0009'),
                    'tiers': {
                        'maker': [
                            [self.parse_number('0'), self.parse_number('0.0009')],
                            [self.parse_number('10'), self.parse_number('0.0007')],
                            [self.parse_number('100'), self.parse_number('0.0006')],
                            [self.parse_number('500'), self.parse_number('0.0005')],
                            [self.parse_number('1000'), self.parse_number('0.0003')],
                            [self.parse_number('5000'), self.parse_number('0.0002')],
                            [self.parse_number('10000'), self.parse_number('0.0001')],
                            [self.parse_number('20000'), self.parse_number('0')],
                            [self.parse_number('50000'), self.parse_number('-0.0001')],
                            [self.parse_number('100000'), self.parse_number('-0.0001')],
                        ],
                        'taker': [
                            [self.parse_number('0'), self.parse_number('0.0009')],
                            [self.parse_number('10'), self.parse_number('0.0008')],
                            [self.parse_number('100'), self.parse_number('0.0007')],
                            [self.parse_number('500'), self.parse_number('0.0007')],
                            [self.parse_number('1000'), self.parse_number('0.0006')],
                            [self.parse_number('5000'), self.parse_number('0.0006')],
                            [self.parse_number('10000'), self.parse_number('0.0005')],
                            [self.parse_number('20000'), self.parse_number('0.0004')],
                            [self.parse_number('50000'), self.parse_number('0.0003')],
                            [self.parse_number('100000'), self.parse_number('0.0002')],
                        ],
                    },
                },
            },
            'timeframes': {
                '1m': 'M1',
                '3m': 'M3',
                '5m': 'M5',
                '15m': 'M15',
                '30m': 'M30',  # default
                '1h': 'H1',
                '4h': 'H4',
                '1d': 'D1',
                '1w': 'D7',
                '1M': '1M',
            },
            'exceptions': {
                'exact': {
                    '429': RateLimitExceeded,
                    '500': ExchangeError,
                    '503': ExchangeNotAvailable,
                    '504': ExchangeNotAvailable,
                    '600': PermissionDenied,
                    '800': ExchangeError,
                    '1002': AuthenticationError,
                    '1003': PermissionDenied,
                    '1004': AuthenticationError,
                    '1005': AuthenticationError,
                    '2001': BadSymbol,
                    '2002': BadRequest,
                    '2003': BadRequest,
                    '2010': BadRequest,
                    '2011': BadRequest,
                    '2012': BadRequest,
                    '2020': BadRequest,
                    '2022': BadRequest,
                    '10001': BadRequest,
                    '10021': AccountSuspended,
                    '10022': BadRequest,
                    '20001': InsufficientFunds,
                    '20002': OrderNotFound,
                    '20003': ExchangeError,
                    '20004': ExchangeError,
                    '20005': ExchangeError,
                    '20006': ExchangeError,
                    '20007': ExchangeError,
                    '20008': InvalidOrder,
                    '20009': InvalidOrder,
                    '20010': OnMaintenance,
                    '20011': ExchangeError,
                    '20012': ExchangeError,
                    '20014': ExchangeError,
                    '20016': ExchangeError,
                    '20031': ExchangeError,
                    '20032': ExchangeError,
                    '20033': ExchangeError,
                    '20034': ExchangeError,
                    '20040': ExchangeError,
                    '20041': ExchangeError,
                    '20042': ExchangeError,
                    '20043': ExchangeError,
                    '20044': PermissionDenied,
                    '20045': ExchangeError,
                    '20080': ExchangeError,
                    '21001': ExchangeError,
                    '21003': AccountSuspended,
                    '21004': AccountSuspended,
                },
                'broad': {},
            },
            'options': {
                'networks': {
                    'ETH': 'USDT20',
                    'ERC20': 'USDT20',
                    'TRX': 'USDTRX',
                    'TRC20': 'USDTRX',
                    'OMNI': 'USDT',
                },
                'accountsByType': {
                    'spot': 'spot',
                    'wallet': 'wallet',
                    'derivatives': 'derivatives',
                },
            },
        })

    def nonce(self):
        return self.milliseconds()

    def fetch_markets(self, params={}):
        response = self.publicGetPublicSymbol(params)
        #
        #     {
        #         "AAVEUSDT_PERP":{
        #             "type":"futures",
        #             "expiry":null,
        #             "underlying":"AAVE",
        #             "base_currency":null,
        #             "quote_currency":"USDT",
        #             "quantity_increment":"0.01",
        #             "tick_size":"0.001",
        #             "take_rate":"0.0005",
        #             "make_rate":"0.0002",
        #             "fee_currency":"USDT",
        #             "margin_trading":true,
        #             "max_initial_leverage":"50.00"
        #         },
        #         "MANAUSDT":{
        #             "type":"spot",
        #             "base_currency":"MANA",
        #             "quote_currency":"USDT",
        #             "quantity_increment":"1",
        #             "tick_size":"0.0000001",
        #             "take_rate":"0.0025",
        #             "make_rate":"0.001",
        #             "fee_currency":"USDT",
        #             "margin_trading":true,
        #             "max_initial_leverage":"5.00"
        #         },
        #     }
        #
        result = []
        ids = list(response.keys())
        for i in range(0, len(ids)):
            id = ids[i]
            market = self.safe_value(response, id)
            marketType = self.safe_string(market, 'type')
            expiry = self.safe_integer(market, 'expiry')
            contract = (marketType == 'futures')
            spot = (marketType == 'spot')
            marginTrading = self.safe_value(market, 'margin_trading', False)
            margin = spot and marginTrading
            future = (expiry is not None)
            swap = (contract and not future)
            option = False
            baseId = self.safe_string_2(market, 'base_currency', 'underlying')
            quoteId = self.safe_string(market, 'quote_currency')
            feeCurrencyId = self.safe_string(market, 'fee_currency')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            feeCurrency = self.safe_currency_code(feeCurrencyId)
            settleId = None
            settle = None
            symbol = base + '/' + quote
            type = 'spot'
            contractSize = None
            linear = None
            inverse = None
            if contract:
                contractSize = self.parse_number('1')
                settleId = feeCurrencyId
                settle = self.safe_currency_code(settleId)
                linear = ((quote is not None) and (quote == settle))
                inverse = not linear
                symbol = symbol + ':' + settle
                if future:
                    symbol = symbol + '-' + expiry
                    type = 'future'
                else:
                    type = 'swap'
            lotString = self.safe_string(market, 'quantity_increment')
            stepString = self.safe_string(market, 'tick_size')
            lot = self.parse_number(lotString)
            step = self.parse_number(stepString)
            taker = self.safe_number(market, 'take_rate')
            maker = self.safe_number(market, 'make_rate')
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'settle': settle,
                'baseId': baseId,
                'quoteId': quoteId,
                'settleId': settleId,
                'type': type,
                'spot': spot,
                'margin': margin,
                'swap': swap,
                'future': future,
                'option': option,
                'contract': contract,
                'linear': linear,
                'inverse': inverse,
                'taker': taker,
                'maker': maker,
                'contractSize': contractSize,
                'active': True,
                'expiry': expiry,
                'expiryDatetime': None,
                'strike': None,
                'optionType': None,
                'feeCurrency': feeCurrency,
                'precision': {
                    'price': step,
                    'amount': lot,
                },
                'limits': {
                    'leverage': {
                        'min': 1,
                        'max': self.safe_number(market, 'max_initial_leverage', 1),
                    },
                    'amount': {
                        'min': lot,
                        'max': None,
                    },
                    'price': {
                        'min': step,
                        'max': None,
                    },
                    'cost': {
                        'min': self.parse_number(Precise.string_mul(lotString, stepString)),
                        'max': None,
                    },
                },
                'info': market,
            })
        return result

    def fetch_currencies(self, params={}):
        response = self.publicGetPublicCurrency(params)
        #
        #     {
        #       "WEALTH": {
        #         "full_name": "ConnectWealth",
        #         "payin_enabled": False,
        #         "payout_enabled": False,
        #         "transfer_enabled": True,
        #         "precision_transfer": "0.001",
        #         "networks": [
        #           {
        #             "network": "ETH",
        #             "protocol": "ERC20",
        #             "default": True,
        #             "payin_enabled": False,
        #             "payout_enabled": False,
        #             "precision_payout": "0.001",
        #             "payout_fee": "0.016800000000",
        #             "payout_is_payment_id": False,
        #             "payin_payment_id": False,
        #             "payin_confirmations": "2"
        #           }
        #         ]
        #       }
        #     }
        #
        result = {}
        currencies = list(response.keys())
        for i in range(0, len(currencies)):
            currencyId = currencies[i]
            code = self.safe_currency_code(currencyId)
            entry = response[currencyId]
            name = self.safe_string(entry, 'full_name')
            precision = self.safe_number(entry, 'precision_transfer')
            payinEnabled = self.safe_value(entry, 'payin_enabled', False)
            payoutEnabled = self.safe_value(entry, 'payout_enabled', False)
            transferEnabled = self.safe_value(entry, 'transfer_enabled', False)
            active = payinEnabled and payoutEnabled and transferEnabled
            rawNetworks = self.safe_value(entry, 'networks', [])
            networks = {}
            fee = None
            for j in range(0, len(rawNetworks)):
                rawNetwork = rawNetworks[j]
                networkId = self.safe_string(rawNetwork, 'protocol')
                if len(networkId) == 0:
                    networkId = self.safe_string(rawNetwork, 'network')
                network = self.safe_network(networkId)
                fee = self.safe_number(rawNetwork, 'payout_fee')
                precision = self.safe_number(rawNetwork, 'precision_payout')
                payinEnabledNetwork = self.safe_value(entry, 'payin_enabled', False)
                payoutEnabledNetwork = self.safe_value(entry, 'payout_enabled', False)
                transferEnabledNetwork = self.safe_value(entry, 'transfer_enabled', False)
                active = payinEnabledNetwork and payoutEnabledNetwork and transferEnabledNetwork
                networks[network] = {
                    'info': rawNetwork,
                    'id': networkId,
                    'network': network,
                    'fee': fee,
                    'active': active,
                    'precision': precision,
                    'limits': {
                        'withdraw': {
                            'min': None,
                            'max': None,
                        },
                    },
                }
            networksKeys = list(networks.keys())
            networksLength = len(networksKeys)
            result[code] = {
                'info': entry,
                'code': code,
                'id': currencyId,
                'precision': precision,
                'name': name,
                'active': active,
                'networks': networks,
                'fee': fee if (networksLength <= 1) else None,
                'limits': {
                    'amount': {
                        'min': None,
                        'max': None,
                    },
                },
            }
        return result

    def safe_network(self, networkId):
        if networkId is None:
            return None
        else:
            return networkId.upper()

    def fetch_deposit_address(self, code, params={}):
        self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
        }
        network = self.safe_string_upper(params, 'network')
        if (network is not None) and (code == 'USDT'):
            networks = self.safe_value(self.options, 'networks')
            parsedNetwork = self.safe_string(networks, network)
            if parsedNetwork is not None:
                request['currency'] = parsedNetwork
            params = self.omit(params, 'network')
        response = self.privateGetWalletCryptoAddress(self.extend(request, params))
        #
        #  [{"currency":"ETH","address":"0xd0d9aea60c41988c3e68417e2616065617b7afd3"}]
        #
        firstAddress = self.safe_value(response, 0)
        address = self.safe_string(firstAddress, 'address')
        currencyId = self.safe_string(firstAddress, 'currency')
        tag = self.safe_string(firstAddress, 'payment_id')
        parsedCode = self.safe_currency_code(currencyId)
        return {
            'info': response,
            'address': address,
            'tag': tag,
            'code': parsedCode,
            'network': None,
        }

    def parse_balance(self, response):
        result = {'info': response}
        for i in range(0, len(response)):
            entry = response[i]
            currencyId = self.safe_string(entry, 'currency')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['free'] = self.safe_string(entry, 'available')
            account['used'] = self.safe_string(entry, 'reserved')
            result[code] = account
        return self.safe_balance(result)

    def fetch_balance(self, params={}):
        type = self.safe_string_lower(params, 'type', 'spot')
        params = self.omit(params, ['type'])
        accountsByType = self.safe_value(self.options, 'accountsByType', {})
        account = self.safe_string(accountsByType, type)
        response = None
        if account == 'wallet':
            response = self.privateGetWalletBalance(params)
        elif account == 'spot':
            response = self.privateGetSpotBalance(params)
        elif account == 'derivatives':
            response = self.privateGetFuturesBalance(params)
        else:
            keys = list(accountsByType.keys())
            raise BadRequest(self.id + ' fetchBalance() type parameter must be one of ' + ', '.join(keys))
        #
        #     [
        #       {
        #         "currency": "PAXG",
        #         "available": "0",
        #         "reserved": "0",
        #         "reserved_margin": "0",
        #       },
        #       ...
        #     ]
        #
        return self.parse_balance(response)

    def fetch_ticker(self, symbol, params={}):
        response = self.fetch_tickers([symbol], params)
        return self.safe_value(response, symbol)

    def fetch_tickers(self, symbols=None, params={}):
        self.load_markets()
        request = {}
        if symbols is not None:
            marketIds = self.market_ids(symbols)
            delimited = ','.join(marketIds)
            request['symbols'] = delimited
        response = self.publicGetPublicTicker(self.extend(request, params))
        #
        #     {
        #       "BTCUSDT": {
        #         "ask": "63049.06",
        #         "bid": "63046.41",
        #         "last": "63048.36",
        #         "low": "62010.00",
        #         "high": "66657.99",
        #         "open": "64839.75",
        #         "volume": "15272.13278",
        #         "volume_quote": "976312127.6277998",
        #         "timestamp": "2021-10-22T04:25:47.573Z"
        #       }
        #     }
        #
        result = {}
        keys = list(response.keys())
        for i in range(0, len(keys)):
            marketId = keys[i]
            market = self.safe_market(marketId)
            symbol = market['symbol']
            entry = response[marketId]
            result[symbol] = self.parse_ticker(entry, market)
        return self.filter_by_array(result, 'symbol', symbols)

    def parse_ticker(self, ticker, market=None):
        #
        #     {
        #       "ask": "62756.01",
        #       "bid": "62754.09",
        #       "last": "62755.87",
        #       "low": "62010.00",
        #       "high": "66657.99",
        #       "open": "65089.27",
        #       "volume": "16719.50366",
        #       "volume_quote": "1063422878.8156828",
        #       "timestamp": "2021-10-22T07:29:14.585Z"
        #     }
        #
        timestamp = self.parse8601(ticker['timestamp'])
        symbol = self.safe_symbol(None, market)
        baseVolume = self.safe_number(ticker, 'volume')
        quoteVolume = self.safe_number(ticker, 'volume_quote')
        open = self.safe_number(ticker, 'open')
        last = self.safe_number(ticker, 'last')
        vwap = self.vwap(baseVolume, quoteVolume)
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_number(ticker, 'high'),
            'low': self.safe_number(ticker, 'low'),
            'bid': self.safe_number(ticker, 'bid'),
            'bidVolume': None,
            'ask': self.safe_number(ticker, 'ask'),
            'askVolume': None,
            'vwap': vwap,
            'open': open,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': None,
            'average': None,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }, market)

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        self.load_markets()
        market = None
        request = {}
        if symbol is not None:
            market = self.market(symbol)
            # symbol is optional for hitbtc fetchTrades
            request['symbols'] = market['id']
        if limit is not None:
            request['limit'] = limit
        if since is not None:
            request['since'] = since
        response = self.publicGetPublicTrades(self.extend(request, params))
        marketIds = list(response.keys())
        trades = []
        for i in range(0, len(marketIds)):
            marketId = marketIds[i]
            market = self.market(marketId)
            rawTrades = response[marketId]
            parsed = self.parse_trades(rawTrades, market)
            trades = self.array_concat(trades, parsed)
        return trades

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        market = None
        request = {}
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        if limit is not None:
            request['limit'] = limit
        if since is not None:
            request['since'] = since
        response = self.privateGetSpotHistoryTrade(self.extend(request, params))
        return self.parse_trades(response, market, since, limit)

    def parse_trade(self, trade, market=None):
        # createMarketOrder
        #
        #  {      fee: "0.0004644",
        #           id:  386394956,
        #        price: "0.4644",
        #     quantity: "1",
        #    timestamp: "2018-10-25T16:41:44.780Z"}
        #
        # fetchTrades
        #
        # {id: 974786185,
        #   price: '0.032462',
        #   qty: '0.3673',
        #   side: 'buy',
        #   timestamp: '2020-10-16T12:57:39.846Z'}
        #
        # fetchMyTrades
        #
        # {id: 277210397,
        #   clientOrderId: '6e102f3e7f3f4e04aeeb1cdc95592f1a',
        #   orderId: 28102855393,
        #   symbol: 'ETHBTC',
        #   side: 'sell',
        #   quantity: '0.002',
        #   price: '0.073365',
        #   fee: '0.000000147',
        #   timestamp: '2018-04-28T18:39:55.345Z',
        #   taker: True}
        #
        timestamp = self.parse8601(trade['timestamp'])
        marketId = self.safe_string(trade, 'symbol')
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        fee = None
        feeCost = self.safe_number(trade, 'fee')
        taker = self.safe_value(trade, 'taker')
        takerOrMaker = None
        if taker is not None:
            takerOrMaker = 'taker' if taker else 'maker'
        if feeCost is not None:
            info = self.safe_value(market, 'info', {})
            feeCurrency = self.safe_string(info, 'fee_currency')
            feeCurrencyCode = self.safe_currency_code(feeCurrency)
            fee = {
                'cost': feeCost,
                'currency': feeCurrencyCode,
            }
        # we use clientOrderId as the order id with self exchange intentionally
        # because most of their endpoints will require clientOrderId
        # explained here: https://github.com/ccxt/ccxt/issues/5674
        orderId = self.safe_string(trade, 'clientOrderId')
        priceString = self.safe_string(trade, 'price')
        amountString = self.safe_string_2(trade, 'quantity', 'qty')
        price = self.parse_number(priceString)
        amount = self.parse_number(amountString)
        cost = self.parse_number(Precise.string_mul(priceString, amountString))
        side = self.safe_string(trade, 'side')
        id = self.safe_string(trade, 'id')
        return {
            'info': trade,
            'id': id,
            'order': orderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'type': None,
            'side': side,
            'takerOrMaker': takerOrMaker,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    def fetch_transactions_helper(self, types, code, since, limit, params):
        self.load_markets()
        request = {
            'types': types,
        }
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['currencies'] = currency['id']
        if since is not None:
            request['from'] = self.iso8601(since)
        if limit is not None:
            request['limit'] = limit
        response = self.privateGetWalletTransactions(self.extend(request, params))
        #
        #     [
        #       {
        #         "id": "101609495",
        #         "created_at": "2018-03-06T22:05:06.507Z",
        #         "updated_at": "2018-03-06T22:11:45.03Z",
        #         "status": "SUCCESS",
        #         "type": "DEPOSIT",
        #         "subtype": "BLOCKCHAIN",
        #         "native": {
        #           "tx_id": "e20b0965-4024-44d0-b63f-7fb8996a6706",
        #           "index": "881652766",
        #           "currency": "ETH",
        #           "amount": "0.01418088",
        #           "hash": "d95dbbff3f9234114f1211ab0ba2a94f03f394866fd5749d74a1edab80e6c5d3",
        #           "address": "0xd9259302c32c0a0295d86a39185c9e14f6ba0a0d",
        #           "confirmations": "20",
        #           "senders": [
        #             "0x243bec9256c9a3469da22103891465b47583d9f1"
        #           ]
        #         }
        #       }
        #     ]
        #
        return self.parse_transactions(response, currency, since, limit, params)

    def parse_transaction_status(self, status):
        statuses = {
            'PENDING': 'pending',
            'FAILED': 'failed',
            'SUCCESS': 'ok',
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction_type(self, type):
        types = {
            'DEPOSIT': 'deposit',
            'WITHDRAW': 'withdrawal',
        }
        return self.safe_string(types, type, type)

    def parse_transaction(self, transaction, currency=None):
        #
        #     {
        #       "id": "101609495",
        #       "created_at": "2018-03-06T22:05:06.507Z",
        #       "updated_at": "2018-03-06T22:11:45.03Z",
        #       "status": "SUCCESS",
        #       "type": "DEPOSIT",
        #       "subtype": "BLOCKCHAIN",
        #       "native": {
        #         "tx_id": "e20b0965-4024-44d0-b63f-7fb8996a6706",
        #         "index": "881652766",
        #         "currency": "ETH",
        #         "amount": "0.01418088",
        #         "hash": "d95dbbff3f9234114f1211ab0ba2a94f03f394866fd5749d74a1edab80e6c5d3",
        #         "address": "0xd9259302c32c0a0295d86a39185c9e14f6ba0a0d",
        #         "confirmations": "20",
        #         "senders": [
        #           "0x243bec9256c9a3469da22103891465b47583d9f1"
        #         ]
        #       }
        #     }
        #
        #     {
        #       "id": "102703545",
        #       "created_at": "2018-03-30T21:39:17.854Z",
        #       "updated_at": "2018-03-31T00:23:19.067Z",
        #       "status": "SUCCESS",
        #       "type": "WITHDRAW",
        #       "subtype": "BLOCKCHAIN",
        #       "native": {
        #         "tx_id": "5ecd7a85-ce5d-4d52-a916-b8b755e20926",
        #         "index": "918286359",
        #         "currency": "OMG",
        #         "amount": "2.45",
        #         "fee": "1.22",
        #         "hash": "0x1c621d89e7a0841342d5fb3b3587f60b95351590161e078c4a1daee353da4ca9",
        #         "address": "0x50227da7644cea0a43258a2e2d7444d01b43dcca",
        #         "confirmations": "0"
        #       }
        #     }
        #
        id = self.safe_string(transaction, 'id')
        timestamp = self.parse8601(self.safe_string(transaction, 'created_at'))
        updated = self.parse8601(self.safe_string(transaction, 'updated_at'))
        type = self.parse_transaction_type(self.safe_string(transaction, 'type'))
        status = self.parse_transaction_status(self.safe_string(transaction, 'status'))
        native = self.safe_value(transaction, 'native')
        currencyId = self.safe_string(native, 'currency')
        code = self.safe_currency_code(currencyId)
        txhash = self.safe_string(native, 'hash')
        address = self.safe_string(native, 'address')
        addressTo = address
        tag = self.safe_string(native, 'payment_id')
        tagTo = tag
        sender = self.safe_value(native, 'senders')
        addressFrom = self.safe_string(sender, 0)
        amount = self.safe_number(native, 'amount')
        fee = None
        feeCost = self.safe_number(native, 'fee')
        if feeCost is not None:
            fee = {
                'code': code,
                'cost': feeCost,
            }
        return {
            'info': transaction,
            'id': id,
            'txid': txhash,
            'code': code,
            'amount': amount,
            'network': None,
            'address': address,
            'addressFrom': addressFrom,
            'addressTo': addressTo,
            'tag': tag,
            'tagFrom': None,
            'tagTo': tagTo,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'updated': updated,
            'status': status,
            'type': type,
            'fee': fee,
        }

    def fetch_transactions(self, code=None, since=None, limit=None, params={}):
        return self.fetch_transactions_helper('DEPOSIT,WITHDRAW', code, since, limit, params)

    def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        return self.fetch_transactions_helper('DEPOSIT', code, since, limit, params)

    def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        return self.fetch_transactions_helper('WITHDRAW', code, since, limit, params)

    def fetch_order_books(self, symbols=None, limit=None, params={}):
        self.load_markets()
        request = {}
        if symbols is not None:
            marketIds = self.market_ids(symbols)
            request['symbols'] = ','.join(marketIds)
        if limit is not None:
            request['depth'] = limit
        response = self.publicGetPublicOrderbook(self.extend(request, params))
        result = {}
        marketIds = list(response.keys())
        for i in range(0, len(marketIds)):
            marketId = marketIds[i]
            orderbook = response[marketId]
            symbol = self.safe_symbol(marketId)
            timestamp = self.parse8601(self.safe_string(orderbook, 'timestamp'))
            result[symbol] = self.parse_order_book(response[marketId], symbol, timestamp, 'bid', 'ask')
        return result

    def fetch_order_book(self, symbol, limit=None, params={}):
        result = self.fetch_order_books([symbol], limit, params)
        return result[symbol]

    def parse_trading_fee(self, fee, market=None):
        #
        #     {
        #         "symbol":"ARVUSDT",  # returned from fetchTradingFees only
        #         "take_rate":"0.0009",
        #         "make_rate":"0.0009"
        #     }
        #
        taker = self.safe_number(fee, 'take_rate')
        maker = self.safe_number(fee, 'make_rate')
        marketId = self.safe_string(fee, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        return {
            'info': fee,
            'symbol': symbol,
            'taker': taker,
            'maker': maker,
        }

    def fetch_trading_fee(self, symbol, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbol': market['id'],
        }
        response = self.privateGetSpotFeeSymbol(self.extend(request, params))
        #
        #     {
        #         "take_rate":"0.0009",
        #         "make_rate":"0.0009"
        #     }
        #
        return self.parse_trading_fee(response, market)

    def fetch_trading_fees(self, symbols=None, params={}):
        self.load_markets()
        response = self.privateGetSpotFee(params)
        #
        #     [
        #         {
        #             "symbol":"ARVUSDT",
        #             "take_rate":"0.0009",
        #             "make_rate":"0.0009"
        #         }
        #     ]
        #
        result = {}
        for i in range(0, len(response)):
            fee = self.parse_trading_fee(response[i])
            symbol = fee['symbol']
            result[symbol] = fee
        return result

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'symbols': market['id'],
            'period': self.timeframes[timeframe],
        }
        if since is not None:
            request['from'] = self.iso8601(since)
        if limit is not None:
            request['limit'] = limit
        response = self.publicGetPublicCandles(self.extend(request, params))
        #
        #     {
        #       "ETHUSDT": [
        #         {
        #           "timestamp": "2021-10-25T07:38:00.000Z",
        #           "open": "4173.391",
        #           "close": "4170.923",
        #           "min": "4170.923",
        #           "max": "4173.986",
        #           "volume": "0.1879",
        #           "volume_quote": "784.2517846"
        #         }
        #       ]
        #     }
        #
        ohlcvs = self.safe_value(response, market['id'])
        return self.parse_ohlcvs(ohlcvs, market, timeframe, since, limit)

    def parse_ohlcv(self, ohlcv, market=None):
        #
        #     {
        #         "timestamp":"2015-08-20T19:01:00.000Z",
        #         "open":"0.006",
        #         "close":"0.006",
        #         "min":"0.006",
        #         "max":"0.006",
        #         "volume":"0.003",
        #         "volume_quote":"0.000018"
        #     }
        #
        return [
            self.parse8601(self.safe_string(ohlcv, 'timestamp')),
            self.safe_number(ohlcv, 'open'),
            self.safe_number(ohlcv, 'max'),
            self.safe_number(ohlcv, 'min'),
            self.safe_number(ohlcv, 'close'),
            self.safe_number(ohlcv, 'volume'),
        ]

    def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        market = None
        request = {}
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        if since is not None:
            request['from'] = self.iso8601(since)
        if limit is not None:
            request['limit'] = limit
        response = self.privateGetSpotHistoryOrder(self.extend(request, params))
        parsed = self.parse_orders(response, market, since, limit)
        return self.filter_by_array(parsed, 'status', ['closed', 'canceled'], False)

    def fetch_order(self, id, symbol=None, params={}):
        self.load_markets()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        request = {
            'client_order_id': id,
        }
        response = self.privateGetSpotHistoryOrder(self.extend(request, params))
        #
        #     [
        #       {
        #         "id": "685965182082",
        #         "client_order_id": "B3CBm9uGg9oYQlw96bBSEt38-6gbgBO0",
        #         "symbol": "BTCUSDT",
        #         "side": "buy",
        #         "status": "new",
        #         "type": "limit",
        #         "time_in_force": "GTC",
        #         "quantity": "0.00010",
        #         "quantity_cumulative": "0",
        #         "price": "50000.00",
        #         "price_average": "0",
        #         "created_at": "2021-10-26T11:40:09.287Z",
        #         "updated_at": "2021-10-26T11:40:09.287Z"
        #       }
        #     ]
        #
        order = self.safe_value(response, 0)
        return self.parse_order(order, market)

    def fetch_order_trades(self, id, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        request = {
            'order_id': id,  # exchange assigned order id as oppose to the client order id
        }
        response = self.privateGetSpotHistoryTrade(self.extend(request, params))
        #
        #     [
        #       {
        #         "id": 1393448977,
        #         "order_id": 653496804534,
        #         "client_order_id": "065f6f0ff9d54547848454182263d7b4",
        #         "symbol": "DICEETH",
        #         "side": "buy",
        #         "quantity": "1.4",
        #         "price": "0.00261455",
        #         "fee": "0.000003294333",
        #         "timestamp": "2021-09-19T05:35:56.601Z",
        #         "taker": True
        #       }
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        market = None
        request = {}
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        response = self.privateGetSpotOrder(self.extend(request, params))
        #
        #     [
        #       {
        #         "id": "488953123149",
        #         "client_order_id": "103ad305301e4c3590045b13de15b36e",
        #         "symbol": "BTCUSDT",
        #         "side": "buy",
        #         "status": "new",
        #         "type": "limit",
        #         "time_in_force": "GTC",
        #         "quantity": "0.00001",
        #         "quantity_cumulative": "0",
        #         "price": "0.01",
        #         "post_only": False,
        #         "created_at": "2021-04-13T13:06:16.567Z",
        #         "updated_at": "2021-04-13T13:06:16.567Z"
        #       }
        #     ]
        #
        return self.parse_orders(response, market, since, limit)

    def fetch_open_order(self, id, symbol=None, params={}):
        self.load_markets()
        market = None
        if symbol is not None:
            market = self.market(symbol)
        request = {
            'client_order_id': id,
        }
        response = self.privateGetSpotOrderClientOrderId(self.extend(request, params))
        return self.parse_order(response, market)

    def cancel_all_orders(self, symbol=None, params={}):
        self.load_markets()
        market = None
        request = {}
        if symbol is not None:
            market = self.market(symbol)
            request['symbol'] = market['id']
        response = self.privateDeleteSpotOrder(self.extend(request, params))
        return self.parse_orders(response, market)

    def cancel_order(self, id, symbol=None, params={}):
        self.load_markets()
        market = None
        request = {
            'client_order_id': id,
        }
        if symbol is not None:
            market = self.market(symbol)
        response = self.privateDeleteSpotOrderClientOrderId(self.extend(request, params))
        return self.parse_order(response, market)

    def edit_order(self, id, symbol, type, side, amount, price=None, params={}):
        self.load_markets()
        market = None
        request = {
            'client_order_id': id,
            'quantity': self.amount_to_precision(symbol, amount),
        }
        if (type == 'limit') or (type == 'stopLimit'):
            if price is None:
                raise ExchangeError(self.id + ' limit order requires price')
            request['price'] = self.price_to_precision(symbol, price)
        if symbol is not None:
            market = self.market(symbol)
        response = self.privatePatchSpotOrderClientOrderId(self.extend(request, params))
        return self.parse_order(response, market)

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'type': type,
            'side': side,
            'quantity': self.amount_to_precision(symbol, amount),
            'symbol': market['id'],
        }
        if (type == 'limit') or (type == 'stopLimit'):
            if price is None:
                raise ExchangeError(self.id + ' limit order requires price')
            request['price'] = self.price_to_precision(symbol, price)
        response = self.privatePostSpotOrder(self.extend(request, params))
        return self.parse_order(response, market)

    def parse_order_status(self, status):
        statuses = {
            'new': 'open',
            'suspended': 'open',
            'partiallyFilled': 'open',
            'filled': 'closed',
            'canceled': 'canceled',
            'expired': 'failed',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order, market=None):
        #
        # limit
        #     {
        #       "id": 488953123149,
        #       "client_order_id": "103ad305301e4c3590045b13de15b36e",
        #       "symbol": "BTCUSDT",
        #       "side": "buy",
        #       "status": "new",
        #       "type": "limit",
        #       "time_in_force": "GTC",
        #       "quantity": "0.00001",
        #       "quantity_cumulative": "0",
        #       "price": "0.01",
        #       "price_average": "0.01",
        #       "post_only": False,
        #       "created_at": "2021-04-13T13:06:16.567Z",
        #       "updated_at": "2021-04-13T13:06:16.567Z"
        #     }
        #
        # market
        #     {
        #       "id": "685877626834",
        #       "client_order_id": "Yshl7G-EjaREyXQYaGbsmdtVbW-nzQwu",
        #       "symbol": "BTCUSDT",
        #       "side": "buy",
        #       "status": "filled",
        #       "type": "market",
        #       "time_in_force": "GTC",
        #       "quantity": "0.00010",
        #       "quantity_cumulative": "0.00010",
        #       "post_only": False,
        #       "created_at": "2021-10-26T08:55:55.1Z",
        #       "updated_at": "2021-10-26T08:55:55.1Z",
        #       "trades": [
        #         {
        #           "id": "1437229630",
        #           "position_id": "0",
        #           "quantity": "0.00010",
        #           "price": "62884.78",
        #           "fee": "0.005659630200",
        #           "timestamp": "2021-10-26T08:55:55.1Z",
        #           "taker": True
        #         }
        #       ]
        #     }
        #
        id = self.safe_string(order, 'client_order_id')
        # we use clientOrderId as the order id with self exchange intentionally
        # because most of their endpoints will require clientOrderId
        # explained here: https://github.com/ccxt/ccxt/issues/5674
        side = self.safe_string(order, 'side')
        type = self.safe_string(order, 'type')
        amount = self.safe_string(order, 'quantity')
        price = self.safe_string(order, 'price')
        average = self.safe_string(order, 'price_average')
        created = self.safe_string(order, 'created_at')
        timestamp = self.parse8601(created)
        updated = self.safe_string(order, 'updated_at')
        lastTradeTimestamp = None
        if updated != created:
            lastTradeTimestamp = self.parse8601(updated)
        filled = self.safe_string(order, 'quantity_cumulative')
        status = self.parse_order_status(self.safe_string(order, 'status'))
        marketId = self.safe_string(order, 'symbol')
        market = self.safe_market(marketId, market)
        symbol = market['symbol']
        postOnly = self.safe_value(order, 'post_only')
        timeInForce = self.safe_string(order, 'time_in_force')
        rawTrades = self.safe_value(order, 'trades')
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'symbol': symbol,
            'price': price,
            'amount': amount,
            'type': type,
            'side': side,
            'timeInForce': timeInForce,
            'postOnly': postOnly,
            'filled': filled,
            'remaining': None,
            'cost': None,
            'status': status,
            'average': average,
            'trades': rawTrades,
            'fee': None,
        }, market)

    def transfer(self, code, amount, fromAccount, toAccount, params={}):
        # account can be "spot", "wallet", or "derivatives"
        self.load_markets()
        currency = self.currency(code)
        requestAmount = self.currency_to_precision(code, amount)
        accountsByType = self.safe_value(self.options, 'accountsByType', {})
        fromAccount = fromAccount.lower()
        toAccount = toAccount.lower()
        fromId = self.safe_string(accountsByType, fromAccount)
        toId = self.safe_string(accountsByType, toAccount)
        keys = list(accountsByType.keys())
        if fromId is None:
            raise ArgumentsRequired(self.id + ' transfer() fromAccount argument must be one of ' + ', '.join(keys))
        if toId is None:
            raise ArgumentsRequired(self.id + ' transfer() toAccount argument must be one of ' + ', '.join(keys))
        if fromId == toId:
            raise BadRequest(self.id + ' transfer() fromAccount and toAccount arguments cannot be the same account')
        request = {
            'currency': currency['id'],
            'amount': requestAmount,
            'source': fromId,
            'destination': toId,
        }
        response = self.privatePostWalletTransfer(self.extend(request, params))
        # ['2db6ebab-fb26-4537-9ef8-1a689472d236']
        id = self.safe_string(response, 0)
        return {
            'info': response,
            'id': id,
            'timestamp': None,
            'datetime': None,
            'amount': self.parse_number(requestAmount),
            'currency': code,
            'fromAccount': fromAccount,
            'toAccount': toAccount,
            'status': None,
        }

    def convert_currency_network(self, code, amount, fromNetwork, toNetwork, params):
        self.load_markets()
        if code != 'USDT':
            raise ExchangeError(self.id + ' convertCurrencyNetwork() only supports USDT currently')
        networks = self.safe_value(self.options, 'networks', {})
        fromNetwork = fromNetwork.upper()
        toNetwork = toNetwork.upper()
        fromNetwork = self.safe_string(networks, fromNetwork)  # handle ETH>ERC20 alias
        toNetwork = self.safe_string(networks, toNetwork)  # handle ETH>ERC20 alias
        if fromNetwork == toNetwork:
            raise BadRequest(self.id + ' fromNetwork cannot be the same as toNetwork')
        if (fromNetwork is None) or (toNetwork is None):
            keys = list(networks.keys())
            raise ArgumentsRequired(self.id + ' convertCurrencyNetwork() requires a fromNetwork parameter and a toNetwork parameter, supported networks are ' + ', '.join(keys))
        request = {
            'from_currency': fromNetwork,
            'to_currency': toNetwork,
            'amount': self.currency_to_precision(code, amount),
        }
        response = self.privatePostWalletConvert(self.extend(request, params))
        # {"result":["587a1868-e62d-4d8e-b27c-dbdb2ee96149","e168df74-c041-41f2-b76c-e43e4fed5bc7"]}
        return {
            'info': response,
        }

    def withdraw(self, code, amount, address, tag=None, params={}):
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        self.load_markets()
        self.check_address(address)
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
            'amount': amount,
            'address': address,
        }
        if tag is not None:
            request['payment_id'] = tag
        networks = self.safe_value(self.options, 'networks', {})
        network = self.safe_string_upper(params, 'network')
        if (network is not None) and (code == 'USDT'):
            parsedNetwork = self.safe_string(networks, network)
            if parsedNetwork is not None:
                request['currency'] = parsedNetwork
            params = self.omit(params, 'network')
        response = self.privatePostWalletCryptoWithdraw(self.extend(request, params))
        # {"id":"084cfcd5-06b9-4826-882e-fdb75ec3625d"}
        id = self.safe_string(response, 'id')
        return {
            'info': response,
            'id': id,
        }

    def fetch_funding_rate_history(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        market = None
        request = {
            # all arguments are optional
            # 'symbols': Comma separated list of symbol codes,
            # 'sort': 'DESC' or 'ASC'
            # 'from': 'Datetime or Number',
            # 'till': 'Datetime or Number',
            # 'limit': 100,
            # 'offset': 0,
        }
        if symbol is not None:
            market = self.market(symbol)
            request['symbols'] = market['id']
        if since is not None:
            request['from'] = since
        if limit is not None:
            request['limit'] = limit
        response = self.publicGetPublicFuturesHistoryFunding(self.extend(request, params))
        #
        #    {
        #        "BTCUSDT_PERP": [
        #            {
        #                "timestamp": "2021-07-29T16:00:00.271Z",
        #                "funding_rate": "0.0001",
        #                "avg_premium_index": "0.000061858585213222",
        #                "next_funding_time": "2021-07-30T00:00:00.000Z",
        #                "interest_rate": "0.0001"
        #            },
        #            ...
        #        ],
        #        ...
        #    }
        #
        contracts = list(response.keys())
        rates = []
        for i in range(0, len(contracts)):
            marketId = contracts[i]
            market = self.safe_market(marketId)
            fundingRateData = response[marketId]
            for i in range(0, len(fundingRateData)):
                entry = fundingRateData[i]
                symbol = self.safe_symbol(market['symbol'])
                fundingRate = self.safe_number(entry, 'funding_rate')
                datetime = self.safe_string(entry, 'timestamp')
                rates.append({
                    'info': entry,
                    'symbol': symbol,
                    'fundingRate': fundingRate,
                    'timestamp': self.parse8601(datetime),
                    'datetime': datetime,
                })
        sorted = self.sort_by(rates, 'timestamp')
        return self.filter_by_symbol_since_limit(sorted, symbol, since, limit)

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        #
        #     {
        #       "error": {
        #         "code": 20001,
        #         "message": "Insufficient funds",
        #         "description": "Check that the funds are sufficient, given commissions"
        #       }
        #     }
        #
        #     {
        #       "error": {
        #         "code": "600",
        #         "message": "Action not allowed"
        #       }
        #     }
        #
        error = self.safe_value(response, 'error')
        errorCode = self.safe_string(error, 'code')
        if errorCode is not None:
            feedback = self.id + ' ' + body
            message = self.safe_string_2(error, 'message', 'description')
            self.throw_exactly_matched_exception(self.exceptions['exact'], errorCode, feedback)
            self.throw_broadly_matched_exception(self.exceptions['broad'], message, feedback)
            raise ExchangeError(feedback)

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        query = self.omit(params, self.extract_params(path))
        implodedPath = self.implode_params(path, params)
        url = self.urls['api'][api] + '/' + implodedPath
        getRequest = None
        keys = list(query.keys())
        queryLength = len(keys)
        headers = {
            'Content-Type': 'application/json',
        }
        if method == 'GET':
            if queryLength:
                getRequest = '?' + self.urlencode(query)
                url = url + getRequest
        else:
            body = self.json(params)
        if api == 'private':
            self.check_required_credentials()
            timestamp = str(self.nonce())
            payload = [method, '/api/3/' + implodedPath]
            if method == 'GET':
                if getRequest is not None:
                    payload.append(getRequest)
            else:
                payload.append(body)
            payload.append(timestamp)
            payloadString = ''.join(payload)
            signature = self.hmac(self.encode(payloadString), self.encode(self.secret), hashlib.sha256, 'hex')
            secondPayload = self.apiKey + ':' + signature + ':' + timestamp
            encoded = self.decode(self.string_to_base64(secondPayload))
            headers['Authorization'] = 'HS256 ' + encoded
        return {'url': url, 'method': method, 'body': body, 'headers': headers}
