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

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

from ccxt.async_support.base.exchange import Exchange
import hashlib
import math
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import 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.precise import Precise


class gateio(Exchange):

    def describe(self):
        return self.deep_extend(super(gateio, self).describe(), {
            'id': 'gateio',
            'name': 'Gate.io',
            'countries': ['KR'],
            'rateLimit': 10 / 3,  # 300 requests per second or 3.33ms
            'version': '4',
            'certified': True,
            'pro': True,
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/31784029-0313c702-b509-11e7-9ccc-bc0da6a0e435.jpg',
                'doc': 'https://www.gate.io/docs/apiv4/en/index.html',
                'www': 'https://gate.io/',
                'api': {
                    'public': 'https://api.gateio.ws/api/v4',
                    'private': 'https://api.gateio.ws/api/v4',
                },
                'referral': {
                    'url': 'https://www.gate.io/ref/2436035',
                    'discount': 0.2,
                },
            },
            'has': {
                'cancelOrder': True,
                'createMarketOrder': False,
                'createOrder': True,
                'fetchBalance': True,
                'fetchClosedOrders': True,
                'fetchCurrencies': True,
                'fetchDeposits': True,
                'fetchFundingRateHistory': True,
                'fetchIndexOHLCV': True,
                'fetchMarkets': True,
                'fetchMarkOHLCV': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchPremiumIndexOHLCV': False,
                'fetchTicker': True,
                'fetchTickers': True,
                'fetchTime': False,
                'fetchTrades': True,
                'fetchWithdrawals': True,
                'transfer': True,
                'withdraw': True,
            },
            'api': {
                'public': {
                    'spot': {
                        'get': {
                            'currencies': 1,
                            'currencies/{currency}': 1,
                            'currency_pairs': 1,
                            'currency_pairs/{currency_pair}': 1,
                            'tickers': 1,
                            'order_book': 1,
                            'trades': 1,
                            'candlesticks': 1,
                        },
                    },
                    'margin': {
                        'get': {
                            'currency_pairs': 1,
                            'currency_pairs/{currency_pair}': 1,
                            'cross/currencies': 1,
                            'cross/currencies/{currency}': 1,
                        },
                    },
                    'futures': {
                        'get': {
                            '{settle}/contracts': 1.5,
                            '{settle}/contracts/{contract}': 1.5,
                            '{settle}/order_book': 1.5,
                            '{settle}/trades': 1.5,
                            '{settle}/candlesticks': 1.5,
                            '{settle}/tickers': 1.5,
                            '{settle}/funding_rate': 1.5,
                            '{settle}/insurance': 1.5,
                            '{settle}/contract_stats': 1.5,
                            '{settle}/liq_orders': 1.5,
                        },
                    },
                    'delivery': {
                        'get': {
                            '{settle}/contracts': 1.5,
                            '{settle}/contracts/{contract}': 1.5,
                            '{settle}/order_book': 1.5,
                            '{settle}/trades': 1.5,
                            '{settle}/candlesticks': 1.5,
                            '{settle}/tickers': 1.5,
                            '{settle}/insurance': 1.5,
                        },
                    },
                },
                'private': {
                    'withdrawals': {
                        'post': {
                            '': 3000,  # 3000 = 10 seconds
                        },
                        'delete': {
                            '{withdrawal_id}': 300,
                        },
                    },
                    'wallet': {
                        'get': {
                            'deposit_address': 300,
                            'withdrawals': 300,
                            'deposits': 300,
                            'sub_account_transfers': 300,
                            'withdraw_status': 300,
                            'sub_account_balances': 300,
                            'fee': 300,
                        },
                        'post': {
                            'transfers': 300,
                            'sub_account_transfers': 300,
                        },
                    },
                    'spot': {
                        'get': {
                            'accounts': 1,
                            'open_orders': 1,
                            'orders': 1,
                            'orders/{order_id}': 1,
                            'my_trades': 1,
                            'price_orders': 1,
                            'price_orders/{order_id}': 1,
                        },
                        'post': {
                            'batch_orders': 1,
                            'orders': 1,
                            'cancel_batch_orders': 1,
                            'price_orders': 1,
                        },
                        'delete': {
                            'orders': 1,
                            'orders/{order_id}': 1,
                            'price_orders': 1,
                            'price_orders/{order_id}': 1,
                        },
                    },
                    'margin': {
                        'get': {
                            'account_book': 1.5,
                            'funding_accounts': 1.5,
                            'loans': 1.5,
                            'loans/{loan_id}': 1.5,
                            'loans/{loan_id}/repayment': 1.5,
                            'loan_records': 1.5,
                            'loan_records/{load_record_id}': 1.5,
                            'auto_repay': 1.5,
                            'transferable': 1.5,
                            'cross/accounts': 1.5,
                            'cross/account_book': 1.5,
                            'cross/loans': 1.5,
                            'cross/loans/{loan_id}': 1.5,
                            'cross/loans/repayments': 1.5,
                            'cross/transferable': 1.5,
                        },
                        'post': {
                            'loans': 1.5,
                            'merged_loans': 1.5,
                            'loans/{loan_id}/repayment': 1.5,
                            'auto_repay': 1.5,
                            'cross/loans': 1.5,
                            'cross/loans/repayments': 1.5,
                        },
                        'patch': {
                            'loans/{loan_id}': 1.5,
                            'loan_records/{loan_record_id}': 1.5,
                        },
                        'delete': {
                            'loans/{loan_id}': 1.5,
                        },
                    },
                    'futures': {
                        'get': {
                            '{settle}/accounts': 1.5,
                            '{settle}/account_book': 1.5,
                            '{settle}/positions': 1.5,
                            '{settle}/positions/{contract}': 1.5,
                            '{settle}/orders': 1.5,
                            '{settle}/orders/{order_id}': 1.5,
                            '{settle}/my_trades': 1.5,
                            '{settle}/position_close': 1.5,
                            '{settle}/liquidates': 1.5,
                            '{settle}/price_orders': 1.5,
                            '{settle}/price_orders/{order_id}': 1.5,
                        },
                        'post': {
                            '{settle}/positions/{contract}/margin': 1.5,
                            '{settle}/positions/{contract}/leverage': 1.5,
                            '{settle}/positions/{contract}/risk_limit': 1.5,
                            '{settle}/dual_mode': 1.5,
                            '{settle}/dual_comp/positions/{contract}': 1.5,
                            '{settle}/dual_comp/positions/{contract}/margin': 1.5,
                            '{settle}/dual_comp/positions/{contract}/leverage': 1.5,
                            '{settle}/dual_comp/positions/{contract}/risk_limit': 1.5,
                            '{settle}/orders': 1.5,
                            '{settle}/price_orders': 1.5,
                        },
                        'delete': {
                            '{settle}/orders': 1.5,
                            '{settle}/orders/{order_id}': 1.5,
                            '{settle}/price_orders': 1.5,
                            '{settle}/price_orders/{order_id}': 1.5,
                        },
                    },
                    'delivery': {
                        'get': {
                            '{settle}/accounts': 1.5,
                            '{settle}/account_book': 1.5,
                            '{settle}/positions': 1.5,
                            '{settle}/positions/{contract}': 1.5,
                            '{settle}/orders': 1.5,
                            '{settle}/orders/{order_id}': 1.5,
                            '{settle}/my_trades': 1.5,
                            '{settle}/position_close': 1.5,
                            '{settle}/liquidates': 1.5,
                            '{settle}/price_orders': 1.5,
                            '{settle}/price_orders/{order_id}': 1.5,
                        },
                        'post': {
                            '{settle}/positions/{contract}/margin': 1.5,
                            '{settle}/positions/{contract}/leverage': 1.5,
                            '{settle}/positions/{contract}/risk_limit': 1.5,
                            '{settle}/orders': 1.5,
                        },
                        'delete': {
                            '{settle}/orders': 1.5,
                            '{settle}/orders/{order_id}': 1.5,
                            '{settle}/price_orders': 1.5,
                            '{settle}/price_orders/{order_id}': 1.5,
                        },
                    },
                },
            },
            'timeframes': {
                '10s': '10s',
                '1m': '1m',
                '5m': '5m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1h',
                '4h': '4h',
                '8h': '8h',
                '1d': '1d',
                '7d': '7d',
            },
            # copied from gateiov2
            'commonCurrencies': {
                '88MPH': 'MPH',
                'BIFI': 'Bitcoin File',
                'BOX': 'DefiBox',
                'BTCBEAR': 'BEAR',
                'BTCBULL': 'BULL',
                'BYN': 'Beyond Finance',
                'GTC': 'Game.com',  # conflict with Gitcoin and Gastrocoin
                'GTC_HT': 'Game.com HT',
                'GTC_BSC': 'Game.com BSC',
                'HIT': 'HitChain',
                'MPH': 'Morpher',  # conflict with 88MPH
                'RAI': 'Rai Reflex Index',  # conflict with RAI Finance
                'SBTC': 'Super Bitcoin',
                'TNC': 'Trinity Network Credit',
                'TON': 'TONToken',
                'VAI': 'VAIOT',
            },
            'options': {
                'networks': {
                    'TRC20': 'TRX',
                    'ERC20': 'ETH',
                    'BEP20': 'BSC',
                },
                'accountsByType': {
                    'spot': 'spot',
                    'margin': 'margin',
                    'futures': 'futures',
                    'delivery': 'delivery',
                },
            },
            'fees': {
                'trading': {
                    'tierBased': True,
                    'feeSide': 'get',
                    'percentage': True,
                    'maker': self.parse_number('0.002'),
                    'taker': self.parse_number('0.002'),
                    'tiers': {
                        # volume is in BTC
                        'maker': [
                            [self.parse_number('0'), self.parse_number('0.002')],
                            [self.parse_number('1.5'), self.parse_number('0.00185')],
                            [self.parse_number('3'), self.parse_number('0.00175')],
                            [self.parse_number('6'), self.parse_number('0.00165')],
                            [self.parse_number('12.5'), self.parse_number('0.00155')],
                            [self.parse_number('25'), self.parse_number('0.00145')],
                            [self.parse_number('75'), self.parse_number('0.00135')],
                            [self.parse_number('200'), self.parse_number('0.00125')],
                            [self.parse_number('500'), self.parse_number('0.00115')],
                            [self.parse_number('1250'), self.parse_number('0.00105')],
                            [self.parse_number('2500'), self.parse_number('0.00095')],
                            [self.parse_number('3000'), self.parse_number('0.00085')],
                            [self.parse_number('6000'), self.parse_number('0.00075')],
                            [self.parse_number('11000'), self.parse_number('0.00065')],
                            [self.parse_number('20000'), self.parse_number('0.00055')],
                            [self.parse_number('40000'), self.parse_number('0.00055')],
                            [self.parse_number('75000'), self.parse_number('0.00055')],
                        ],
                        'taker': [
                            [self.parse_number('0'), self.parse_number('0.002')],
                            [self.parse_number('1.5'), self.parse_number('0.00195')],
                            [self.parse_number('3'), self.parse_number('0.00185')],
                            [self.parse_number('6'), self.parse_number('0.00175')],
                            [self.parse_number('12.5'), self.parse_number('0.00165')],
                            [self.parse_number('25'), self.parse_number('0.00155')],
                            [self.parse_number('75'), self.parse_number('0.00145')],
                            [self.parse_number('200'), self.parse_number('0.00135')],
                            [self.parse_number('500'), self.parse_number('0.00125')],
                            [self.parse_number('1250'), self.parse_number('0.00115')],
                            [self.parse_number('2500'), self.parse_number('0.00105')],
                            [self.parse_number('3000'), self.parse_number('0.00095')],
                            [self.parse_number('6000'), self.parse_number('0.00085')],
                            [self.parse_number('11000'), self.parse_number('0.00075')],
                            [self.parse_number('20000'), self.parse_number('0.00065')],
                            [self.parse_number('40000'), self.parse_number('0.00065')],
                            [self.parse_number('75000'), self.parse_number('0.00065')],
                        ],
                    },
                },
            },
            # https://www.gate.io/docs/apiv4/en/index.html#label-list
            'exceptions': {
                'INVALID_PARAM_VALUE': BadRequest,
                'INVALID_PROTOCOL': BadRequest,
                'INVALID_ARGUMENT': BadRequest,
                'INVALID_REQUEST_BODY': BadRequest,
                'MISSING_REQUIRED_PARAM': ArgumentsRequired,
                'BAD_REQUEST': BadRequest,
                'INVALID_CONTENT_TYPE': BadRequest,
                'NOT_ACCEPTABLE': BadRequest,
                'METHOD_NOT_ALLOWED': BadRequest,
                'NOT_FOUND': ExchangeError,
                'INVALID_CREDENTIALS': AuthenticationError,
                'INVALID_KEY': AuthenticationError,
                'IP_FORBIDDEN': AuthenticationError,
                'READ_ONLY': PermissionDenied,
                'INVALID_SIGNATURE': AuthenticationError,
                'MISSING_REQUIRED_HEADER': AuthenticationError,
                'REQUEST_EXPIRED': AuthenticationError,
                'ACCOUNT_LOCKED': AccountSuspended,
                'FORBIDDEN': PermissionDenied,
                'SUB_ACCOUNT_NOT_FOUND': ExchangeError,
                'SUB_ACCOUNT_LOCKED': AccountSuspended,
                'MARGIN_BALANCE_EXCEPTION': ExchangeError,
                'MARGIN_TRANSFER_FAILED': ExchangeError,
                'TOO_MUCH_FUTURES_AVAILABLE': ExchangeError,
                'FUTURES_BALANCE_NOT_ENOUGH': InsufficientFunds,
                'ACCOUNT_EXCEPTION': ExchangeError,
                'SUB_ACCOUNT_TRANSFER_FAILED': ExchangeError,
                'ADDRESS_NOT_USED': ExchangeError,
                'TOO_FAST': RateLimitExceeded,
                'WITHDRAWAL_OVER_LIMIT': ExchangeError,
                'API_WITHDRAW_DISABLED': ExchangeNotAvailable,
                'INVALID_WITHDRAW_ID': ExchangeError,
                'INVALID_WITHDRAW_CANCEL_STATUS': ExchangeError,
                'INVALID_PRECISION': InvalidOrder,
                'INVALID_CURRENCY': BadSymbol,
                'INVALID_CURRENCY_PAIR': BadSymbol,
                'POC_FILL_IMMEDIATELY': ExchangeError,
                'ORDER_NOT_FOUND': OrderNotFound,
                'ORDER_CLOSED': InvalidOrder,
                'ORDER_CANCELLED': InvalidOrder,
                'QUANTITY_NOT_ENOUGH': InvalidOrder,
                'BALANCE_NOT_ENOUGH': InsufficientFunds,
                'MARGIN_NOT_SUPPORTED': InvalidOrder,
                'MARGIN_BALANCE_NOT_ENOUGH': InsufficientFunds,
                'AMOUNT_TOO_LITTLE': InvalidOrder,
                'AMOUNT_TOO_MUCH': InvalidOrder,
                'REPEATED_CREATION': InvalidOrder,
                'LOAN_NOT_FOUND': OrderNotFound,
                'LOAN_RECORD_NOT_FOUND': OrderNotFound,
                'NO_MATCHED_LOAN': ExchangeError,
                'NOT_MERGEABLE': ExchangeError,
                'NO_CHANGE': ExchangeError,
                'REPAY_TOO_MUCH': ExchangeError,
                'TOO_MANY_CURRENCY_PAIRS': InvalidOrder,
                'TOO_MANY_ORDERS': InvalidOrder,
                'MIXED_ACCOUNT_TYPE': InvalidOrder,
                'AUTO_BORROW_TOO_MUCH': ExchangeError,
                'TRADE_RESTRICTED': InsufficientFunds,
                'USER_NOT_FOUND': ExchangeError,
                'CONTRACT_NO_COUNTER': ExchangeError,
                'CONTRACT_NOT_FOUND': BadSymbol,
                'RISK_LIMIT_EXCEEDED': ExchangeError,
                'INSUFFICIENT_AVAILABLE': InsufficientFunds,
                'LIQUIDATE_IMMEDIATELY': InvalidOrder,
                'LEVERAGE_TOO_HIGH': InvalidOrder,
                'LEVERAGE_TOO_LOW': InvalidOrder,
                'ORDER_NOT_OWNED': ExchangeError,
                'ORDER_FINISHED': ExchangeError,
                'POSITION_CROSS_MARGIN': ExchangeError,
                'POSITION_IN_LIQUIDATION': ExchangeError,
                'POSITION_IN_CLOSE': ExchangeError,
                'POSITION_EMPTY': InvalidOrder,
                'REMOVE_TOO_MUCH': ExchangeError,
                'RISK_LIMIT_NOT_MULTIPLE': ExchangeError,
                'RISK_LIMIT_TOO_HIGH': ExchangeError,
                'RISK_LIMIT_TOO_lOW': ExchangeError,
                'PRICE_TOO_DEVIATED': InvalidOrder,
                'SIZE_TOO_LARGE': InvalidOrder,
                'SIZE_TOO_SMALL': InvalidOrder,
                'PRICE_OVER_LIQUIDATION': InvalidOrder,
                'PRICE_OVER_BANKRUPT': InvalidOrder,
                'ORDER_POC_IMMEDIATE': InvalidOrder,
                'INCREASE_POSITION': InvalidOrder,
                'CONTRACT_IN_DELISTING': ExchangeError,
                'INTERNAL': ExchangeError,
                'SERVER_ERROR': ExchangeError,
                'TOO_BUSY': ExchangeNotAvailable,
            },
        })

    async def fetch_markets(self, params={}):
        response = await self.publicSpotGetCurrencyPairs(params)
        #
        #     {
        #       "id": "DEGO_USDT",
        #       "base": "DEGO",
        #       "quote": "USDT",
        #       "fee": "0.2",
        #       "min_quote_amount": "1",
        #       "amount_precision": "4",
        #       "precision": "4",
        #       "trade_status": "tradable",
        #       "sell_start": "0",
        #       "buy_start": "0"
        #     }
        #
        result = []
        for i in range(0, len(response)):
            entry = response[i]
            id = self.safe_string(entry, 'id')
            baseId = self.safe_string(entry, 'base')
            quoteId = self.safe_string(entry, 'quote')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = base + '/' + quote
            # Fee is in %, so divide by 100
            taker = self.safe_number(entry, 'fee') / 100
            maker = taker
            tradeStatus = self.safe_string(entry, 'trade_status')
            active = tradeStatus == 'tradable'
            amountPrecision = self.safe_string(entry, 'amount_precision')
            pricePrecision = self.safe_string(entry, 'precision')
            amountLimit = self.parse_precision(amountPrecision)
            priceLimit = self.parse_precision(pricePrecision)
            limits = {
                'amount': {
                    'min': self.parse_number(amountLimit),
                    'max': None,
                },
                'price': {
                    'min': self.parse_number(priceLimit),
                    'max': None,
                },
                'cost': {
                    'min': self.safe_number(entry, 'min_quote_amount'),
                    'max': None,
                },
            }
            precision = {
                'amount': int(amountPrecision),
                'price': int(pricePrecision),
            }
            result.append({
                'info': entry,
                'id': id,
                'baseId': baseId,
                'quoteId': quoteId,
                'base': base,
                'quote': quote,
                'symbol': symbol,
                'limits': limits,
                'precision': precision,
                'active': active,
                'maker': maker,
                'taker': taker,
            })
        return result

    async def fetch_currencies(self, params={}):
        response = await self.publicSpotGetCurrencies(params)
        #
        #     {
        #       "currency": "BCN",
        #       "delisted": False,
        #       "withdraw_disabled": True,
        #       "withdraw_delayed": False,
        #       "deposit_disabled": True,
        #       "trade_disabled": False
        #     }
        #
        result = {}
        # TODO: remove magic constants
        amountPrecision = 6
        for i in range(0, len(response)):
            entry = response[i]
            currencyId = self.safe_string(entry, 'currency')
            code = self.safe_currency_code(currencyId)
            delisted = self.safe_value(entry, 'delisted')
            withdraw_disabled = self.safe_value(entry, 'withdraw_disabled')
            deposit_disabled = self.safe_value(entry, 'disabled_disabled')
            trade_disabled = self.safe_value(entry, 'trade_disabled')
            active = not (delisted and withdraw_disabled and deposit_disabled and trade_disabled)
            result[code] = {
                'id': currencyId,
                'name': None,
                'code': code,
                'precision': amountPrecision,
                'info': entry,
                'active': active,
                'fee': None,
                'fees': [],
                'limits': self.limits,
            }
        return result

    async def fetch_network_deposit_address(self, code, params={}):
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
        }
        response = await self.privateWalletGetDepositAddress(self.extend(request, params))
        addresses = self.safe_value(response, 'multichain_addresses')
        currencyId = self.safe_string(response, 'currency')
        code = self.safe_currency_code(currencyId)
        result = {}
        for i in range(0, len(addresses)):
            entry = addresses[i]
            #
            #     {
            #       "chain": "ETH",
            #       "address": "0x359a697945E79C7e17b634675BD73B33324E9408",
            #       "payment_id": "",
            #       "payment_name": "",
            #       "obtain_failed": "0"
            #     }
            #
            obtainFailed = self.safe_integer(entry, 'obtain_failed')
            if obtainFailed:
                continue
            network = self.safe_string(entry, 'chain')
            address = self.safe_string(entry, 'address')
            tag = self.safe_string(entry, 'payment_id')
            tagLength = len(tag)
            tag = tag if tagLength else None
            result[network] = {
                'info': entry,
                'code': code,
                'address': address,
                'tag': tag,
            }
        return result

    async def fetch_deposit_address(self, code, params={}):
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
        }
        response = await self.privateWalletGetDepositAddress(self.extend(request, params))
        #
        #     {
        #       "currency": "XRP",
        #       "address": "rHcFoo6a9qT5NHiVn1THQRhsEGcxtYCV4d 391331007",
        #       "multichain_addresses": [
        #         {
        #           "chain": "XRP",
        #           "address": "rHcFoo6a9qT5NHiVn1THQRhsEGcxtYCV4d",
        #           "payment_id": "391331007",
        #           "payment_name": "Tag",
        #           "obtain_failed": 0
        #         }
        #       ]
        #     }
        #
        currencyId = self.safe_string(response, 'currency')
        code = self.safe_currency_code(currencyId)
        addressField = self.safe_string(response, 'address')
        tag = None
        address = None
        if addressField.find(' ') >= 0:
            splitted = addressField.split(' ')
            address = splitted[0]
            tag = splitted[1]
        else:
            address = addressField
        return {
            'info': response,
            'code': code,
            'address': address,
            'tag': tag,
        }

    async def fetch_trading_fees(self, params={}):
        await self.load_markets()
        response = await self.privateWalletGetFee(params)
        #
        #     {
        #       "user_id": 1486602,
        #       "taker_fee": "0.002",
        #       "maker_fee": "0.002",
        #       "gt_discount": True,
        #       "gt_taker_fee": "0.0015",
        #       "gt_maker_fee": "0.0015",
        #       "loan_fee": "0.18",
        #       "point_type": "0",
        #       "futures_taker_fee": "0.0005",
        #       "futures_maker_fee": "0"
        #     }
        #
        result = {}
        taker = self.safe_number(response, 'taker_fee')
        maker = self.safe_number(response, 'maker_fee')
        for i in range(0, len(self.symbols)):
            symbol = self.symbols[i]
            result[symbol] = {
                'maker': maker,
                'taker': taker,
                'info': response,
                'symbol': symbol,
            }
        return result

    async def fetch_funding_fees(self, params={}):
        await self.load_markets()
        response = await self.privateWalletGetWithdrawStatus(params)
        #
        #     {
        #       "currency": "MTN",
        #       "name": "Medicalchain",
        #       "name_cn": "Medicalchain",
        #       "deposit": "0",
        #       "withdraw_percent": "0%",
        #       "withdraw_fix": "900",
        #       "withdraw_day_limit": "500000",
        #       "withdraw_day_limit_remain": "500000",
        #       "withdraw_amount_mini": "900.1",
        #       "withdraw_eachtime_limit": "90000000000",
        #       "withdraw_fix_on_chains": {
        #         "ETH": "900"
        #       }
        #     }
        #
        withdrawFees = {}
        for i in range(0, len(response)):
            entry = response[i]
            currencyId = self.safe_string(entry, 'currency')
            code = self.safe_currency_code(currencyId)
            withdrawFees[code] = {}
            withdrawFix = self.safe_value(entry, 'withdraw_fix_on_chains')
            if withdrawFix is None:
                withdrawFix = {}
                withdrawFix[code] = self.safe_number(entry, 'withdraw_fix')
            keys = list(withdrawFix.keys())
            for i in range(0, len(keys)):
                key = keys[i]
                withdrawFees[code][key] = self.parse_number(withdrawFix[key])
        return {
            'info': response,
            'withdraw': withdrawFees,
            'deposit': {},
        }

    async def fetch_order_book(self, symbol, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'currency_pair': market['id'],
        }
        if limit is not None:
            request['limit'] = limit  # default 10, max 100
        response = await self.publicSpotGetOrderBook(self.extend(request, params))
        timestamp = self.safe_integer(response, 'current')
        return self.parse_order_book(response, symbol, timestamp)

    async def fetch_ticker(self, symbol, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'currency_pair': market['id'],
        }
        response = await self.publicSpotGetTickers(self.extend(request, params))
        ticker = self.safe_value(response, 0)
        return self.parse_ticker(ticker, market)

    def parse_ticker(self, ticker, market=None):
        #
        #     {
        #       "currency_pair": "KFC_USDT",
        #       "last": "7.255",
        #       "lowest_ask": "7.298",
        #       "highest_bid": "7.218",
        #       "change_percentage": "-1.18",
        #       "base_volume": "1219.053687865",
        #       "quote_volume": "8807.40299875455",
        #       "high_24h": "7.262",
        #       "low_24h": "7.095"
        #     }
        #
        marketId = self.safe_string(ticker, 'currency_pair')
        symbol = self.safe_symbol(marketId, market)
        last = self.safe_number(ticker, 'last')
        ask = self.safe_number(ticker, 'lowest_ask')
        bid = self.safe_number(ticker, 'highest_bid')
        high = self.safe_number(ticker, 'high_24h')
        low = self.safe_number(ticker, 'low_24h')
        baseVolume = self.safe_number(ticker, 'base_volume')
        quoteVolume = self.safe_number(ticker, 'quote_volume')
        percentage = self.safe_number(ticker, 'change_percentage')
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': None,
            'datetime': None,
            'high': high,
            'low': low,
            'bid': bid,
            'bidVolume': None,
            'ask': ask,
            'askVolume': None,
            'vwap': None,
            'open': None,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': percentage,
            'average': None,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }, market)

    async def fetch_tickers(self, symbols=None, params={}):
        await self.load_markets()
        response = await self.publicSpotGetTickers(params)
        return self.parse_tickers(response, symbols)

    async def fetch_balance(self, params={}):
        await self.load_markets()
        response = await self.privateSpotGetAccounts(params)
        #
        #     [
        #       {
        #         "currency": "DBC",
        #         "available": "0",
        #         "locked": "0"
        #       },
        #       ...
        #     ]
        #
        result = {}
        for i in range(0, len(response)):
            entry = response[i]
            account = self.account()
            currencyId = self.safe_string(entry, 'currency')
            code = self.safe_currency_code(currencyId)
            account['used'] = self.safe_string(entry, 'locked')
            account['free'] = self.safe_string(entry, 'available')
            result[code] = account
        return self.parse_balance(result)

    async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        price = self.safe_string(params, 'price')
        params = self.omit(params, 'price')
        isMark = (price == 'mark')
        isIndex = (price == 'index')
        isFuture = isMark or isIndex
        request = {
            'interval': self.timeframes[timeframe],
        }
        if since is None:
            if limit is not None:
                request['limit'] = limit
        else:
            request['from'] = int(math.floor(since / 1000))
            if limit is not None:
                request['to'] = self.sum(request['from'], limit * self.parse_timeframe(timeframe) - 1)
        method = 'publicSpotGetCandlesticks'
        if isFuture:
            request['contract'] = market['id']
            method = 'publicFuturesGetSettleCandlesticks'
            request['settle'] = market['quote'].lower()
            if isMark:
                request['contract'] = 'mark_' + request['contract']
            elif isIndex:
                request['contract'] = 'index_' + request['contract']
        else:
            request['currency_pair'] = market['id']
        response = await getattr(self, method)(self.extend(request, params))
        return self.parse_ohlcvs(response, market, timeframe, since, limit)

    async def fetch_mark_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        request = {
            'price': 'mark',
        }
        return await self.fetch_ohlcv(symbol, timeframe, since, limit, self.extend(request, params))

    async def fetch_funding_rate_history(self, symbol, limit=None, since=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'contract': market['id'],
            'settle': market['quote'].lower(),
        }
        if limit is not None:
            request['limit'] = limit
        method = 'publicFuturesGetSettleFundingRate'
        response = await getattr(self, method)(self.extend(request, params))
        #
        #     {
        #         "fundingRate": "0.00063521",
        #         "fundingTime": "1621267200000",
        #     }
        #
        rates = []
        for i in range(0, len(response)):
            rates.append({
                'symbol': symbol,
                'fundingRate': self.safe_number(response[i], 'r'),
                'timestamp': self.safe_number(response[i], 't'),
            })
        return rates

    async def fetch_index_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        request = {
            'price': 'index',
        }
        return await self.fetch_ohlcv(symbol, timeframe, since, limit, self.extend(request, params))

    def parse_ohlcv(self, ohlcv, market=None):
        # Spot market candles
        #     [
        #       "1626163200",           # Unix timestamp in seconds
        #       "346711.933138181617",  # Trading volume
        #       "33165.23",             # Close price
        #       "33260",                # Highest price
        #       "33117.6",              # Lowest price
        #       "33184.47"              # Open price
        #     ]
        #
        # Mark and Index price candles
        # {
        #      "t":1632873600,         # Unix timestamp in seconds
        #      "o":"41025",            # Open price
        #      "h":"41882.17",         # Highest price
        #      "c":"41776.92",         # Close price
        #      "l":"40783.94"          # Lowest price
        # }
        #
        if isinstance(ohlcv, list):
            return [
                self.safe_timestamp(ohlcv, 0),   # unix timestamp in seconds
                self.safe_number(ohlcv, 5),      # open price
                self.safe_number(ohlcv, 3),      # highest price
                self.safe_number(ohlcv, 4),      # lowest price
                self.safe_number(ohlcv, 2),      # close price
                self.safe_number(ohlcv, 1),      # trading volume
            ]
        else:
            # Mark and Index price candles
            return [
                self.safe_timestamp(ohlcv, 't'),  # unix timestamp in seconds
                self.safe_number(ohlcv, 'o'),    # open price
                self.safe_number(ohlcv, 'h'),    # highest price
                self.safe_number(ohlcv, 'l'),    # lowest price
                self.safe_number(ohlcv, 'c'),    # close price
                0,
            ]

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'currency_pair': market['id'],
        }
        response = await self.publicSpotGetTrades(self.extend(request, params))
        return self.parse_trades(response, market, since, limit)

    async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'currency_pair': market['id'],
            # 'limit': limit,
            # 'page': 0,
            # 'order_id': 'Order ID',
            # 'account': 'spot',  # default to spot and margin account if not specified, set to cross_margin to operate against margin account
            # 'from': since,  # default to 7 days before current time
            # 'to': self.milliseconds(),  # default to current time
        }
        if limit is not None:
            request['limit'] = limit  # default 100, max 1000
        if since is not None:
            request['from'] = int(math.floor(since / 1000))
            # request['to'] = since + 7 * 24 * 60 * 60
        response = await self.privateSpotGetMyTrades(self.extend(request, params))
        return self.parse_trades(response, market, since, limit)

    def parse_trade(self, trade, market=None):
        #
        # public
        #     {
        #       "id": "1334253759",
        #       "create_time": "1626342738",
        #       "create_time_ms": "1626342738331.497000",
        #       "currency_pair": "BTC_USDT",
        #       "side": "sell",
        #       "amount": "0.0022",
        #       "price": "32452.16"
        #     }
        #
        # private
        #     {
        #       "id": "218087755",
        #       "create_time": "1578958740",
        #       "create_time_ms": "1578958740122.710000",
        #       "currency_pair": "BTC_USDT",
        #       "side": "sell",
        #       "role": "taker",
        #       "amount": "0.0004",
        #       "price": "8112.77",
        #       "order_id": "8445563839",
        #       "fee": "0.006490216",
        #       "fee_currency": "USDT",
        #       "point_fee": "0",
        #       "gt_fee": "0"
        #     }
        #
        id = self.safe_string(trade, 'id')
        timestampString = self.safe_string_2(trade, 'create_time_ms', 'time')
        timestamp = None
        if timestampString.find('.') > 0:
            milliseconds = timestampString.split('.')
            timestamp = int(milliseconds[0])
        marketId = self.safe_string(trade, 'currency_pair')
        symbol = self.safe_symbol(marketId, market)
        amountString = self.safe_string(trade, 'amount')
        priceString = self.safe_string(trade, 'price')
        cost = self.parse_number(Precise.string_mul(amountString, priceString))
        amount = self.parse_number(amountString)
        price = self.parse_number(priceString)
        side = self.safe_string(trade, 'side')
        orderId = self.safe_string(trade, 'order_id')
        gtFee = self.safe_string(trade, 'gt_fee')
        feeCurrency = None
        feeCost = None
        if gtFee == '0':
            feeCurrency = self.safe_string(trade, 'fee_currency')
            feeCost = self.safe_number(trade, 'fee')
        else:
            feeCurrency = 'GT'
            feeCost = self.parse_number(gtFee)
        fee = {
            'cost': feeCost,
            'currency': feeCurrency,
        }
        takerOrMaker = self.safe_string(trade, 'role')
        return {
            'info': trade,
            'id': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'order': orderId,
            'type': None,
            'side': side,
            'takerOrMaker': takerOrMaker,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {}
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        if limit is not None:
            request['limit'] = limit
        if since is not None:
            request['from'] = int(math.floor(since / 1000))
            request['to'] = since + 30 * 24 * 60 * 60
        response = await self.privateWalletGetDeposits(self.extend(request, params))
        return self.parse_transactions(response, currency)

    async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        request = {}
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['currency'] = currency['id']
        if limit is not None:
            request['limit'] = limit
        if since is not None:
            request['from'] = int(math.floor(since / 1000))
            request['to'] = since + 30 * 24 * 60 * 60
        response = await self.privateWalletGetWithdrawals(self.extend(request, params))
        return self.parse_transactions(response, currency)

    async def withdraw(self, code, amount, address, tag=None, params={}):
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        self.check_address(address)
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
            'address': address,
            'amount': self.currency_to_precision(code, amount),
        }
        if tag is not None:
            request['memo'] = tag
        networks = self.safe_value(self.options, 'networks', {})
        network = self.safe_string_upper(params, 'network')  # self line allows the user to specify either ERC20 or ETH
        network = self.safe_string_lower(networks, network, network)  # handle ETH>ERC20 alias
        if network is not None:
            request['chain'] = network
            params = self.omit(params, 'network')
        response = await self.privateWithdrawalsPost(self.extend(request, params))
        #
        #     {
        #       "id": "w13389675",
        #       "currency": "USDT",
        #       "amount": "50",
        #       "address": "TUu2rLFrmzUodiWfYki7QCNtv1akL682p1",
        #       "memo": null
        #     }
        #
        currencyId = self.safe_string(response, 'currency')
        id = self.safe_string(response, 'id')
        return {
            'info': response,
            'id': id,
            'code': self.safe_currency_code(currencyId),
            'amount': self.safe_number(response, 'amount'),
            'address': self.safe_string(response, 'address'),
            'tag': self.safe_string(response, 'memo'),
        }

    def parse_transaction_status(self, status):
        statuses = {
            'PEND': 'pending',
            'REQUEST': 'pending',
            'DMOVE': 'pending',
            'CANCEL': 'failed',
            'DONE': 'ok',
        }
        return self.safe_string(statuses, status, status)

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

    def parse_transaction(self, transaction, currency=None):
        #
        # deposits
        #     {
        #       "id": "d33361395",
        #       "currency": "USDT_TRX",
        #       "address": "TErdnxenuLtXfnMafLbfappYdHtnXQ5U4z",
        #       "amount": "100",
        #       "txid": "ae9374de34e558562fe18cbb1bf9ab4d9eb8aa7669d65541c9fa2a532c1474a0",
        #       "timestamp": "1626345819",
        #       "status": "DONE",
        #       "memo": ""
        #     }
        #
        # withdrawals
        id = self.safe_string(transaction, 'id')
        type = None
        if id is not None:
            type = self.parse_transaction_type(id[0])
        currencyId = self.safe_string(transaction, 'currency')
        code = self.safe_currency_code(currencyId)
        amount = self.safe_number(transaction, 'amount')
        txid = self.safe_string(transaction, 'txid')
        rawStatus = self.safe_string(transaction, 'status')
        status = self.parse_transaction_status(rawStatus)
        address = self.safe_string(transaction, 'address')
        fee = self.safe_number(transaction, 'fee')
        tag = self.safe_string(transaction, 'memo')
        if tag == '':
            tag = None
        timestamp = self.safe_timestamp(transaction, 'timestamp')
        return {
            'info': transaction,
            'id': id,
            'txid': txid,
            'currency': code,
            'amount': amount,
            'address': address,
            'tag': tag,
            'status': status,
            'type': type,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fee': fee,
        }

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'currency_pair': market['id'],
            'amount': self.amount_to_precision(symbol, amount),
            'price': self.price_to_precision(symbol, price),
            'side': side,
        }
        response = await self.privateSpotPostOrders(self.extend(request, params))
        return self.parse_order(response, market)

    def parse_order(self, order, market=None):
        #
        # createOrder
        #
        #     {
        #       "id": "62364648575",
        #       "text": "apiv4",
        #       "create_time": "1626354834",
        #       "update_time": "1626354834",
        #       "create_time_ms": "1626354833544",
        #       "update_time_ms": "1626354833544",
        #       "status": "open",
        #       "currency_pair": "BTC_USDT",
        #       "type": "limit",
        #       "account": "spot",
        #       "side": "buy",
        #       "amount": "0.0001",
        #       "price": "30000",
        #       "time_in_force": "gtc",
        #       "iceberg": "0",
        #       "left": "0.0001",
        #       "fill_price": "0",
        #       "filled_total": "0",
        #       "fee": "0",
        #       "fee_currency": "BTC",
        #       "point_fee": "0",
        #       "gt_fee": "0",
        #       "gt_discount": True,
        #       "rebated_fee": "0",
        #       "rebated_fee_currency": "USDT"
        #     }
        #
        #
        id = self.safe_string(order, 'id')
        marketId = self.safe_string(order, 'currency_pair')
        symbol = self.safe_symbol(marketId, market)
        timestamp = self.safe_timestamp(order, 'create_time')
        timestamp = self.safe_integer(order, 'create_time_ms', timestamp)
        lastTradeTimestamp = self.safe_timestamp(order, 'update_time')
        lastTradeTimestamp = self.safe_integer(order, 'update_time_ms', lastTradeTimestamp)
        amount = self.safe_number(order, 'amount')
        price = self.safe_number(order, 'price')
        remaining = self.safe_number(order, 'left')
        cost = self.safe_number(order, 'filled_total')  # same as filled_price
        side = self.safe_string(order, 'side')
        type = self.safe_string(order, 'type')
        # open, closed, cancelled - almost already ccxt unified!
        status = self.safe_string(order, 'status')
        if status == 'cancelled':
            status = 'canceled'
        timeInForce = self.safe_string_upper(order, 'time_in_force')
        fees = []
        fees.append({
            'currency': 'GT',
            'cost': self.safe_number(order, 'gt_fee'),
        })
        fees.append({
            'currency': self.safe_currency_code(self.safe_string(order, 'fee_currency')),
            'cost': self.safe_number(order, 'fee'),
        })
        rebate = self.safe_string(order, 'rebated_fee')
        fees.append({
            'currency': self.safe_currency_code(self.safe_string(order, 'rebated_fee_currency')),
            'cost': self.parse_number(Precise.string_neg(rebate)),
        })
        return self.safe_order({
            'id': id,
            'clientOrderId': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'status': status,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': None,
            'side': side,
            'price': price,
            'stopPrice': None,
            'average': None,
            'amount': amount,
            'cost': cost,
            'filled': None,
            'remaining': remaining,
            'fee': None,
            'fees': fees,
            'trades': None,
            'info': order,
        })

    async def fetch_order(self, id, symbol=None, params={}):
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOrder() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'order_id': id,
            'currency_pair': market['id'],
        }
        response = await self.privateSpotGetOrdersOrderId(self.extend(request, params))
        return self.parse_order(response, market)

    async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        if symbol is None:
            request = {
                # 'page': 1,
                # 'limit': limit,
                # 'account': '',  # spot/margin(default), cross_margin
            }
            if limit is not None:
                request['limit'] = limit
            response = await self.privateSpotGetOpenOrders(self.extend(request, params))
            #
            #     [
            #         {
            #             "currency_pair": "ETH_BTC",
            #             "total": 1,
            #             "orders": [
            #                 {
            #                     "id": "12332324",
            #                     "text": "t-123456",
            #                     "create_time": "1548000000",
            #                     "update_time": "1548000100",
            #                     "currency_pair": "ETH_BTC",
            #                     "status": "open",
            #                     "type": "limit",
            #                     "account": "spot",
            #                     "side": "buy",
            #                     "amount": "1",
            #                     "price": "5.00032",
            #                     "time_in_force": "gtc",
            #                     "left": "0.5",
            #                     "filled_total": "2.50016",
            #                     "fee": "0.005",
            #                     "fee_currency": "ETH",
            #                     "point_fee": "0",
            #                     "gt_fee": "0",
            #                     "gt_discount": False,
            #                     "rebated_fee": "0",
            #                     "rebated_fee_currency": "BTC"
            #                 }
            #             ]
            #         },
            #         ...
            #     ]
            #
            allOrders = []
            for i in range(0, len(response)):
                entry = response[i]
                orders = self.safe_value(entry, 'orders', [])
                parsed = self.parse_orders(orders, None, since, limit)
                allOrders = self.array_concat(allOrders, parsed)
            return self.filter_by_since_limit(allOrders, since, limit)
        return await self.fetch_orders_by_status('open', symbol, since, limit, params)

    async def fetch_closed_orders(self, symbol=None, since=None, limit=None, params={}):
        return await self.fetch_orders_by_status('finished', symbol, since, limit, params)

    async def fetch_orders_by_status(self, status, symbol=None, since=None, limit=None, params={}):
        await self.load_markets()
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOrdersByStatus requires a symbol argument')
        market = self.market(symbol)
        request = {
            'currency_pair': market['id'],
            'status': status,
        }
        if limit is not None:
            request['limit'] = limit
        if since is not None:
            request['start'] = int(math.floor(since / 1000))
        response = await self.privateSpotGetOrders(self.extend(request, params))
        return self.parse_orders(response, market, since, limit)

    async def cancel_order(self, id, symbol=None, params={}):
        await self.load_markets()
        if symbol is None:
            raise ArgumentsRequired(self.id + ' cancelOrders requires a symbol parameter')
        market = self.market(symbol)
        request = {
            'order_id': id,
            'currency_pair': market['id'],
        }
        response = await self.privateSpotDeleteOrdersOrderId(self.extend(request, params))
        return self.parse_order(response)

    async def transfer(self, code, amount, fromAccount, toAccount, params={}):
        await self.load_markets()
        currency = self.currency(code)
        accountsByType = self.safe_value(self.options, 'accountsByType', {})
        fromId = self.safe_string(accountsByType, fromAccount, fromAccount)
        toId = self.safe_string(accountsByType, toAccount, toAccount)
        if fromId is None:
            keys = list(accountsByType.keys())
            raise ExchangeError(self.id + ' fromAccount must be one of ' + ', '.join(keys))
        if toId is None:
            keys = list(accountsByType.keys())
            raise ExchangeError(self.id + ' toAccount must be one of ' + ', '.join(keys))
        truncated = self.currency_to_precision(code, amount)
        request = {
            'currency': currency['id'],
            'from': fromId,
            'to': toId,
            'amount': truncated,
        }
        if (toId == 'futures') or (toId == 'delivery'):
            request['settle'] = currency['id']
        response = await self.privateWalletPostTransfers(self.extend(request, params))
        #
        # according to the docs
        #     {
        #       "currency": "BTC",
        #       "from": "spot",
        #       "to": "margin",
        #       "amount": "1",
        #       "currency_pair": "BTC_USDT"
        #     }
        #
        # actual response
        #  POST https://api.gateio.ws/api/v4/wallet/transfers 204 No Content
        #
        return {
            'info': response,
            'from': fromId,
            'to': toId,
            'amount': truncated,
            'code': code,
        }

    def sign(self, path, api=[], method='GET', params={}, headers=None, body=None):
        authentication = api[0]  # public, private
        type = api[1]  # spot, margin, future, delivery
        query = self.omit(params, self.extract_params(path))
        path = self.implode_params(path, params)
        endPart = (path == '' if '' else '/' + path)
        entirePath = '/' + type + endPart
        url = self.urls['api'][authentication] + entirePath
        queryString = ''
        if authentication == 'public':
            queryString = self.urlencode(query)
            if query:
                url += '?' + queryString
        else:
            if (method == 'GET') or (method == 'DELETE'):
                queryString = self.urlencode(query)
                if query:
                    url += '?' + queryString
            else:
                body = self.json(query)
            bodyPayload = '' if (body is None) else body
            bodySignature = self.hash(self.encode(bodyPayload), 'sha512')
            timestamp = self.seconds()
            timestampString = str(timestamp)
            signaturePath = '/api/v4' + entirePath
            payloadArray = [method.upper(), signaturePath, queryString, bodySignature, timestampString]
            # eslint-disable-next-line quotes
            payload = "\n".join(payloadArray)
            signature = self.hmac(self.encode(payload), self.encode(self.secret), hashlib.sha512)
            headers = {
                'KEY': self.apiKey,
                'Timestamp': timestampString,
                'SIGN': signature,
                'Content-Type': 'application/json',
            }
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code, reason, url, method, headers, body, response, requestHeaders, requestBody):
        label = self.safe_string(response, 'label')
        if label is not None:
            message = self.safe_string_2(response, 'message', 'detail', '')
            Error = self.safe_value(self.exceptions, label, ExchangeError)
            raise Error(self.id + ' ' + message)
