# -*- 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
import math
from ccxt.base.errors import ExchangeError
from ccxt.base.errors import AuthenticationError
from ccxt.base.errors import ArgumentsRequired
from ccxt.base.errors import BadRequest
from ccxt.base.errors import InsufficientFunds
from ccxt.base.errors import InvalidOrder
from ccxt.base.errors import ExchangeNotAvailable


class mandala (Exchange):

    def describe(self):
        return self.deep_extend(super(mandala, self).describe(), {
            'id': 'mandala',
            'name': 'Mandala',
            'countries': ['MT'],
            'version': 'v1.1',
            'rateLimit': 1500,
            'certified': False,
            # new metainfo interface
            'has': {
                'cancelAllOrders': True,
                'CORS': True,
                'createDepositAddress': True,
                'createMarketOrder': True,
                'fetchCurrencies': True,
                'fetchDepositAddress': True,
                'fetchDepositAddresses': True,
                'fetchDeposits': True,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrders': True,
                'fetchOrderStatus': True,
                'fetchTickers': True,
                'fetchWithdrawals': True,
                'withdraw': True,
            },
            'timeframes': {
                '1m': '1',
                '5m': '5',
                '1h': '60',
                '1d': '1440',
            },
            'comment': 'Modulus Exchange API ',
            'hostname': 'mandalaex.com',
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/54686665-df629400-4b2a-11e9-84d3-d88856367dd7.jpg',
                'api': 'https://zapi.{hostname}',
                'www': 'https://mandalaex.com',
                'doc': [
                    'https://apidocs.mandalaex.com',
                ],
                'fees': [
                    'https://mandalaex.com/trading-rules/',
                ],
                'referral': 'https://trade.mandalaex.com/?ref=564377',
            },
            'api': {
                'settings': {
                    'get': [
                        'getCoinInfo',  # FIX ME, self endpoint is documented, but broken: https://zapi.mandalaex.com/api/getCoinInfo
                        'GetSettings',
                        'CurrencySettings',
                        'Get_Withdrawal_Limits',
                    ],
                },
                'token': {
                    'post': [
                        'token',
                    ],
                },
                'public': {
                    'post': [
                        'AuthenticateUser',
                        'ForgotPassword',
                        'SignUp',
                        'check_Duplicate_Mobile',
                        'check_Duplicate_Email',
                    ],
                },
                'api': {
                    'get': [
                        'GAuth_Check_Status',
                        'GAuth_Enable_Request',
                        'GetProfile',
                        'Loginhistory',
                        'ListAllAddresses',
                        'Get_User_Withdrawal_Limits',
                        'GetPendingOrders',  # ?side=aLL&pair=ALL&timestamp=1541240408&recvWindow=3600',
                        'TradeHistory',  # ?side=ALL&pair=ALL&timestamp=1550920234&recvWindow=10000&count=100&page=1',
                        'GOKYC_Get_Kyc_Form',
                        'language_list',
                        'language',  # ?code=en&namespace=translation',
                        'get_page_n_content',
                        'GetExchangeTokenDiscountEnrollmentStatus',
                        'GetDiscountTiers',
                        'My_Affiliate',
                        'Affiliate_Summary',
                        'Affiliate_Commission',
                        'List_Fiat_Manual_Deposit_Requests',
                        'List_Fiat_BanksList/YCN/',
                        'Get_Fiat_PGs',  # ?Currency=TRY',
                        'get_insta_pairs',
                        'hmac',  # ?side=BUY&market=BTC&trade=ETH&type=STOPLIMIT&volume=0.025&rate=0.032&timeInForce=GTC&stop=2&',
                    ],
                    'post': [
                        'GAuth_Set_Enable',
                        'GAuth_Disable_Request',
                        'VerifyAccount',
                        'SignUp_Resend_Email',
                        'AuthenticateUser_Resend_EmailOTP/{tempAuthToken}',
                        'Validate_BearerToken',
                        'RequestChangePasswordOT',
                        'ChangePassword',
                        'ResetPassword',
                        'GenerateAddress',
                        'GetBalance',
                        'GetDeposits',
                        'GetWithdrawals',
                        'RequestWithdraw',
                        'RequestWithdrawConfirmation',
                        'RequestTransfer_AeraPass',
                        'PlaceOrder',
                        'PlaceOrder_Priced',
                        'CancelOrder',
                        'KYC_GetSumAndSub_AccessToken',
                        'KYC_SaveSumAndSubstanceApplicationId',
                        'GOKYC_Submit_KYC_Form',
                        'SetExchangeTokenDiscountEnrollment',
                        'Dis_Enroll_ExchangeTokenDiscount',
                        'Webhook_BitGoDeposit',
                        'Add_Fiat_Manual_Deposit_Request',
                        'Add_Fiat_Manual_Withdrawal_Request',
                        'Add_Fiat_PG_Deposit_Request',
                        'ListApiKey',
                        'GenerateApiKey',
                        'DeleteApiKey',
                        'request_insta_trade',
                        'confirm_insta_trade',
                        'simplex_get_quote',
                        'simplex_payment',
                        'hmac',
                        'import_translations',
                    ],
                },
                'market': {
                    'get': [
                        'get-market-summary',
                        'get-market-summary/{marketId}',
                        'get-trade-history/{marketId}',
                        'get-bid_ask-price/{marketId}',
                        'get-open-orders/{marketId}/{side}/{depth}',
                        'get-currency-price/{marketId}',
                        'get-currency-usd-rate/{currencyId}',
                        'depth',  # ?symbol=BTC_ETH&limit=10
                        'get-chart-data',  # ?baseCurrency=BTC&quoteCurrency=ETH&interval=60&limit=200&timestamp=1541228704517
                    ],
                },
                'order': {
                    'get': [
                        'my-order-history/{key}/{side}',
                        'my-order-history/{key}/{side}/{orderId}',
                        'my-order-status/{key}/{side}/{orderId}',
                        'my-trade-history',  # ?side=BUY&pair=BTC_ETH&orderID=13165837&apiKey=d14b1eb4-fe1f-4bfc-896d-97285975989e
                        'hmac',  # ?side=BUY&market=BTC&trade=ETH&type=STOPLIMIT&volume=0.025&rate=0.032&timeInForce=GTC&stop=2&'
                    ],
                    'post': [
                        'my-order-history',
                        'my-order-status',
                        'PlaceOrder',
                        'cancel-my-order',
                        'cancel-all-my-orders',
                        'get-balance',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'tierBased': False,
                    'percentage': True,
                    'maker': 0.005,
                    'taker': 0.005,
                },
            },
            'exceptions': {
                'exact': {
                    'Failure_General': ExchangeError,  # {"Status":"Error","Message":"Failure_General","Data":"Cannot roll back TransBuyOrder. No transaction or savepoint of that name was found."}
                    'Exception_Insufficient_Funds': InsufficientFunds,  # {"Status":"Error","Message":"Exception_Insufficient_Funds","Data":"Insufficient Funds."}
                    'Exception_TimeStamp': BadRequest,  # {"status":"BadRequest","message":"Exception_TimeStamp","data":"Invalid timestamp."}
                    'Exception_HMAC_Validation': AuthenticationError,  # {"status":"Error","message":"Exception_HMAC_Validation","data":"HMAC validation failed."}
                    'Exception_General': BadRequest,  # {"status":"BadRequest","message":"Exception_General","data":"Our servers are experiencing some glitch, please try again later."}
                    'Must provide the orderID param.': BadRequest,  # {"Status":"BadRequest","Message":"Must provide the orderID param.","Data":null}
                    'Invalid Market_Currency pairnot ': ExchangeError,  # {"status":"Error","errorMessage":"Invalid Market_Currency pairnot ","data":null}
                    'Invalid volume parameter.': InvalidOrder,  # {"Status":"BadRequest","Message":"Invalid volume parameter.","Data":null}
                    'Invalid rate parameter.': InvalidOrder,  # {"Status":"BadRequest","Message":"Invalid rate parameter.","Data":null}
                    "Invalid parameter 'side', must be 'BUY' or 'SELL'.": InvalidOrder,  # {"Status":"BadRequest","Message":"Invalid parameter 'side', must be 'BUY' or 'SELL'.","Data":null}
                    'Invalid Type': BadRequest,  # on fetchOrders with a wrong type {"status":"Error","errorMessage":"Invalid Type","data":null}
                    'Exception_Invalid_CurrencyName': BadRequest,  # {"status":"BadRequest","message":"Exception_Invalid_CurrencyName","data":"Invalid Currency name"}
                    'Exception_BadRequest': BadRequest,  # {"status":"BadRequest","message":"Exception_BadRequest","data":"Invalid Payload"}
                },
                'broad': {
                    'Some error occurred, try again later.': ExchangeNotAvailable,  # {"status":"Error","errorMessage":"Some error occurred, try again later.","data":null}
                },
            },
            'options': {
                'symbolSeparator': '_',
                'api': {
                    'settings': 'api',
                    'public': 'api',
                },
                'fetchCurrencies': {
                    'expires': 5000,
                },
                # https://documenter.getpostman.com/view/5614390/RWguuvfd#a74ee943-3b7a-415e-9315-a7bf204db09d
                # HMAC can be obtained using a Secret key. Thispre shared secret key ensures that the message is encrypted by a legitimate source. You can get a secret key issued for your sandbox enviroment by writing an email to support@modulus.io
                # Secret-Key : 03c06dd7-4982-441a-910d-5fd2cbb3f1c6
                'secret': '03c06dd7-4982-441a-910d-5fd2cbb3f1c6',
            },
        })

    def sign_in(self, params={}):
        if not self.login or not self.password:
            raise AuthenticationError(self.id + ' signIn() requires self.login(email) and self.password credentials')
        authenticateRequest = {
            'email': self.login,
            'password': self.password,
        }
        authenticateResponse = self.publicPostAuthenticateUser(authenticateRequest)
        #
        #     {
        #         status: 'Success',
        #         message: 'Successnot ',
        #         data: {
        #             tempAuthToken: 'e1b0603a-5996-4bac-9ec4-f097a02d9696',
        #             tokenExpiry: '2019-03-19T21:16:15.999201Z',
        #             twoFAMehtod: 'GAuth'
        #         }
        #     }
        #
        data = self.safe_value(authenticateResponse, 'data', {})
        tempAuthToken = self.safe_string(data, 'tempAuthToken')
        otp = None
        if self.twofa is not None:
            otp = self.oath()
        otp = self.safe_string(params, 'password', otp)
        if otp is None:
            raise AuthenticationError(self.id + ' signIn() requires self.twofa credential or a one-time 2FA "password" parameter')
        tokenRequest = {
            'grant_type': 'password',
            'username': tempAuthToken,
            'password': otp,
        }
        tokenResponse = self.tokenPostToken(self.extend(tokenRequest, params))
        #
        #     {
        #         "access_token": "WWRNCO--bFjX3zKAixROAjy3dbU0csNoI91PXpT1oScTrik50mVrSIbr22HrsJV5ATXgN867vy66pxY7IzMQGzYtz-7KTxUnL6uPbQpiveBgPEGD5drpvh5KwhcCOzFelJ1-OxZa6g6trx82x2YqQI7Lny0VkAIEv-EBQT8B4C_UVYhoMVCzYumeQgcxtyXc9hoRolVUwwQ965--LrAYIybBby85LzRRIfh7Yg_CVSx6zehAcHFUeKh2tE4NwN9lYweeDEPb6z2kHn0UJb18nxYcC3-NjgiyublBiY1AI_U",
        #         "token_type": "bearer",
        #         "expires_in": 86399
        #     }
        #
        expiresIn = self.safe_integer(tokenResponse, 'expires_in')
        self.options['expires'] = self.sum(self.milliseconds(), expiresIn * 1000)
        self.options['accessToken'] = self.safe_string(tokenResponse, 'accessToken')
        self.options['tokenType'] = self.safe_string(tokenResponse, 'token_type')
        # accessToken = self.safe_value(tokenResponse, 'access_token')
        # self.headers['Authorization'] = 'Bearer ' + accessToken
        return tokenResponse

    def fetch_currencies_from_cache(self, params={}):
        # self method is now redundant
        # currencies are now fetched before markets
        options = self.safe_value(self.options, 'fetchCurrencies', {})
        timestamp = self.safe_integer(options, 'timestamp')
        expires = self.safe_integer(options, 'expires', 1000)
        now = self.milliseconds()
        if (timestamp is None) or ((now - timestamp) > expires):
            response = self.settingsGetCurrencySettings(params)
            self.options['fetchCurrencies'] = self.extend(options, {
                'response': response,
                'timestamp': now,
            })
        return self.safe_value(self.options['fetchCurrencies'], 'response')

    def fetch_currencies(self, params={}):
        response = self.fetch_currencies_from_cache(params)
        self.options['currencies'] = {
            'timestamp': self.milliseconds(),
            'response': response,
        }
        #
        #     {
        #         status: 'Success',
        #         message: 'Successnot ',
        #         data: [
        #             {
        #                 shortName: 'BAT',
        #                 fullName: 'Basic Attention Token',
        #                 buyServiceCharge: 0.5,
        #                 sellServiceCharge: 0.5,
        #                 withdrawalServiceCharge: 0.25,
        #                 withdrawalServiceChargeInBTC: 0,
        #                 confirmationCount: 29,
        #                 contractAddress: null,
        #                 minWithdrawalLimit: 100,
        #                 maxWithdrawalLimit: 2000000,
        #                 decimalPrecision: 18,
        #                 tradeEnabled: True,
        #                 depositEnabled: True,
        #                 withdrawalEnabled: True,
        #                 secondaryWalletType: '',
        #                 addressSeparator: '',
        #                 walletType: 'BitGo',
        #                 withdrawalServiceChargeType: 'Percentage',
        #             },
        #             {
        #                 shortName: 'BCH',
        #                 fullName: 'BitcoinCash',
        #                 buyServiceCharge: 0.5,
        #                 sellServiceCharge: 0.5,
        #                 withdrawalServiceCharge: 0.25,
        #                 withdrawalServiceChargeInBTC: 0.001,
        #                 confirmationCount: 3,
        #                 contractAddress: null,
        #                 minWithdrawalLimit: 0.1,
        #                 maxWithdrawalLimit: 300,
        #                 decimalPrecision: 8,
        #                 tradeEnabled: True,
        #                 depositEnabled: True,
        #                 withdrawalEnabled: True,
        #                 secondaryWalletType: '',
        #                 addressSeparator: '',
        #                 walletType: 'BitGo',
        #                 withdrawalServiceChargeType: 'Percentage',
        #             },
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        result = {}
        for i in range(0, len(data)):
            currency = data[i]
            id = self.safe_string(currency, 'shortName')
            code = self.safe_currency_code(id)
            name = self.safe_string(currency, 'fullName')
            precision = self.safe_integer(currency, 'decimalPrecision')
            active = True
            canWithdraw = self.safe_value(currency, 'withdrawalEnabled')
            canDeposit = self.safe_value(currency, 'depositEnabled')
            if not canWithdraw or not canDeposit:
                active = False
            result[code] = {
                'id': id,
                'code': code,
                'name': name,
                'active': active,
                'precision': precision,
                'fee': self.safe_float(currency, 'withdrawalServiceCharge') / 100,
                'limits': {
                    'amount': {
                        'min': math.pow(10, -precision),
                        'max': math.pow(10, precision),
                    },
                    'price': {
                        'min': math.pow(10, -precision),
                        'max': math.pow(10, precision),
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                    'withdraw': {
                        'min': self.safe_float(currency, 'minWithdrawalLimit'),
                        'max': self.safe_float(currency, 'maxWithdrawalLimit'),
                    },
                },
                'info': currency,
            }
        return result

    def fetch_markets(self, params={}):
        currenciesResponse = self.fetch_currencies_from_cache(params)
        currencies = self.safe_value(currenciesResponse, 'data', [])
        currenciesById = self.index_by(currencies, 'shortName')
        response = self.marketGetGetMarketSummary()
        #
        #     {
        #         status: 'Success',
        #         errorMessage: null,
        #         data: {
        #             BTC_BAT:
        #                 Last: 0.00003431,
        #                 LowestAsk: 0,
        #                 HeighestBid: 0,
        #                 PercentChange: 0,
        #                 BaseVolume: 0,
        #                 QuoteVolume: 0,
        #                 High_24hr: 0,
        #                 Low_24hr: 0,
        #             },
        #             ETH_ZRX: {
        #                 Last: 0.00213827,
        #                 LowestAsk: 0,
        #                 HeighestBid: 0,
        #                 PercentChange: 0,
        #                 BaseVolume: 0,
        #                 QuoteVolume: 0,
        #                 High_24hr: 0,
        #                 Low_24hr: 0,
        #             },
        #         },
        #     }
        #
        result = []
        data = self.safe_value(response, 'data', {})
        ids = list(data.keys())
        for i in range(0, len(ids)):
            id = ids[i]
            market = data[id]
            quoteId, baseId = id.split('_')  # they have base/quote reversed with some endpoints
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            symbol = base + '/' + quote
            baseCurrency = self.safe_value(currenciesById, baseId, {})
            quoteCurrency = self.safe_value(currenciesById, quoteId, {})
            precision = {
                'amount': self.safe_integer(baseCurrency, 'decimalPrecision', 8),
                'price': self.safe_integer(quoteCurrency, 'decimalPrecision', 8),
            }
            baseTradeEnabled = self.safe_value(baseCurrency, 'tradeEnabled', True)
            quoteTradeEnabled = self.safe_value(quoteCurrency, 'tradeEnabled', True)
            active = baseTradeEnabled and quoteTradeEnabled
            result.append({
                'id': id,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'baseId': baseId,
                'quoteId': quoteId,
                'active': active,
                'info': market,
                'precision': precision,
                'limits': {
                    'amount': {
                        'min': math.pow(10, -precision['amount']),
                        'max': None,
                    },
                    'price': {
                        'min': math.pow(10, -precision['price']),
                        'max': None,
                    },
                },
            })
        return result

    def fetch_balance(self, params={}):
        self.load_markets()
        request = {
            'currency': 'ALL',
        }
        response = self.orderPostGetBalance(self.extend(request, params))
        #
        #     {
        #         Status: 'Success',
        #         Message: null,
        #         Data: [
        #             {currency: 'BCH', balance: 0, balanceInTrade: 0},
        #             {currency: 'BTC', balance: 0, balanceInTrade: 0},
        #             ...,
        #         ],
        #     }
        #
        data = self.safe_value(response, 'Data', [])
        result = {'info': response}
        for i in range(0, len(data)):
            balance = data[i]
            currencyId = self.safe_string(balance, 'currency')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['free'] = self.safe_float(balance, 'balance')
            account['used'] = self.safe_float(balance, 'balanceInTrade')
            result[code] = account
        return self.parse_balance(result)

    def fetch_order_book(self, symbol, limit=None, params={}):
        self.load_markets()
        if limit is None:
            limit = 10
        request = {
            'symbol': self.market_id(symbol),
            'limit': limit,
        }
        response = self.marketGetDepth(self.extend(request, params))
        # https://documenter.getpostman.com/view/6273708/RznBP1Hh#19469d73-45b5-4dd1-8464-c043efb62e00
        #
        #     {
        #         status: 'Success',
        #         errorMessage: '',
        #         data: {
        #             lastUpdate: 1552825727108,
        #             bids: [
        #                 ["0.02880201", "0.05939008", []],
        #                 ["0.02880200", "0.30969842", []],
        #             ],
        #             'asks': [
        #                 ["0.02877161", "0.00001779", []],
        #                 ["0.02881321", "0.47325696", []],
        #             ],
        #         },
        #     }
        #
        data = self.safe_value(response, 'data', {})
        timestamp = self.safe_integer(data, 'lastUpdate')
        return self.parse_order_book(data, timestamp)

    def parse_ticker(self, ticker, market=None):
        #
        # fetchTicker, fetchTickers
        #     {
        #         Pair: 'ETH_MDX',  # FIXME missing in fetchTickers
        #         Last: 0.000055,
        #         LowestAsk: 0.000049,
        #         HeighestBid: 0.00003,
        #         PercentChange: 12.47,
        #         BaseVolume: 34.60345,
        #         QuoteVolume: 629153.63636364,
        #         IsFrozen: False,  # FIXME missing in fetchTickers
        #         High_24hr: 0,
        #         Low_24hr: 0
        #     }
        #
        symbol = None
        marketId = self.safe_string(ticker, 'Pair')
        if marketId is not None:
            if marketId in self.markets_by_id:
                market = self.markets_by_id[marketId]
                symbol = market['symbol']
            else:
                symbol = self.parse_symbol(marketId)
        if symbol is None:
            if market is not None:
                symbol = market['symbol']
        last = self.safe_float(ticker, 'Last')
        return {
            'symbol': symbol,
            'timestamp': None,  # FIXME, no timestamp in tickers
            'datetime': None,
            'high': self.safe_float(ticker, 'High_24hr'),
            'low': self.safe_float(ticker, 'Low_24hr'),
            'bid': self.safe_float(ticker, 'HeighestBid'),
            'bidVolume': None,
            'ask': self.safe_float(ticker, 'LowestAsk'),
            'askVolume': None,
            'vwap': None,
            'open': None,
            'close': last,
            'last': last,
            'previousClose': None,
            'change': None,
            'percentage': self.safe_float(ticker, 'PercentChange'),
            'average': None,
            'baseVolume': self.safe_float(ticker, 'QuoteVolume'),
            'quoteVolume': self.safe_float(ticker, 'BaseVolume'),
            'info': ticker,
        }

    def fetch_tickers(self, symbols=None, params={}):
        self.load_markets()
        response = self.marketGetGetMarketSummary(params)
        #
        #     {
        #         status: 'Success',
        #         errorMessage: null,
        #         data: {
        #             BTC_BAT: {
        #                 Last: 0.00003431,
        #                 LowestAsk: 0,
        #                 HeighestBid: 0,
        #                 PercentChange: 0,
        #                 BaseVolume: 0,
        #                 QuoteVolume: 0,
        #                 High_24hr: 0,
        #                 Low_24hr: 0,
        #             },
        #             ETH_ZRX: {
        #                 Last: 0.00213827,
        #                 LowestAsk: 0,
        #                 HeighestBid: 0,
        #                 PercentChange: 0,
        #                 BaseVolume: 0,
        #                 QuoteVolume: 0,
        #                 High_24hr: 0,
        #                 Low_24hr: 0,
        #             },
        #         },
        #     }
        #
        data = self.safe_value(response, 'data', {})
        ids = list(data.keys())
        result = {}
        for i in range(0, len(ids)):
            id = ids[i]
            ticker = data[id]
            market = None
            symbol = id
            if id in self.markets_by_id:
                market = self.markets_by_id[id]
                symbol = market['symbol']
            else:
                symbol = self.parse_symbol(id)
            result[symbol] = self.parse_ticker(ticker, market)
        return result

    def fetch_ticker(self, symbol, params={}):
        self.load_markets()
        request = {
            'marketId': self.market_id(symbol),
        }
        response = self.marketGetGetMarketSummaryMarketId(self.extend(request, params))
        #
        #     {
        #         status: 'Success',
        #         errorMessage: null,
        #         data: {
        #             Pair: 'ETH_MDX',
        #             Last: 0.000055,
        #             LowestAsk: 0.000049,
        #             HeighestBid: 0.00003,
        #             PercentChange: 12.47,
        #             BaseVolume: 34.60345,
        #             QuoteVolume: 629153.63636364,
        #             IsFrozen: False,
        #             High_24hr: 0,
        #             Low_24hr: 0
        #         }
        #     }
        #
        data = self.safe_value(response, 'data')
        return self.parse_ticker(data)

    def parse_trade(self, trade, market=None):
        #
        # fetchTrades(public)
        #
        #     {
        #         TradeID:  619255,
        #         Rate:  0.000055,
        #         Volume:  79163.63636364,
        #         Total:  4.354,
        #         Date: "2019-03-16T23:14:48.613",
        #         Type: "Buy"
        #     }
        #
        # fetchMyTrades(private)
        #
        #     {
        #         orderId: 20000040,
        #         market: 'ETH',
        #         trade: 'MDX',
        #         volume: 1,
        #         rate: 2,
        #         amount: 2,
        #         serviceCharge: 0.003,
        #         side: 'SELL',
        #         date: '2019-03-20T01:47:09.14'
        #     }
        #
        timestamp = self.parse8601(self.safe_string_2(trade, 'Date', 'date'))
        side = self.safe_string_lower_2(trade, 'Type', 'side')
        id = self.safe_string(trade, 'TradeID')
        symbol = None
        baseId = self.safe_string(trade, 'trade')
        quoteId = self.safe_string(trade, 'market')
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        if base is not None and quote is not None:
            symbol = base + '/' + quote
        else:
            if market is not None:
                symbol = market['symbol']
        cost = self.safe_float_2(trade, 'Total', 'amount')
        price = self.safe_float_2(trade, 'Rate', 'rate')
        amount = self.safe_float_2(trade, 'Volume', 'volume')
        orderId = self.safe_string(trade, 'orderId')
        feeCost = self.safe_value(trade, 'serviceCharge')
        fee = None
        if feeCost is not None:
            fee = {
                'cost': feeCost,
                'currency': quote,
            }
        return {
            'id': id,
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'order': orderId,
            'type': None,
            'side': side,
            'takerOrMaker': None,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }

    def fetch_trades(self, symbol, since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        request = {
            'marketId': market['id'],
        }
        # self endpoint returns last 50 trades
        response = self.marketGetGetTradeHistoryMarketId(self.extend(request, params))
        #
        #     {
        #         status:   "Success",
        #         errorMessage:    null,
        #         data: [
        #             {
        #                 TradeID:  619255,
        #                 Rate:  0.000055,
        #                 Volume:  79163.63636364,
        #                 Total:  4.354,
        #                 Date: "2019-03-16T23:14:48.613",
        #                 Type: "Buy"
        #             },
        #             {
        #                 TradeID:  619206,
        #                 Rate:  0.000073,
        #                 Volume:  7635.50136986,
        #                 Total:  0.5573916,
        #                 Date: "2019-02-13T16:49:54.02",
        #                 Type: "Sell"
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data')
        return self.parse_trades(data, market, since, limit)

    def parse_ohlcv(self, ohlcv, market=None, timeframe='1m', since=None, limit=None):
        #
        #     {
        #         time: 1552830600000,
        #         open: 0.000055,
        #         close: 0.000055,
        #         high: 0.000055,
        #         low: 0.000055,
        #         volume: 0,
        #     }
        #
        return [
            self.safe_integer(ohlcv, 'time'),
            self.safe_float(ohlcv, 'open'),
            self.safe_float(ohlcv, 'high'),
            self.safe_float(ohlcv, 'low'),
            self.safe_float(ohlcv, 'close'),
            self.safe_float(ohlcv, 'volume'),
        ]

    def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        if limit is None:
            limit = 100  # default is 100
        offset = self.parse_timeframe(timeframe) * self.sum(limit, 1) * 1000
        if since is None:
            since = self.milliseconds() - offset
        request = {
            'interval': self.timeframes[timeframe],
            'baseCurrency': market['baseId'],  # they have base/quote reversed with some endpoints
            'quoteCurrency': market['quoteId'],
            'limit': limit,
            'timestamp': self.sum(since, offset),
        }
        response = self.marketGetGetChartData(self.extend(request, params))
        #
        #     {
        #         status: 'Success',
        #         errorMessage: null,
        #         data: [
        #             {
        #                 time: 1552830600000,
        #                 open: 0.000055,
        #                 close: 0.000055,
        #                 high: 0.000055,
        #                 low: 0.000055,
        #                 volume: 0,
        #             },
        #             {
        #                 time: 1552830540000,
        #                 open: 0.000055,
        #                 close: 0.000055,
        #                 high: 0.000055,
        #                 low: 0.000055,
        #                 volume: 0,
        #             },
        #         ],
        #     }
        #
        data = self.safe_value(response, 'data')
        return self.parse_ohlcvs(data, market, timeframe, since, limit)

    def create_order(self, symbol, type, side, amount, price=None, params={}):
        self.load_markets()
        market = self.market(symbol)
        orderPrice = price
        if type == 'market':
            orderPrice = 0
        request = {
            'market': market['quoteId'],
            'trade': market['baseId'],
            'type': type.upper(),  # MARKET, LIMIT, STOPLIMIT
            'side': side.upper(),  # BUY, SELL
            # Here GTC should be default for LIMIT, MARKET & STOP LIMIT Orders.
            # IOC,FOK, DO must be passed only with a LIMIT order.
            # GTC(Good till cancelled), IOC(Immediate or cancel), FOK(Fill or Kill), Do(Day only)
            'timeInForce': 'GTC',
            'rate': self.price_to_precision(symbol, orderPrice),
            'volume': self.amount_to_precision(symbol, amount),
            # the stop-price at which a stop-limit order
            # triggers and becomes a limit order
            'stop': 0,  # stop is always zero for limit and market orders
            # 'clientOrderId': self.uuid(),
        }
        response = self.orderPostPlaceOrder(self.extend(request, params))
        #
        #     {
        #         Status: 'Success',
        #         Message: 'Successnot ',
        #         Data: {
        #             orderId: 20000031,
        #         },
        #     }
        #
        data = self.safe_value(response, 'Data', {})
        order = self.parse_order(data, market)
        return self.extend(order, {
            'symbol': symbol,
            'type': type,
            'side': side,
            'price': price,
            'amount': amount,
            'status': 'open',
        })

    def cancel_order(self, id, symbol=None, params={}):
        self.load_markets()
        side = self.safe_string(params, 'side')
        if side is None:
            raise ArgumentsRequired(self.id + ' cancelOrder() requires an order `side` extra parameter')
        params = self.omit(params, 'side')
        id = str(id)
        request = {
            'orderId': id,
            'side': side.upper(),
        }
        response = self.orderPostCancelMyOrder(self.extend(request, params))
        #
        #     {
        #         Status: 'Success',
        #         Message: 'Success_General',
        #         Data: 'Successnot '
        #     }
        #
        return self.parse_order(response, {
            'id': id,
            'symbol': symbol,
            'side': side.lower(),
            'status': 'canceled',
        })

    def cancel_all_orders(self, symbols=None, params={}):
        side = self.safe_string(params, 'side')
        if side is None:
            raise ArgumentsRequired(self.id + ' cancelAllOrders() requires an order `side` extra parameter')
        params = self.omit(params, 'side')
        if symbols is None:
            raise ArgumentsRequired(self.id + ' cancelAllOrders() requires a `symbols` argument(a list containing one symbol)')
        else:
            numSymbols = len(symbols)
            if numSymbols != 1:
                raise ArgumentsRequired(self.id + ' cancelAllOrders() requires a `symbols` argument(a list containing one symbol)')
        symbol = symbols[0]
        request = {
            'side': side.upper(),
            'pair': self.market_id(symbol),
        }
        return self.orderPostCancelAllMyOrders(self.extend(request, params))

    def parse_symbol(self, id):
        quote, base = id.split(self.options['symbolSeparator'])
        base = self.safe_currency_code(base)
        quote = self.safe_currency_code(quote)
        return base + '/' + quote

    def parse_order(self, order, market=None):
        #
        # fetchOrders
        #
        #     {
        #         orderId: 20000038,
        #         market: 'BTC',
        #         trade: 'ETH',
        #         volume: 1,
        #         pendingVolume: 1,
        #         orderStatus: False,
        #         rate: 1,
        #         amount: 1,
        #         serviceCharge: 0,
        #         placementDate: '2019-03-19T18:28:43.553',
        #         completionDate: null
        #     }
        #
        # fetchOpenOrders
        #
        #     {
        #         orderId: 20000038,
        #         market: 'BTC',
        #         trade: 'ETH',
        #         volume: 1,
        #         rate: 1,
        #         side: 'SELL',
        #         date: '2019-03-19T18:28:43.553',
        #     }
        #
        # fetchOrderStatus
        #
        #     {
        #         "PendingVolume": 0.7368974,
        #         "Volume": 0.7368974,
        #         "Price": 0.22921771,
        #         "Status": True
        #     }
        #
        id = self.safe_string(order, 'orderId')
        baseId = self.safe_string(order, 'trade')
        quoteId = self.safe_string(order, 'market')
        base = self.safe_currency_code(baseId)
        quote = self.safe_currency_code(quoteId)
        symbol = None
        if base is not None and quote is not None:
            symbol = base + '/' + quote
        completionDate = self.parse8601(self.safe_string(order, 'completionDate'))
        timestamp = self.parse8601(self.safe_string_2(order, 'placementDate', 'date'))
        price = self.safe_float_2(order, 'rate', 'Price')
        amount = self.safe_float_2(order, 'volume', 'Volume')
        cost = self.safe_float(order, 'amount')
        remaining = self.safe_float_2(order, 'pendingVolume', 'PendingVolume')
        filled = None
        if amount is not None and remaining is not None:
            filled = max(amount - remaining, 0)
        if not cost:
            if price and filled:
                cost = price * filled
        if not price:
            if cost and filled:
                price = cost / filled
        status = self.safe_value_2(order, 'orderStatus', 'Status')
        status = 'closed' if status else 'open'
        lastTradeTimestamp = None
        if filled > 0:
            lastTradeTimestamp = completionDate
        if (filled is not None) and(amount is not None):
            if (filled < amount) and(status == 'closed'):
                status = 'canceled'
        feeCost = self.safe_value(order, 'serviceCharge')
        fee = None
        if feeCost is not None:
            fee = {
                'cost': feeCost,
                'currency': quote,
            }
        return {
            'info': order,
            'id': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': lastTradeTimestamp,
            'symbol': symbol,
            'type': 'limit',
            'side': None,
            'price': price,
            'cost': cost,
            'average': None,
            'amount': amount,
            'filled': filled,
            'remaining': remaining,
            'status': status,
            'fee': fee,
        }

    def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        side = self.safe_string(params, 'side')
        if side is None:
            raise ArgumentsRequired(self.id + ' fetchOrders() requires an order `side` extra parameter')
        params = self.omit(params, 'side')
        request = {
            'key': self.apiKey,
            'side': side.upper(),
            # 'orderId': id,
        }
        response = self.orderGetMyOrderHistoryKeySide(self.extend(request, params))
        #
        #     {
        #         status: 'Success',
        #         errorMessage: null,
        #         data: [
        #             {
        #                 orderId: 20000038,
        #                 market: 'BTC',
        #                 trade: 'ETH',
        #                 volume: 1,
        #                 pendingVolume: 1,
        #                 orderStatus: False,
        #                 rate: 1,
        #                 amount: 1,
        #                 serviceCharge: 0,
        #                 placementDate: '2019-03-19T18:28:43.553',
        #                 completionDate: null
        #             },
        #             {
        #                 orderId: 20000037,
        #                 market: 'BTC',
        #                 trade: 'ETH',
        #                 volume: 1,
        #                 pendingVolume: 1,
        #                 orderStatus: True,
        #                 rate: 1,
        #                 amount: 1,
        #                 serviceCharge: 0,
        #                 placementDate: '2019-03-19T18:27:51.087',
        #                 completionDate: '2019-03-19T18:28:16.07'
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data', [])
        market = self.market(symbol) if (symbol is not None) else None
        return self.parse_orders(data, market, since, limit, {
            'side': side.lower(),
        })

    def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        side = self.safe_string(params, 'side', 'ALL')
        params = self.omit(params, 'side')
        market = None
        pair = 'ALL'
        if symbol is not None:
            market = self.market(symbol)
            pair = market['baseId'] + '-' + market['quoteId']
        request = {
            'side': side.upper(),
            'pair': pair,
        }
        response = self.apiGetGetPendingOrders(self.extend(request, params))
        #
        #     {
        #         status: 'Success',
        #         message: 'Successnot ',
        #         data: [
        #             {
        #                 orderId: 20000038,
        #                 market: 'BTC',
        #                 trade: 'ETH',
        #                 volume: 1,
        #                 rate: 1,
        #                 side: 'SELL',
        #                 date: '2019-03-19T18:28:43.553',
        #             },
        #             {
        #                 orderId: 20000039,
        #                 market: 'BTC',
        #                 trade: 'ETH',
        #                 volume: 1,
        #                 rate: 2,
        #                 side: 'SELL',
        #                 date: '2019-03-19T18:48:12.033',
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'data')
        return self.parse_orders(data, market, since, limit)

    def fetch_order(self, id, symbol=None, params={}):
        self.load_markets()
        side = self.safe_string(params, 'side')
        if side is None:
            raise ArgumentsRequired(self.id + ' fetchOrder() requires an order `side` extra parameter')
        params = self.omit(params, 'side')
        id = str(id)
        request = {
            'key': self.apiKey,
            'side': side.upper(),
            'orderId': id,
        }
        response = self.orderGetMyOrderStatusKeySideOrderId(self.extend(request, params))
        #
        #     {
        #         "status": "Success",
        #         "errorMessage": null,
        #         "data": {
        #             "PendingVolume": 0.7368974,
        #             "Volume": 0.7368974,
        #             "Price": 0.22921771,
        #             "Status": True
        #         }
        #     }
        #
        data = self.safe_value(response, 'data')
        return self.extend(self.parse_order(data), {
            'id': id,
            'side': side.lower(),
        })

    def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        self.load_markets()
        side = self.safe_string(params, 'side', 'ALL')
        params = self.omit(params, 'side')
        market = None
        pair = 'ALL'
        if symbol is not None:
            market = self.market(symbol)
            pair = market['id']
        request = {
            'side': side.upper(),
            'pair': pair,
            'orderID': -1,
            'apiKey': self.apiKey,
        }
        response = self.orderGetMyTradeHistory(self.extend(request, params))
        #
        #     {
        #         Status: 'Success',
        #         Message: null,
        #         Data: [
        #             {
        #                 orderId: 20000040,
        #                 market: 'ETH',
        #                 trade: 'MDX',
        #                 volume: 1,
        #                 rate: 2,
        #                 amount: 2,
        #                 serviceCharge: 0.003,
        #                 side: 'SELL',
        #                 date: '2019-03-20T01:47:09.14'
        #             },
        #             {
        #                 orderId: 20000041,
        #                 market: 'ETH',
        #                 trade: 'MDX',
        #                 volume: 0.5,
        #                 rate: 3,
        #                 amount: 1.5,
        #                 serviceCharge: 0.00225,
        #                 side: 'SELL',
        #                 date: '2019-03-20T01:49:20.42'
        #             },
        #             {
        #                 orderId: 20000041,
        #                 market: 'ETH',
        #                 trade: 'MDX',
        #                 volume: 0.25,
        #                 rate: 3,
        #                 amount: 0.75,
        #                 serviceCharge: 0.001125,
        #                 side: 'SELL',
        #                 date: '2019-03-20T01:51:01.307'
        #             }
        #         ]
        #     }
        #
        data = self.safe_value(response, 'Data')
        return self.parse_trades(data, market, since, limit)

    def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        self.load_markets()
        currency = None
        requestCurrency = 'ALL'
        if code is not None:
            currency = self.currency(code)
            requestCurrency = currency['id']
        request = {
            'currency': requestCurrency,
        }
        response = self.apiPostGetDeposits(self.extend(request, params))
        #
        #     {
        #         "status": "Success",
        #         "message": null,
        #         "data": {
        #             "deposits": [
        #                 {
        #                     ?
        #                 }
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        deposits = self.safe_value(data, 'deposits', [])
        return self.parseTransactions(deposits, currency, since, limit)

    def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        self.load_markets()
        currency = None
        requestCurrency = 'ALL'
        if code is not None:
            currency = self.currency(code)
            requestCurrency = currency['id']
        request = {
            'currency': requestCurrency,
        }
        response = self.apiPostGetWithdrawals(self.extend(request, params))
        #
        #     {
        #         "status": "Success",
        #         "message": null,
        #         "data": {
        #             "withdrawals": [
        #                 {
        #                     "withdrawalType": "ETH",
        #                     "withdrawalAddress": "0xE28CE3A999d6035d042D1a87FAab389Cb0B78Db6",
        #                     "withdrawalAmount": 0.071,
        #                     "txnHash": null,
        #                     "withdrawalReqDate": "2018-11-12T09:38:28.43",
        #                     "withdrawalConfirmDate": null,
        #                     "withdrawalStatus": "Pending"
        #                 }
        #             ]
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        withdrawals = self.safe_value(data, 'withdrawals', [])
        return self.parseTransactions(withdrawals, currency, since, limit)

    def parse_transaction_status(self, status):
        statuses = {
            'Pending': 'pending',
        }
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction, currency=None):
        #
        # fetchDeposits
        #
        #     {
        #         ?
        #     }
        #
        # fetchWithdrawals
        #
        #     {
        #         "withdrawalType": "ETH",
        #         "withdrawalAddress": "0xE28CE3A999d6035d042D1a87FAab389Cb0B78Db6",
        #         "withdrawalAmount": 0.071,
        #         "txnHash": null,
        #         "withdrawalReqDate": "2018-11-12T09:38:28.43",
        #         "withdrawalConfirmDate": null,
        #         "withdrawalStatus": "Pending"
        #     }
        #
        id = None
        amount = self.safe_float(transaction, 'withdrawalAmount')
        address = self.safe_string(transaction, 'withdrawalAddress')
        tag = None
        txid = self.safe_string(transaction, 'txnHash')
        updated = self.parse8601(self.safe_value(transaction, 'withdrawalConfirmDate'))
        timestamp = self.parse8601(self.safe_string(transaction, 'withdrawalReqDate', updated))
        type = 'withdrawal' if ('withdrawalReqDate' in list(transaction.keys())) else 'deposit'
        currencyId = self.safe_string(transaction, 'withdrawalType')
        code = self.safe_currency_code(currencyId, currency)
        status = self.parse_transaction_status(self.safe_string(transaction, 'withdrawalStatus'))
        feeCost = None
        if type == 'deposit':
            status = 'ok'
            feeCost = 0
        fee = None
        if feeCost is not None:
            fee = {
                'cost': feeCost,
                'currency': code,
            }
        return {
            'info': transaction,
            'id': id,
            'currency': code,
            'amount': amount,
            'address': address,
            'tag': tag,
            'status': status,
            'type': type,
            'updated': updated,
            'txid': txid,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'fee': fee,
        }

    def parse_deposit_addresses(self, addresses):
        result = []
        ids = list(addresses.keys())
        for i in range(0, len(ids)):
            id = ids[i]
            address = addresses[id]
            currencyId = id.upper()
            currency = self.safe_value(self.currencies_by_id, currencyId)
            result.append(self.parse_deposit_address(address, currency))
        return result

    def fetch_deposit_addresses(self, codes=None, params={}):
        self.load_markets()
        response = self.apiGetListAllAddresses(params)
        #
        #     {
        #         "status": "Success",
        #         "message": null,
        #         "data": {
        #             "btc": "3PLKhwm59C21U3KN3YZVQmrQhoE3q1p1i8",
        #             "eth": "0x8143c11ed6b100e5a96419994846c890598647cf",
        #             "xrp": "rKHZQttBiDysDT4PtYL7RmLbGm6p5HBHfV:3931222419"
        #         }
        #     }
        #
        data = self.safe_value(response, 'data')
        return self.parse_deposit_addresses(data)

    def parse_deposit_address(self, depositAddress, currency=None):
        #
        #     "btc": "3PLKhwm59C21U3KN3YZVQmrQhoE3q1p1i8",
        #     "eth": "0x8143c11ed6b100e5a96419994846c890598647cf",
        #     "xrp": "rKHZQttBiDysDT4PtYL7RmLbGm6p5HBHfV:3931222419"
        #
        parts = depositAddress.split(':')
        address = parts[0]
        self.check_address(address)
        tag = None
        numParts = len(parts)
        if numParts > 1:
            tag = parts[1]
        code = None
        if currency is not None:
            code = currency['code']
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'info': depositAddress,
        }

    def fetch_deposit_address(self, code, params={}):
        self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
        }
        response = self.apiPostGenerateAddress(self.extend(request, params))
        #
        #     {
        #         status: 'Success',
        #         message: '',
        #         data: {
        #             address: '0x13a1ac355bf1be5b157486f619169cf7f9ffed4e'
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        address = self.safe_string(data, 'address')
        return self.parse_deposit_address(address, currency)

    def create_deposit_address(self, code, params={}):
        self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
        }
        response = self.apiPostGenerateAddress(self.extend(request, params))
        #
        #     {
        #         status: 'Success',
        #         message: '',
        #         data: {
        #             address: '0x13a1ac355bf1be5b157486f619169cf7f9ffed4e'
        #         }
        #     }
        #
        data = self.safe_value(response, 'data', {})
        address = self.safe_string(data, 'address')
        return self.parse_deposit_address(address, currency)

    def withdraw(self, code, amount, address, tag=None, params={}):
        self.check_address(address)
        self.load_markets()
        currency = self.currency(code)
        withdrawalRequest = {
            'currency': currency['id'],
            'amount': float(amount),
            'address': address,
            # 'addressTag': null,
        }
        if tag is not None:
            withdrawalRequest['addressTag'] = tag
        withdrawalResponse = self.apiPostRequestWithdraw(self.extend(withdrawalRequest, params))
        #
        #     {
        #         "status": "Success",
        #         "message": null,
        #         "data": {
        #             "withdrawalId": "E26AA92F-F526-4F6C-85FD-B1EA9B1B118D"
        #         }
        #     }
        #
        data = self.safe_value(withdrawalResponse, 'data', {})
        id = self.safe_string(data, 'withdrawalId')
        otp = None
        if self.twofa is not None:
            otp = self.oath()
        otp = self.safe_string(params, 'emailToken', otp)
        if otp is None:
            raise AuthenticationError(self.id + ' signIn() requires self.twofa credential or a one-time 2FA "emailToken" parameter')
        confirmationRequest = {
            'EmailToken': otp,
        }
        confirmationResponse = self.apiPostRequestWithdrawConfirmation(self.extend(confirmationRequest, params))
        timestamp = self.milliseconds()
        return {
            'info': [withdrawalResponse, confirmationResponse],
            'id': id,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'currency': code,
            'amount': amount,
            'address': address,
            'tag': tag,
            'addressFrom': None,
            'tagFrom': None,
            'addressTo': address,
            'tagTo': tag,
            'type': 'withdrawal',
            'updated': None,
            'txid': None,
            'status': 'pending',
            'fee': None,
        }

    def sign(self, path, api='api', method='GET', params={}, headers=None, body=None):
        url = self.implode_params(self.urls['api'], {
            'hostname': self.hostname,
        })
        if api != 'token':
            url += '/' + self.safe_string(self.options['api'], api, api)
        url += '/' + self.implode_params(path, params)
        query = self.omit(params, self.extract_params(path))
        # isPublic = self.safe_value(self.options['api'], api, True)
        if api == 'market' or api == 'settings' or api == 'public':
            if method == 'POST':
                body = self.json(query)
                headers = {
                    'Content-Type': 'application/json',
                }
            else:
                if query:
                    url += '?' + self.urlencode(query)
        elif api == 'token':
            body = self.urlencode(query)
            headers = {
                'Content-Type': 'application/x-www-form-urlencoded',
            }
        else:
            self.check_required_credentials()
            query = self.keysort(self.extend({
                'timestamp': self.seconds(),
            }, query))
            auth = self.urlencode(query)
            secret = self.options['secret'] if (api == 'api') else self.secret
            signature = self.hmac(self.encode(auth), self.encode(secret), hashlib.sha512)
            headers = {
                'HMAC': signature.upper(),
            }
            if api == 'api':
                token = self.safe_string(self.options, 'accessToken')
                if token is None:
                    raise AuthenticationError(self.id + ' ' + path + ' endpoint requires an `accessToken` option or a prior call to signIn() method')
                expires = self.safe_integer(self.options, 'expires')
                if expires is not None:
                    if self.milliseconds() >= expires:
                        raise AuthenticationError(self.id + ' accessToken expired, supply a new `accessToken` or call signIn() method')
                tokenType = self.safe_string(self.options, 'tokenType', 'bearer')
                headers['Authorization'] = tokenType + ' ' + token
            if method == 'POST':
                body = self.json(query)
                headers['Content-Type'] = 'application/json'
                headers['apiKey'] = self.apiKey
            elif method == 'GET':
                if query:
                    url += '?' + self.urlencode(query)
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, httpCode, reason, url, method, headers, body, response, requestHeaders, requestBody):
        if not response:
            return  # fallback to default error handler
        #
        #     {"Status":"Error","Message":"Exception_Insufficient_Funds","Data":"Insufficient Funds."}
        #     {"status":"Error","errorMessage":"Invalid Market_Currency pairnot ","data":null}
        #     {"status":"BadRequest","message":"Exception_BadRequest","data":"Invalid Payload"}
        #
        #
        status = self.safe_string_2(response, 'status', 'Status')
        if (status is not None) and(status != 'Success'):
            message = self.safe_string_2(response, 'errorMessage', 'Message')
            message = self.safe_string(response, 'message', message)
            feedback = self.id + ' ' + self.json(response)
            exact = self.exceptions['exact']
            if message in exact:
                raise exact[message](feedback)
            broad = self.exceptions['broad']
            broadKey = self.findBroadlyMatchedKey(broad, message)
            if broadKey is not None:
                raise broad[broadKey](feedback)
            raise ExchangeError(feedback)  # unknown message
