text: # -*- 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 PermissionDenied
  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': 'v2',
              'rateLimit': 1500,
              'certified': True,
              # 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,
                  'fetchClosedOrders': 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',
                          'v2/PlaceOrder',
                          'v2/my-order-history',
                          'v2/my-order-status',
                          'v2/my-trade-history',
                          'v2/cancel-my-order',
                          'v2/cancel-all-my-orders',
                          'v2/GetDeposits',
                          'v2/GetWithdrawals',
                          'v2/GenerateAddress',
                          'v2/Get_User_Withdrawal_Limits',
                          'v2/ListAllAddresses',
                          'v2/RequestWithdraw',
                          'v2/RequestWithdrawConfirmation',
                      ],
                  },
              },
              'fees': {
                  'trading': {
                      'tierBased': False,
                      'percentage': True,
                      'maker': 0.00,
                      'taker': 0.001,
                  },
              },
              '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"}
                      'Blacklisted IP Address': PermissionDenied,  # {"status":"Error","errorMessage":"Blacklisted IP Address","data":null}
                      'Trade_Invalid_Size': InvalidOrder,  # {"status":"Error","errorMessage":"Trade_Invalid_Size","data":"Invalid trade size."}
                  },
                  '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={}):
          response = self.settingsGetGetSettings(params)
          #
          #     {
          #         status: "Success",
          #         message: "Successnot ",
          #         data: {
          #             server_Time_UTC:   "1567260547",
          #             default_Pair:   "ETH_BTC",
          #             disable_RM:   "False",
          #             disable_TDM:   "False",
          #             enable_TDM_Pay_IN_Exchange_Token:   "False",
          #             disable_2FA:   "False",
          #             disable_Login:   "False",
          #             enable_AeraPass:   "False",
          #             enable_InstaTrade:   "False",
          #             enable_CopyTrade:   "False",
          #             auto_Sell:   "False",
          #             enable_CryptoForecasting:   "False",
          #             enable_Simplex:   "False",
          #             aeraPass_Url:   "False",
          #             logo_Url:   "https://trade.mandalaex.com/assets/logo.png",
          #             favIcon_Url:   "favicon.ico",
          #             navBarLogo_Url:   "https://trade.mandalaex.com/assets/logo.png",
          #             fiat_List:   "USD,RUB,AUD,EUR,ARS,CAD,COP,TRY,UGX,BRL",
          #             exchange_IEO_Coins:   "XYZ,ABC",
          #             mfa_Type: {
          #                 name: "Google",
          #                 codeLength:  6,
          #                 downloadLink: "google.com"
          #             },
          #             _CoName:   "Green Donuts",
          #             exchangeName:   "Green Donuts",
          #             _xrp_address:   "rBsqK5rzMvo5a4ViVoPudJkXd7NNPXKE9f",
          #             tdM_Token_Name:   "MDX",
          #             enable_DustConversion:   "True",
          #             exchange_SupportDesk_URL:   "https://modulushelp.freshdesk.com",
          #             kyc: {
          #                 enable_GoKYC: "False",
          #                 enable_SumSub_iframe: "True"
          #             },
          #             markets: ["BTC", "ETH", "PAX"],
          #             customErrorMessages: {
          #                 exception_General: "Our servers are experiencing some glitch, please try again later.",
          #                 exception_Email: "Unable to send an email. try again later",
          #                 exception_BadRequest: "Invalid Payload",
          #                 exception_HMAC_Missing: "Must provide the HMAC of the request body.",
          #                 exception_HMAC_Validation: "HMAC validation failed.",
          #                 exception_TimeStamp: "Invalid timestamp.",
          #                 exception_RecvWindow: "Invalid recvWindow value.",
          #                 exception_TimeStamp_Window_Invalid: "Timestamp for self request is outside of the recvWindow.",
          #                 exception_Body_Missing: "Must provide the request body.",
          #                 exception_Invalid_Body: "Request body was invalid.",
          #                 exception_Invalid_Address: "Invalid Address.",
          #                 exception_Invalid_OrderSide: "Invalid parameter \'side\', must be \'BUY\' or \'SELL\'",
          #                 exception_Invalid_Orderid: "The OrderId or clientOrderId is required",
          #                 exception_Invalid_CurrencyName: "Invalid Currency name",
          #                 exception_Invalid_XRP_DTag_Required: "Must provide the addressTag for XRP address.",
          #                 order_Trade_Suspended: "Sorrynot  Trade Suspended.",
          #                 order_Invalid_Order_Type: "Invalid Order Type.",
          #                 order_Invalid_Client_Order_ID: "Order with self client order id already exists.",
          #                 order_Invalid_Pair: "Invalid Pair.",
          #                 order_Invalid_Trade_Volume: "Invalid Trade Volume.",
          #                 order_Cannot_Be_Served: "Volume Order cannot be served.",
          #                 order_Invalid_Stop_Price: "Invalid Stop Price.",
          #                 order_Invalid_Trade_Price: "Invalid Trade Price.",
          #                 order_Invalid_Rate_Volume: "Invalid Rate/Volume.",
          #                 exception_Link_Expired: "The current page url expired.",
          #                 exception_Insufficient_Funds: "Insufficient Funds.",
          #                 exception_Coin_Maintenance: "Sorrynot  Coin under maintenance.",
          #                 exception_Account_Suspended: "Sorrynot  Account Suspended.",
          #                 address_No_Unused_Address: "No unused address available.",
          #                 withdrawal_Invalid_Amount: "Sorrynot  Invalid Withdrawal Amount.",
          #                 withdrawal_Suspended: "Sorrynot  Withdrawals Suspended",
          #                 success_General: "Successnot ",
          #                 success_NoRowsFound: "No Rows Foundnot ",
          #                 success_Saved: "Details Saved Successfully.",
          #                 success_Deleted: "Details deleted Successfully.",
          #                 error_Disabled_BY_Admin: "Feature disabled by admin.",
          #                 failure_General: "Something went wrong. try again later",
          #                 failure_GME: "GME Busy.. try later.",
          #                 request_Invalid: "Invalid request.",
          #                 trade_CurrencyType_Missing: "Must provide the trade currency.",
          #                 trade_TradeType_Missing: "Must provide the trade type.",
          #                 trade_TradeType_Invalid: "Invalid parameter \'type\'. Options are \'MARKET\', \'STOPLIMIT\' or \'LIMIT\'",
          #                 trade_Volume_Invalid: "Invalid trade volume.",
          #                 trade_Rate_Invalid: "Invalid trade rate.",
          #                 trade_Stop_Invalid: "Invalid stop rate.",
          #                 trade_MarketType_Missing: "Must provide the market currency.",
          #                 trade_Invalid_Size: "Invalid trade size.",
          #                 withdrawal_Error: "Must provide the market currency.",
          #                 facility_Suspended: "This facility is blocked for your account.",
          #                 feature_Disabled: "This feature is currently disabled.",
          #                 coin_Maintenance: "Coin under maintenance.",
          #                 insufficientFunds: "Insufficient funds.",
          #                 signUp_Invalid_Referrer: "Referral Id does not exists.",
          #                 signUp_Duplicate_Mobile: "Mobile number already exists.",
          #                 signUp_Duplicate_Email: "Email already exists.",
          #                 signUp_Phone_Error: "Phone already exists.",
          #                 signIn_Authentication_Failed: "Invalid login credentials.",
          #                 signIn_Invalid_OTP: "Invalid OTP",
          #                 signIn_Missing_OTP: "Must provide the otp.",
          #                 signIn_Unvarified_Email: "Email Unverified. Please reset your password.",
          #                 signIn_Suspended_Account: "Account Suspended. Contact Support.",
          #                 changePassword_Same_Error: "Your new password cannot be same as old password.",
          #                 changePassword_Invalid_OldPassword: "Password provided doesn\'t match our records.",
          #                 gAuth_Required: "Must provide the Google Auth Code.",
          #                 gAuth_Enabled_Mandatory: "Enable Google two-factor authentication to use the endpoint.",
          #                 gAuth_Two_Factor_Error: "Invalid Google 2FA Code.",
          #                 gAuth_Two_Factor_Already_Enabled: "Google 2FA is already enabled.",
          #                 kyC_Not_Approved: "You must be KYC approved in order to use self feature.",
          #                 kyC_Custom_Error: "",
          #                 kyC_Provider_Error: "KYC service provider not found.",
          #                 kyC_Upload_Error: "Unable to Upload KYC.",
          #                 kyC_Image_NotFound: "No Image Foundnot ",
          #                 kyC_Approved_Error: "KYC already approved.",
          #                 kyC_Pending_Error: "Your KYC is processing. We\'ll notify once it\'s processed.",
          #                 kyC_Invalid_CID_Email: "Email or CID does not exists.",
          #                 kyC_Not_Submitted: "KYC not submitted yet.",
          #                 kyC_Form_NotFound: "KYC not submitted yet.",
          #                 kyC_Form_Corrupted: "KYC form is corrupted.",
          #                 kyC_Server_Down: "KYC server down.",
          #                 payment_Amount_Missing: "There must be some amount.",
          #                 payment_Gateway_Invalid: "Invalid payment gateway.",
          #                 apI_Inavalid_IP: "Invalid IP Address(es)",
          #                 apI_Key_Type_Required: "Invalid Key type. Allowed options are \'trade\',\'readonly\',\'all\'",
          #                 apI_Secretkey_Required: "The Secret Key is missing in the Header.",
          #                 invalid_Currency: "Invalid currency.",
          #                 invalid_Fiat_PG_Currency: "Invalid Fiat PG currency.",
          #                 depositDisabled: "Deposit disabled for self currency.",
          #                 withdrawalLimitReached: "Withdrawal limit reached.",
          #                 invalidLanguage: "Language not found.",
          #                 transitiveFollowing: "Transitive-following not allowed.",
          #                 selfFollowing: "Self-following not allowed.",
          #                 invalidProTraderID: "Invalid ProTrader UserID.",
          #                 multipleFollowing: "Multiple-following not allowed.",
          #                 weakPassword: "Password must have 8 characters with at least 1 uppercase letter and 1 number.",
          #                 withdrawalPending: "another withdrawal is pending already for same currency",
          #                 depositPending: "another fiat deposit request is pending already for same currency",
          #                 withdrawalLimitReachedExclusive: "{curr} withdrawal limit exceeds.",
          #                 withdrawalLimitReachedAggregate: "Overall {curr} withdrawal limit exceeds.",
          #                 readOnlyToken: "Read-only access token doesn\'t have the permission to perform self operation.",
          #                 marginCall: "Placing new order is not allowed while a margin call is pending.",
          #                 force_Liquidation: "Placing new order is not allowed while a force liquidation in process.",
          #                 feature_Unavailable: "This feature is not available for your account.",
          #                 chainAlysis_Blacklisted: "AML Risk Assessment Failed for self transaction."
          #             },
          #             themes:    null,
          #             trade_setting: [
          #                 {
          #                     coinName: "BCH",
          #                     marketName: "BTC",
          #                     minTickSize:  1e-8,
          #                     minTradeAmount:  1e-8,
          #                     minOrderValue:  0.01,
          #                     tradeEnabled:  True
          #                 },
          #                 {
          #                     coinName: "MDX",
          #                     marketName: "XRP",
          #                     minTickSize:  1e-8,
          #                     minTradeAmount:  1e-8,
          #                     minOrderValue:  0.01,
          #                     tradeEnabled:  True
          #                 }
          #             ],
          #             seo: {
          #                 google_Analytics_ID:   "None",
          #                 google_Tag_Manager:   "None",
          #                 reCaptchaKey:   "None",
          #                 meta_Tags: []
          #             },
          #             market_groups: []
          #         }
          #     }
          #
          result = []
          data = self.safe_value(response, 'data', {})
          markets = self.safe_value(data, 'trade_setting')
          for i in range(0, len(markets)):
              market = markets[i]
              baseId = self.safe_string(market, 'coinName')
              quoteId = self.safe_string(market, 'marketName')
              id = quoteId + '_' + baseId
              base = self.safe_currency_code(baseId)
              quote = self.safe_currency_code(quoteId)
              symbol = base + '/' + quote
              minAmount = self.safe_float(market, 'minTradeAmount')
              minPrice = self.safe_float(market, 'minTickSize')
              precision = {
                  'amount': self.precision_from_string(self.number_to_string(minAmount)),
                  'price': self.precision_from_string(self.number_to_string(minPrice)),
              }
              active = self.safe_value(market, 'tradeEnabled', True)
              result.append({
                  'id': id,
                  'symbol': symbol,
                  'base': base,
                  'quote': quote,
                  'baseId': baseId,
                  'quoteId': quoteId,
                  'active': active,
                  'info': market,
                  'precision': precision,
                  'limits': {
                      'amount': {
                          'min': minAmount,
                          'max': None,
                      },
                      'price': {
                          'min': minPrice,
                          'max': None,
                      },
                      'cost': {
                          'min': self.safe_float(market, 'minOrderValue'),
                          'max': None,
                      },
                  },
              })
          return result
  
      def fetch_balance(self, params={}):
          self.load_markets()
          request = {
              'currency': 'ALL',
          }
          response = self.orderPostGetBalance(self.extend(request, params))
          #
          #     {
          #         status: 'Success',
          #         errorMessage: 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
          timestamp = self.sum(since, offset)
          request = {
              'interval': self.timeframes[timeframe],
              'baseCurrency': market['baseId'],
              'quoteCurrency': market['quoteId'],
              'limit': limit,
              'timestamp': timestamp,
          }
          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.orderPostV2PlaceOrder(self.extend(request, params))
          #
          #     {
          #         status: 'Success',
          #         errorMessage: 'Success_General',
          #         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', 'ALL')
          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.orderPostV2CancelMyOrder(self.extend(request, params))
          #
          #     {
          #         status: "Success",
          #         errorMessage: "Success_General",
          #         data: "Request accepted"
          #     }
          #
          return self.extend(self.parse_order(response), {
              'id': id,
              'symbol': symbol,
              'status': 'canceled',
          })
  
      def cancel_all_orders(self, symbols=None, params={}):
          side = self.safe_string(params, 'side', 'ALL')
          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.orderPostV2CancelAllMyOrders(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_status(self, status):
          statuses = {
              'Pending': 'open',
              'Filled': 'closed',
              'Paritally-Filled': 'open',  # an actual typo in the response
              'Partially-Filled': 'open',  # a correct string in case it's fixed
              'Cancelled': 'canceled',
          }
          return self.safe_string(statuses, status, status)
  
      def parse_order(self, order, market=None):
          #
          # fetchClosedOrders, fetchOpenOrders
          #
          #     {
          #         "orderId":29894309,
          #         "market":"BTC",
          #         "trade":"MDX",
          #         "volume":370.00000000,
          #         "pendingVolume":370.00000000,
          #         "orderStatus":false,
          #         "rate":0.00019530,
          #         "amount":0.07226100,
          #         "serviceCharge":0.00000000,
          #         "placementDate":"2019-07-31T22:14:30.193",
          #         "completionDate":null,
          #         "side":"Buy"
          #     }
          #
          # fetchOrder
          #
          #     {
          #         "orderId":"29885793",
          #         "side":"ALL",
          #         "Volume":350.00000000,
          #         "PendingVolume":300.00000000,
          #         "Price":0.00020050,
          #         "Status":false,
          #         "status_string":"Paritally-Filled"
          #     }
          #
          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.parse_order_status(self.safe_string(order, 'status_string'))
          if status is None:
              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,
              }
          side = self.safe_string_lower(order, 'side')
          return {
              'info': order,
              'id': id,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'lastTradeTimestamp': lastTradeTimestamp,
              'symbol': symbol,
              'type': 'limit',
              'side': side,
              '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
          #                 side: 'Buy'
          #             },
          #             {
          #                 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'
          #                 side: 'Buy'
          #             }
          #         ]
          #     }
          #
          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_closed_orders(self, symbol=None, since=None, limit=None, params={}):
          self.load_markets()
          side = self.safe_string(params, 'side', 'ALL')  # required by the endpoint on the exchange side
          params = self.omit(params, 'side')
          market = None
          request = {
              'openOrders': False,  # True returns open orders only, False returns filled & cancelled orders only, default is False
              'side': side.upper(),  # required by the endpoint on the exchange side
          }
          if symbol is not None:
              market = self.market(symbol)
              request['pair'] = market['baseId'] + '-' + market['quoteId']
          response = self.orderPostV2MyOrderHistory(self.extend(request, params))
          #
          #     {
          #         "status":"Success",
          #         "errorMessage":null,
          #         "data":[
          #             {
          #                 "orderId":20991907,
          #                 "market":"BTC",
          #                 "trade":"ETH",
          #                 "volume":1.00000000,
          #                 "pendingVolume":0.00000000,
          #                 "orderStatus":true,
          #                 "rate":1.00000000,
          #                 "amount":1.00000000,
          #                 "serviceCharge":0.00000000,
          #                 "placementDate":"2019-07-17T23:48:43.357",
          #                 "completionDate":"2019-07-17T23:49:14.733",
          #                 "side":"Buy"
          #             },
          #             {
          #                 "orderId":20000048,
          #                 "market":"ETH",
          #                 "trade":"MDX",
          #                 "volume":10.00000000,
          #                 "pendingVolume":10.00000000,
          #                 "orderStatus":true,
          #                 "rate":3.00000000,
          #                 "amount":30.00000000,
          #                 "serviceCharge":0.00000000,
          #                 "placementDate":"2019-06-23T18:16:06.2",
          #                 "completionDate":"2019-06-23T18:16:06.247",
          #                 "side":"Buy"
          #             }
          #         ]
          #     }
          #
          data = self.safe_value(response, 'data')
          return self.parse_orders(data, market, since, limit)
  
      def fetch_open_orders(self, symbol=None, since=None, limit=None, params={}):
          self.load_markets()
          side = self.safe_string(params, 'side', 'ALL')  # required by the endpoint on the exchange side
          params = self.omit(params, 'side')
          market = None
          request = {
              'openOrders': True,  # True returns open orders only, False returns filled & cancelled orders only, default is False
              'side': side.upper(),  # required by the endpoint on the exchange side
          }
          if symbol is not None:
              market = self.market(symbol)
              request['pair'] = market['baseId'] + '-' + market['quoteId']
          response = self.orderPostV2MyOrderHistory(self.extend(request, params))
          #
          #     {
          #         "status":"Success",
          #         "errorMessage":null,
          #         "data":[
          #             {
          #                 "orderId":29894309,
          #                 "market":"BTC",
          #                 "trade":"MDX",
          #                 "volume":370.00000000,
          #                 "pendingVolume":370.00000000,
          #                 "orderStatus":false,
          #                 "rate":0.00019530,
          #                 "amount":0.07226100,
          #                 "serviceCharge":0.00000000,
          #                 "placementDate":"2019-07-31T22:14:30.193",
          #                 "completionDate":null,
          #                 "side":"Buy"
          #             }
          #         ]
          #     }
          #
          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', 'ALL')
          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.orderPostV2MyOrderStatus(self.extend(request, params))
          #
          #     {
          #         "status":"Success",
          #         "errorMessage":null,
          #         "data":{
          #             "orderId":"29885793",
          #             "side":"ALL",
          #             "Volume":350.00000000,
          #             "PendingVolume":300.00000000,
          #             "Price":0.00020050,
          #             "Status":false,
          #             "status_string":"Paritally-Filled"
          #         }
          #     }
          #
          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()
          market = None
          pair = 'ALL'  # required by the endpoint on the exchange side
          if symbol is not None:
              market = self.market(symbol)
              pair = market['id']
          request = {
              'pair': pair,  # required by the endpoint on the exchange side
              'orderID': -1,
              'apiKey': self.apiKey,
          }
          response = self.orderPostV2MyTradeHistory(self.extend(request, params))
          #
          #     {
          #         status: 'Success',
          #         errorMessage: 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.orderPostV2GetDeposits(self.extend(request, params))
          #
          #     {
          #         "status":"Success",
          #         "errorMessage":"Successnot ",
          #         "data":{
          #             "Deposits":[
          #                 {
          #                     "DepositType": "BTC",
          #                     "DepositAddress": "2N4WaF2q7Gncazx7qDuEC13TNE6QicjgtaN",
          #                     "DepositAmount": 1258.01337584,
          #                     "TXNHash": "c71c0a24c63d43d077e238bdad7efc7a5b312f542caf097a6cd36f4fc5e15249",
          #                     "DepositReqDate": "2019-07-20T08:08:05.413",
          #                     "DepositConfirmDate": "2019-07-20T08:08:05.413",
          #                     "CurrentTxnCount": 121914,
          #                     "RequiredTxnCount": 5,
          #                     "ExplorerURL": "https://live.blockcypher.com/btc-testnet/tx/c71c0a24c63d43d077e238bdad7efc7a5b312f542caf097a6cd36f4fc5e15249"
          #                 }
          #             ]
          #         }
          #     }
          #
          data = self.safe_value(response, 'data', {})
          deposits = self.safe_value(data, 'Deposits', [])
          return self.parse_transactions(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.orderPostV2GetWithdrawals(self.extend(request, params))
          #
          #     {
          #         "status": "Success",
          #         "errorMessage": "Successnot ",
          #         "data": {
          #             "Withdrawals": [
          #                 {
          #                     "WithdrawalType": "BTC",
          #                     "WithdrawalAddress": "mtHpWL1nyQa1CCTCSMD6aV1ycEHWCWD3WK",
          #                     "WithdrawalAmount": 0.00990099,
          #                     "TXNHash": "eb3a27b027d4004ff3fdad0b6f5d2dded9078e31527fb6fd5d18e0abf43e4e00",
          #                     "WithdrawalReqDate": "2019-06-24T13:04:13.76",
          #                     "WithdrawalConfirmDate": "2019-06-24T13:04:31.51",
          #                     "WithdrawalStatus": "Processed",
          #                     "RejectReason": "",
          #                     "ExplorerURL": "https://live.blockcypher.com/btc-testnet/tx/eb3a27b027d4004ff3fdad0b6f5d2dded9078e31527fb6fd5d18e0abf43e4e00"
          #                 },
          #             ]
          #         }
          #     }
          #
          data = self.safe_value(response, 'data', {})
          withdrawals = self.safe_value(data, 'Withdrawals', [])
          return self.parse_transactions(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
          #
          #     {
          #         "DepositType": "BTC",
          #         "DepositAddress": "2N4WaF2q7Gncazx7qDuEC13TNE6QicjgtaN",
          #         "DepositAmount": 1258.01337584,
          #         "TXNHash": "c71c0a24c63d43d077e238bdad7efc7a5b312f542caf097a6cd36f4fc5e15249",
          #         "DepositReqDate": "2019-07-20T08:08:05.413",
          #         "DepositConfirmDate": "2019-07-20T08:08:05.413",
          #         "CurrentTxnCount": 121914,
          #         "RequiredTxnCount": 5,
          #         "ExplorerURL": "https://live.blockcypher.com/btc-testnet/tx/c71c0a24c63d43d077e238bdad7efc7a5b312f542caf097a6cd36f4fc5e15249"
          #     }
          #
          # fetchWithdrawals
          #
          #     {
          #         "WithdrawalType": "BTC",
          #         "WithdrawalAddress": "mtHpWL1nyQa1CCTCSMD6aV1ycEHWCWD3WK",
          #         "WithdrawalAmount": 0.00990099,
          #         "TXNHash": "eb3a27b027d4004ff3fdad0b6f5d2dded9078e31527fb6fd5d18e0abf43e4e00",
          #         "WithdrawalReqDate": "2019-06-24T13:04:13.76",
          #         "WithdrawalConfirmDate": "2019-06-24T13:04:31.51",
          #         "WithdrawalStatus": "Processed",
          #         "RejectReason": "",
          #         "ExplorerURL": "https://live.blockcypher.com/btc-testnet/tx/eb3a27b027d4004ff3fdad0b6f5d2dded9078e31527fb6fd5d18e0abf43e4e00"
          #     }
          #
          id = None
          amount = self.safe_float_2(transaction, 'WithdrawalAmount', 'DepositAmount')
          txid = self.safe_string(transaction, 'TXNHash')
          updated = self.parse8601(self.safe_string_2(transaction, 'WithdrawalConfirmDate', 'DepositConfirmDate'))
          timestamp = self.parse8601(self.safe_string_2(transaction, 'WithdrawalReqDate', 'DepositReqDate', updated))
          type = 'withdrawal' if ('WithdrawalReqDate' in list(transaction.keys())) else 'deposit'
          currencyId = self.safe_string(transaction, 'WithdrawalType', 'DepositType')
          code = self.safe_currency_code(currencyId, currency)
          currency = self.currency(code)
          addressString = self.safe_string_2(transaction, 'WithdrawalAddress', 'DepositAddress')
          addressStructure = self.parse_address(addressString, currency)
          address = addressStructure['address']
          addressFrom = None
          addressTo = address
          tag = addressStructure['tag']
          tagFrom = None
          tagTo = tag
          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,
              'addressFrom': addressFrom,
              'addressTo': addressTo,
              'tag': tag,
              'tagFrom': tagFrom,
              'tagTo': tagTo,
              'status': status,
              'type': type,
              'updated': updated,
              'txid': txid,
              'timestamp': timestamp,
              'datetime': self.iso8601(timestamp),
              'fee': fee,
          }
  
      def parse_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_address(address, currency))
          return result
  
      def parse_address(self, depositAddress, currency=None):
          #
          #     "btc": "3PLKhwm59C21U3KN3YZVQmrQhoE3q1p1i8",
          #     "eth": "0x8143c11ed6b100e5a96419994846c890598647cf",
          #     "xrp": "rKHZQttBiDysDT4PtYL7RmLbGm6p5HBHfV?dt=3931222419"
          #
          info = self.safe_value(currency, 'info', {})
          address = depositAddress
          separator = self.safe_value(info, 'addressSeparator', '?dt=')
          tag = None
          if len(separator) > 0:
              parts = depositAddress.split(separator)
              address = parts[0]
              self.check_address(address)
              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_addresses(self, codes=None, params={}):
          self.load_markets()
          response = self.orderPostV2ListAllAddresses(params)
          #
          #     {
          #         "status": "Success",
          #         "errorMessage": null,
          #         "data": {
          #             "btc": "3PLKhwm59C21U3KN3YZVQmrQhoE3q1p1i8",
          #             "eth": "0x8143c11ed6b100e5a96419994846c890598647cf",
          #             "xrp": "rKHZQttBiDysDT4PtYL7RmLbGm6p5HBHfV?dt=3931222419"
          #         }
          #     }
          #
          data = self.safe_value(response, 'data')
          return self.parse_addresses(data)
  
      def generate_deposit_address(self, code, params={}):
          # a common implementation of fetchDepositAddress and createDepositAddress
          self.load_markets()
          currency = self.currency(code)
          request = {
              'currency': currency['id'],
          }
          response = self.orderPostV2GenerateAddress(self.extend(request, params))
          #
          #     {
          #         status: 'Success',
          #         errorMessage: '',
          #         data: {
          #             Address: '0x13a1ac355bf1be5b157486f619169cf7f9ffed4e'
          #         }
          #     }
          #
          data = self.safe_value(response, 'data', {})
          address = self.safe_string(data, 'Address')
          return self.parse_address(address, currency)
  
      def fetch_deposit_address(self, code, params={}):
          return self.generate_deposit_address(code, params)
  
      def create_deposit_address(self, code, params={}):
          return self.generate_deposit_address(code, params)
  
      def withdraw(self, code, amount, address, tag=None, params={}):
          self.check_address(address)
          self.load_markets()
          currency = self.currency(code)
          gauth_code = None
          if self.twofa is not None:
              gauth_code = self.oath()
          gauth_code = self.safe_string(params, 'gauth_code', gauth_code)
          if gauth_code is None:
              raise ArgumentsRequired(self.id + ' withdraw() requires a `self.twofa` key or a 2FA code in the `gauth_code` parameter as a string.')
          params = self.omit(params, 'gauth_code')
          request = {
              'currency': currency['id'],
              'amount': float(amount),
              'address': address,
              'gauth_code': gauth_code,
          }
          if tag is not None:
              request['addressTag'] = tag
          response = self.apiPostRequestWithdraw(self.extend(request, params))
          #
          #     {
          #         "status": "Success",
          #         "message": null,
          #         "data": {
          #             "withdrawalId": "E26AA92F-F526-4F6C-85FD-B1EA9B1B118D"
          #         }
          #     }
          #
          data = self.safe_value(response, 'data', {})
          id = self.safe_string(data, 'withdrawalId')
          timestamp = None
          return {
              'info': response,
              '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
  