# -*- 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 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',
            },
        })

    async 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 = await 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 = await 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

    async 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 = await self.settingsGetCurrencySettings(params)
            self.options['fetchCurrencies'] = self.extend(options, {
                'response': response,
                'timestamp': now,
            })
        return self.safe_value(self.options['fetchCurrencies'], 'response')

    async def fetch_currencies(self, params={}):
        response = await 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

    async def fetch_markets(self, params={}):
        currenciesResponse = await self.fetch_currencies_from_cache(params)
        currencies = self.safe_value(currenciesResponse, 'data', [])
        currenciesById = self.index_by(currencies, 'shortName')
        response = await 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

    async def fetch_balance(self, params={}):
        await self.load_markets()
        request = {
            'currency': 'ALL',
        }
        response = await 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)

    async def fetch_order_book(self, symbol, limit=None, params={}):
        await self.load_markets()
        if limit is None:
            limit = 10
        request = {
            'symbol': self.market_id(symbol),
            'limit': limit,
        }
        response = await 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,
        }

    async def fetch_tickers(self, symbols=None, params={}):
        await self.load_markets()
        response = await 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

    async def fetch_ticker(self, symbol, params={}):
        await self.load_markets()
        request = {
            'marketId': self.market_id(symbol),
        }
        response = await 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,
        }

    async def fetch_trades(self, symbol, since=None, limit=None, params={}):
        await self.load_markets()
        market = self.market(symbol)
        request = {
            'marketId': market['id'],
        }
        # self endpoint returns last 50 trades
        response = await 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'),
        ]

    async def fetch_ohlcv(self, symbol, timeframe='1m', since=None, limit=None, params={}):
        await 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 = await 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)

    async def create_order(self, symbol, type, side, amount, price=None, params={}):
        await 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 = await 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',
        })

    async def cancel_order(self, id, symbol=None, params={}):
        await 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 = await 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',
        })

    async 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 await 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,
        }

    async def fetch_orders(self, symbol=None, since=None, limit=None, params={}):
        await 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 = await 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(),
        })

    async def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
        await 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 = await 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)

    async def fetch_order(self, id, symbol=None, params={}):
        await 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 = await 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(),
        })

    async def fetch_my_trades(self, symbol=None, since=None, limit=None, params={}):
        await 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 = await 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)

    async def fetch_deposits(self, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        currency = None
        requestCurrency = 'ALL'
        if code is not None:
            currency = self.currency(code)
            requestCurrency = currency['id']
        request = {
            'currency': requestCurrency,
        }
        response = await 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)

    async def fetch_withdrawals(self, code=None, since=None, limit=None, params={}):
        await self.load_markets()
        currency = None
        requestCurrency = 'ALL'
        if code is not None:
            currency = self.currency(code)
            requestCurrency = currency['id']
        request = {
            'currency': requestCurrency,
        }
        response = await 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

    async def fetch_deposit_addresses(self, codes=None, params={}):
        await self.load_markets()
        response = await 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,
        }

    async def fetch_deposit_address(self, code, params={}):
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
        }
        response = await 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)

    async def create_deposit_address(self, code, params={}):
        await self.load_markets()
        currency = self.currency(code)
        request = {
            'currency': currency['id'],
        }
        response = await 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)

    async def withdraw(self, code, amount, address, tag=None, params={}):
        self.check_address(address)
        await 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 = await 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 = await 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
