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

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

from ccxt_zagrava.async_support.base.exchange import Exchange
from ccxt_zagrava.abstract.tokocrypto import ImplicitAPI
import hashlib
import json
from ccxt_zagrava.base.types import Balances, Currency, Int, Market, Num, Order, OrderBook, OrderSide, OrderType, Str, Strings, Ticker, Tickers, Trade, Transaction
from typing import List
from ccxt_zagrava.base.errors import ExchangeError
from ccxt_zagrava.base.errors import AuthenticationError
from ccxt_zagrava.base.errors import PermissionDenied
from ccxt_zagrava.base.errors import AccountSuspended
from ccxt_zagrava.base.errors import ArgumentsRequired
from ccxt_zagrava.base.errors import BadRequest
from ccxt_zagrava.base.errors import BadSymbol
from ccxt_zagrava.base.errors import MarginModeAlreadySet
from ccxt_zagrava.base.errors import InsufficientFunds
from ccxt_zagrava.base.errors import InvalidOrder
from ccxt_zagrava.base.errors import OrderNotFound
from ccxt_zagrava.base.errors import OrderImmediatelyFillable
from ccxt_zagrava.base.errors import OrderNotFillable
from ccxt_zagrava.base.errors import NotSupported
from ccxt_zagrava.base.errors import DDoSProtection
from ccxt_zagrava.base.errors import RateLimitExceeded
from ccxt_zagrava.base.errors import ExchangeNotAvailable
from ccxt_zagrava.base.errors import OnMaintenance
from ccxt_zagrava.base.errors import InvalidNonce
from ccxt_zagrava.base.errors import RequestTimeout
from ccxt_zagrava.base.errors import BadResponse
from ccxt_zagrava.base.decimal_to_precision import TRUNCATE
from ccxt_zagrava.base.decimal_to_precision import TICK_SIZE
from ccxt_zagrava.base.precise import Precise


class tokocrypto(Exchange, ImplicitAPI):

    def describe(self):
        return self.deep_extend(super(tokocrypto, self).describe(), {
            'id': 'tokocrypto',
            'name': 'Tokocrypto',
            'countries': ['ID'],  # Indonesia
            'certified': False,
            'pro': False,
            'version': 'v1',
            # new metainfo interface
            'has': {
                'CORS': None,
                'spot': True,
                'margin': True,
                'swap': False,
                'future': False,
                'option': False,
                'addMargin': None,
                'borrowMargin': None,
                'cancelAllOrders': False,
                'cancelOrder': True,
                'cancelOrders': None,
                'createDepositAddress': False,
                'createMarketBuyOrderWithCost': True,
                'createMarketOrderWithCost': False,
                'createMarketSellOrderWithCost': False,
                'createOrder': True,
                'createReduceOnlyOrder': None,
                'createStopLimitOrder': True,
                'createStopMarketOrder': True,
                'createStopOrder': True,
                'fetchAccounts': False,
                'fetchBalance': True,
                'fetchBidsAsks': True,
                'fetchBorrowInterest': None,
                'fetchBorrowRateHistories': None,
                'fetchBorrowRateHistory': None,
                'fetchCanceledOrders': False,
                'fetchClosedOrder': False,
                'fetchClosedOrders': 'emulated',
                'fetchCrossBorrowRate': False,
                'fetchCrossBorrowRates': False,
                'fetchCurrencies': False,
                'fetchDeposit': False,
                'fetchDepositAddress': True,
                'fetchDepositAddresses': False,
                'fetchDepositAddressesByNetwork': False,
                'fetchDeposits': True,
                'fetchDepositsWithdrawals': False,
                'fetchFundingHistory': False,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': False,
                'fetchFundingRates': False,
                'fetchIndexOHLCV': False,
                'fetchIsolatedBorrowRate': False,
                'fetchIsolatedBorrowRates': False,
                'fetchL3OrderBook': False,
                'fetchLedger': None,
                'fetchLeverage': False,
                'fetchLeverageTiers': False,
                'fetchMarketLeverageTiers': 'emulated',
                'fetchMarkets': True,
                'fetchMarkOHLCV': False,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenInterestHistory': False,
                'fetchOpenOrder': False,
                'fetchOpenOrders': True,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrderBooks': False,
                'fetchOrders': True,
                'fetchOrderTrades': False,
                'fetchPosition': False,
                'fetchPositions': False,
                'fetchPositionsRisk': False,
                'fetchPremiumIndexOHLCV': False,
                'fetchStatus': False,
                'fetchTicker': False,
                'fetchTickers': False,
                'fetchTime': True,
                'fetchTrades': True,
                'fetchTradingFee': False,
                'fetchTradingFees': False,
                'fetchTradingLimits': False,
                'fetchTransactionFee': False,
                'fetchTransactionFees': False,
                'fetchTransactions': False,
                'fetchTransfers': False,
                'fetchWithdrawal': False,
                'fetchWithdrawals': True,
                'fetchWithdrawalWhitelist': False,
                'reduceMargin': False,
                'repayCrossMargin': False,
                'repayIsolatedMargin': False,
                'setLeverage': False,
                'setMargin': False,
                'setMarginMode': False,
                'setPositionMode': False,
                'signIn': False,
                'transfer': False,
                'withdraw': True,
            },
            'timeframes': {
                '1m': '1m',
                '3m': '3m',
                '5m': '5m',
                '15m': '15m',
                '30m': '30m',
                '1h': '1h',
                '2h': '2h',
                '4h': '4h',
                '6h': '6h',
                '8h': '8h',
                '12h': '12h',
                '1d': '1d',
                '3d': '3d',
                '1w': '1w',
                '1M': '1M',
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/183870484-d3398d0c-f6a1-4cce-91b8-d58792308716.jpg',
                'api': {
                    'rest': {
                        'public': 'https://www.tokocrypto.com',
                        'binance': 'https://api.binance.com/api/v3',
                        'private': 'https://www.tokocrypto.com',
                    },
                },
                'www': 'https://tokocrypto.com',
                # 'referral': 'https://www.binance.us/?ref=35005074',
                'doc': 'https://www.tokocrypto.com/apidocs/',
                'fees': 'https://www.tokocrypto.com/fees/newschedule',
            },
            'api': {
                'binance': {
                    'get': {
                        'ping': 1,
                        'time': 1,
                        'depth': {'cost': 1, 'byLimit': [[100, 1], [500, 5], [1000, 10], [5000, 50]]},
                        'trades': 1,
                        'aggTrades': 1,
                        'historicalTrades': 5,
                        'klines': 1,
                        'ticker/24hr': {'cost': 1, 'noSymbol': 40},
                        'ticker/price': {'cost': 1, 'noSymbol': 2},
                        'ticker/bookTicker': {'cost': 1, 'noSymbol': 2},
                        'exchangeInfo': 10,
                    },
                    'put': {
                        'userDataStream': 1,
                    },
                    'post': {
                        'userDataStream': 1,
                    },
                    'delete': {
                        'userDataStream': 1,
                    },
                },
                'public': {
                    'get': {
                        'open/v1/common/time': 1,
                        'open/v1/common/symbols': 1,
                        # all the actual symbols are type 1
                        'open/v1/market/depth': 1,  # when symbol type is not 1
                        'open/v1/market/trades': 1,  # when symbol type is not 1
                        'open/v1/market/agg-trades': 1,  # when symbol type is not 1
                        'open/v1/market/klines': 1,  # when symbol type is not 1
                    },
                },
                'private': {
                    'get': {
                        'open/v1/orders/detail': 1,
                        'open/v1/orders': 1,
                        'open/v1/account/spot': 1,
                        'open/v1/account/spot/asset': 1,
                        'open/v1/orders/trades': 1,
                        'open/v1/withdraws': 1,
                        'open/v1/deposits': 1,
                        'open/v1/deposits/address': 1,
                    },
                    'post': {
                        'open/v1/orders': 1,
                        'open/v1/orders/cancel': 1,
                        'open/v1/orders/oco': 1,
                        'open/v1/withdraws': 1,
                        'open/v1/user-data-stream': 1,
                    },
                },
            },
            'fees': {
                'trading': {
                    'tierBased': True,
                    'percentage': True,
                    'taker': self.parse_number('0.0075'),  # 0.1% trading fee, zero fees for all trading pairs before November 1
                    'maker': self.parse_number('0.0075'),  # 0.1% trading fee, zero fees for all trading pairs before November 1
                },
            },
            'precisionMode': TICK_SIZE,
            'options': {
                # 'fetchTradesMethod': 'binanceGetTrades',  # binanceGetTrades, binanceGetAggTrades
                'createMarketBuyOrderRequiresPrice': True,
                'defaultTimeInForce': 'GTC',  # 'GTC' = Good To Cancel(default), 'IOC' = Immediate Or Cancel
                # 'defaultType': 'spot',  # 'spot', 'future', 'margin', 'delivery'
                'hasAlreadyAuthenticatedSuccessfully': False,
                'warnOnFetchOpenOrdersWithoutSymbol': True,
                # 'fetchPositions': 'positionRisk',  # or 'account'
                'recvWindow': 5 * 1000,  # 5 sec, binance default
                'timeDifference': 0,  # the difference between system clock and Binance clock
                'adjustForTimeDifference': False,  # controls the adjustment logic upon instantiation
                'newOrderRespType': {
                    'market': 'FULL',  # 'ACK' for order id, 'RESULT' for full order or 'FULL' for order with fills
                    'limit': 'FULL',  # we change it from 'ACK' by default to 'FULL'(returns immediately if limit is not hit)
                },
                'quoteOrderQty': False,  # whether market orders support amounts in quote currency
                'networks': {
                    'ERC20': 'ETH',
                    'TRC20': 'TRX',
                    'BEP2': 'BNB',
                    'BEP20': 'BSC',
                    'OMNI': 'OMNI',
                    'EOS': 'EOS',
                    'SPL': 'SOL',
                },
                'reverseNetworks': {
                    'tronscan.org': 'TRC20',
                    'etherscan.io': 'ERC20',
                    'bscscan.com': 'BSC',
                    'explorer.binance.org': 'BEP2',
                    'bithomp.com': 'XRP',
                    'bloks.io': 'EOS',
                    'stellar.expert': 'XLM',
                    'blockchair.com/bitcoin': 'BTC',
                    'blockchair.com/bitcoin-cash': 'BCH',
                    'blockchair.com/ecash': 'XEC',
                    'explorer.litecoin.net': 'LTC',
                    'explorer.avax.network': 'AVAX',
                    'solscan.io': 'SOL',
                    'polkadot.subscan.io': 'DOT',
                    'dashboard.internetcomputer.org': 'ICP',
                    'explorer.chiliz.com': 'CHZ',
                    'cardanoscan.io': 'ADA',
                    'mainnet.theoan.com': 'AION',
                    'algoexplorer.io': 'ALGO',
                    'explorer.ambrosus.com': 'AMB',
                    'viewblock.io/zilliqa': 'ZIL',
                    'viewblock.io/arweave': 'AR',
                    'explorer.ark.io': 'ARK',
                    'atomscan.com': 'ATOM',
                    'www.mintscan.io': 'CTK',
                    'explorer.bitcoindiamond.org': 'BCD',
                    'btgexplorer.com': 'BTG',
                    'bts.ai': 'BTS',
                    'explorer.celo.org': 'CELO',
                    'explorer.nervos.org': 'CKB',
                    'cerebro.cortexlabs.ai': 'CTXC',
                    'chainz.cryptoid.info': 'VIA',
                    'explorer.dcrdata.org': 'DCR',
                    'digiexplorer.info': 'DGB',
                    'dock.subscan.io': 'DOCK',
                    'dogechain.info': 'DOGE',
                    'explorer.elrond.com': 'EGLD',
                    'blockscout.com': 'ETC',
                    'explore-fetchhub.fetch.ai': 'FET',
                    'filfox.info': 'FIL',
                    'fio.bloks.io': 'FIO',
                    'explorer.firo.org': 'FIRO',
                    'neoscan.io': 'NEO',
                    'ftmscan.com': 'FTM',
                    'explorer.gochain.io': 'GO',
                    'block.gxb.io': 'GXS',
                    'hash-hash.info': 'HBAR',
                    'www.hiveblockexplorer.com': 'HIVE',
                    'explorer.helium.com': 'HNT',
                    'tracker.icon.foundation': 'ICX',
                    'www.iostabc.com': 'IOST',
                    'explorer.iota.org': 'IOTA',
                    'iotexscan.io': 'IOTX',
                    'irishub.iobscan.io': 'IRIS',
                    'kava.mintscan.io': 'KAVA',
                    'scope.klaytn.com': 'KLAY',
                    'kmdexplorer.io': 'KMD',
                    'kusama.subscan.io': 'KSM',
                    'explorer.lto.network': 'LTO',
                    'polygonscan.com': 'POLYGON',
                    'explorer.ont.io': 'ONT',
                    'minaexplorer.com': 'MINA',
                    'nanolooker.com': 'NANO',
                    'explorer.nebulas.io': 'NAS',
                    'explorer.nbs.plus': 'NBS',
                    'explorer.nebl.io': 'NEBL',
                    'nulscan.io': 'NULS',
                    'nxscan.com': 'NXS',
                    'explorer.harmony.one': 'ONE',
                    'explorer.poa.network': 'POA',
                    'qtum.info': 'QTUM',
                    'explorer.rsk.co': 'RSK',
                    'www.oasisscan.com': 'ROSE',
                    'ravencoin.network': 'RVN',
                    'sc.tokenview.com': 'SC',
                    'secretnodes.com': 'SCRT',
                    'explorer.skycoin.com': 'SKY',
                    'steemscan.com': 'STEEM',
                    'explorer.stacks.co': 'STX',
                    'www.thetascan.io': 'THETA',
                    'scan.tomochain.com': 'TOMO',
                    'explore.vechain.org': 'VET',
                    'explorer.vite.net': 'VITE',
                    'www.wanscan.org': 'WAN',
                    'wavesexplorer.com': 'WAVES',
                    'wax.eosx.io': 'WAXP',
                    'waltonchain.pro': 'WTC',
                    'chain.nem.ninja': 'XEM',
                    'verge-blockchain.info': 'XVG',
                    'explorer.yoyow.org': 'YOYOW',
                    'explorer.zcha.in': 'ZEC',
                    'explorer.zensystem.io': 'ZEN',
                },
                'impliedNetworks': {
                    'ETH': {'ERC20': 'ETH'},
                    'TRX': {'TRC20': 'TRX'},
                },
                'legalMoney': {
                    'MXN': True,
                    'UGX': True,
                    'SEK': True,
                    'CHF': True,
                    'VND': True,
                    'AED': True,
                    'DKK': True,
                    'KZT': True,
                    'HUF': True,
                    'PEN': True,
                    'PHP': True,
                    'USD': True,
                    'TRY': True,
                    'EUR': True,
                    'NGN': True,
                    'PLN': True,
                    'BRL': True,
                    'ZAR': True,
                    'KES': True,
                    'ARS': True,
                    'RUB': True,
                    'AUD': True,
                    'NOK': True,
                    'CZK': True,
                    'GBP': True,
                    'UAH': True,
                    'GHS': True,
                    'HKD': True,
                    'CAD': True,
                    'INR': True,
                    'JPY': True,
                    'NZD': True,
                },
            },
            # https://binance-docs.github.io/apidocs/spot/en/#error-codes-2
            'exceptions': {
                'exact': {
                    'System is under maintenance.': OnMaintenance,  # {"code":1,"msg":"System is under maintenance."}
                    'System abnormality': ExchangeError,  # {"code":-1000,"msg":"System abnormality"}
                    'You are not authorized to execute self request.': PermissionDenied,  # {"msg":"You are not authorized to execute self request."}
                    'API key does not exist': AuthenticationError,
                    'Order would trigger immediately.': OrderImmediatelyFillable,
                    'Stop price would trigger immediately.': OrderImmediatelyFillable,  # {"code":-2010,"msg":"Stop price would trigger immediately."}
                    'Order would immediately match and take.': OrderImmediatelyFillable,  # {"code":-2010,"msg":"Order would immediately match and take."}
                    'Account has insufficient balance for requested action.': InsufficientFunds,
                    'Rest API trading is not enabled.': ExchangeNotAvailable,
                    "You don't have permission.": PermissionDenied,  # {"msg":"You don't have permission.","success":false}
                    'Market is closed.': ExchangeNotAvailable,  # {"code":-1013,"msg":"Market is closed."}
                    'Too many requests. Please try again later.': DDoSProtection,  # {"msg":"Too many requests. Please try again later.","success":false}
                    'This action disabled is on self account.': AccountSuspended,  # {"code":-2010,"msg":"This action disabled is on self account."}
                    '-1000': ExchangeNotAvailable,  # {"code":-1000,"msg":"An unknown error occured while processing the request."}
                    '-1001': ExchangeNotAvailable,  # {"code":-1001,"msg":"'Internal error; unable to process your request. Please try again.'"}
                    '-1002': AuthenticationError,  # {"code":-1002,"msg":"'You are not authorized to execute self request.'"}
                    '-1003': RateLimitExceeded,  # {"code":-1003,"msg":"Too much request weight used, current limit is 1200 request weight per 1 MINUTE. Please use the websocket for live updates to avoid polling the API."}
                    '-1004': DDoSProtection,  # {"code":-1004,"msg":"Server is busy, please wait and try again"}
                    '-1005': PermissionDenied,  # {"code":-1005,"msg":"No such IP has been white listed"}
                    '-1006': BadResponse,  # {"code":-1006,"msg":"An unexpected response was received from the message bus. Execution status unknown."}
                    '-1007': RequestTimeout,  # {"code":-1007,"msg":"Timeout waiting for response from backend server. Send status unknown; execution status unknown."}
                    '-1010': BadResponse,  # {"code":-1010,"msg":"ERROR_MSG_RECEIVED."}
                    '-1011': PermissionDenied,  # {"code":-1011,"msg":"This IP cannot access self route."}
                    '-1013': InvalidOrder,  # {"code":-1013,"msg":"createOrder -> 'invalid quantity'/'invalid price'/MIN_NOTIONAL"}
                    '-1014': InvalidOrder,  # {"code":-1014,"msg":"Unsupported order combination."}
                    '-1015': RateLimitExceeded,  # {"code":-1015,"msg":"'Too many new orders; current limit is %s orders per %s.'"}
                    '-1016': ExchangeNotAvailable,  # {"code":-1016,"msg":"'This service is no longer available.',"}
                    '-1020': BadRequest,  # {"code":-1020,"msg":"'This operation is not supported.'"}
                    '-1021': InvalidNonce,  # {"code":-1021,"msg":"'your time is ahead of server'"}
                    '-1022': AuthenticationError,  # {"code":-1022,"msg":"Signature for self request is not valid."}
                    '-1023': BadRequest,  # {"code":-1023,"msg":"Start time is greater than end time."}
                    '-1099': AuthenticationError,  # {"code":-1099,"msg":"Not found, authenticated, or authorized"}
                    '-1100': BadRequest,  # {"code":-1100,"msg":"createOrder(symbol, 1, asdf) -> 'Illegal characters found in parameter 'price'"}
                    '-1101': BadRequest,  # {"code":-1101,"msg":"Too many parameters; expected %s and received %s."}
                    '-1102': BadRequest,  # {"code":-1102,"msg":"Param %s or %s must be sent, but both were empty"}
                    '-1103': BadRequest,  # {"code":-1103,"msg":"An unknown parameter was sent."}
                    '-1104': BadRequest,  # {"code":-1104,"msg":"Not all sent parameters were read, read 8 parameters but was sent 9"}
                    '-1105': BadRequest,  # {"code":-1105,"msg":"Parameter %s was empty."}
                    '-1106': BadRequest,  # {"code":-1106,"msg":"Parameter %s sent when not required."}
                    '-1108': BadRequest,  # {"code":-1108,"msg":"Invalid asset."}
                    '-1109': AuthenticationError,  # {"code":-1109,"msg":"Invalid account."}
                    '-1110': BadRequest,  # {"code":-1110,"msg":"Invalid symbolType."}
                    '-1111': BadRequest,  # {"code":-1111,"msg":"Precision is over the maximum defined for self asset."}
                    '-1112': InvalidOrder,  # {"code":-1112,"msg":"No orders on book for symbol."}
                    '-1113': BadRequest,  # {"code":-1113,"msg":"Withdrawal amount must be negative."}
                    '-1114': BadRequest,  # {"code":-1114,"msg":"TimeInForce parameter sent when not required."}
                    '-1115': BadRequest,  # {"code":-1115,"msg":"Invalid timeInForce."}
                    '-1116': BadRequest,  # {"code":-1116,"msg":"Invalid orderType."}
                    '-1117': BadRequest,  # {"code":-1117,"msg":"Invalid side."}
                    '-1118': BadRequest,  # {"code":-1118,"msg":"New client order ID was empty."}
                    '-1119': BadRequest,  # {"code":-1119,"msg":"Original client order ID was empty."}
                    '-1120': BadRequest,  # {"code":-1120,"msg":"Invalid interval."}
                    '-1121': BadSymbol,  # {"code":-1121,"msg":"Invalid symbol."}
                    '-1125': AuthenticationError,  # {"code":-1125,"msg":"This listenKey does not exist."}
                    '-1127': BadRequest,  # {"code":-1127,"msg":"More than %s hours between startTime and endTime."}
                    '-1128': BadRequest,  # {"code":-1128,"msg":"{"code":-1128,"msg":"Combination of optional parameters invalid."}"}
                    '-1130': BadRequest,  # {"code":-1130,"msg":"Data sent for paramter %s is not valid."}
                    '-1131': BadRequest,  # {"code":-1131,"msg":"recvWindow must be less than 60000"}
                    '-1136': BadRequest,  # {"code":-1136,"msg":"Invalid newOrderRespType"}
                    '-2008': AuthenticationError,  # {"code":-2008,"msg":"Invalid Api-Key ID."}
                    '-2010': ExchangeError,  # {"code":-2010,"msg":"generic error code for createOrder -> 'Account has insufficient balance for requested action.', {"code":-2010,"msg":"Rest API trading is not enabled."}, etc..."}
                    '-2011': OrderNotFound,  # {"code":-2011,"msg":"cancelOrder(1, 'BTC/USDT') -> 'UNKNOWN_ORDER'"}
                    '-2013': OrderNotFound,  # {"code":-2013,"msg":"fetchOrder(1, 'BTC/USDT') -> 'Order does not exist'"}
                    '-2014': AuthenticationError,  # {"code":-2014,"msg":"API-key format invalid."}
                    '-2015': AuthenticationError,  # {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."}
                    '-2016': BadRequest,  # {"code":-2016,"msg":"No trading window could be found for the symbol. Try ticker/24hrs instead."}
                    '-2018': InsufficientFunds,  # {"code":-2018,"msg":"Balance is insufficient"}
                    '-2019': InsufficientFunds,  # {"code":-2019,"msg":"Margin is insufficient."}
                    '-2020': OrderNotFillable,  # {"code":-2020,"msg":"Unable to fill."}
                    '-2021': OrderImmediatelyFillable,  # {"code":-2021,"msg":"Order would immediately trigger."}
                    '-2022': InvalidOrder,  # {"code":-2022,"msg":"ReduceOnly Order is rejected."}
                    '-2023': InsufficientFunds,  # {"code":-2023,"msg":"User in liquidation mode now."}
                    '-2024': InsufficientFunds,  # {"code":-2024,"msg":"Position is not sufficient."}
                    '-2025': InvalidOrder,  # {"code":-2025,"msg":"Reach max open order limit."}
                    '-2026': InvalidOrder,  # {"code":-2026,"msg":"This OrderType is not supported when reduceOnly."}
                    '-2027': InvalidOrder,  # {"code":-2027,"msg":"Exceeded the maximum allowable position at current leverage."}
                    '-2028': InsufficientFunds,  # {"code":-2028,"msg":"Leverage is smaller than permitted: insufficient margin balance"}
                    '-3000': ExchangeError,  # {"code":-3000,"msg":"Internal server error."}
                    '-3001': AuthenticationError,  # {"code":-3001,"msg":"Please enable 2FA first."}
                    '-3002': BadSymbol,  # {"code":-3002,"msg":"We don't have self asset."}
                    '-3003': BadRequest,  # {"code":-3003,"msg":"Margin account does not exist."}
                    '-3004': ExchangeError,  # {"code":-3004,"msg":"Trade not allowed."}
                    '-3005': InsufficientFunds,  # {"code":-3005,"msg":"Transferring out not allowed. Transfer out amount exceeds max amount."}
                    '-3006': InsufficientFunds,  # {"code":-3006,"msg":"Your borrow amount has exceed maximum borrow amount."}
                    '-3007': ExchangeError,  # {"code":-3007,"msg":"You have pending transaction, please try again later.."}
                    '-3008': InsufficientFunds,  # {"code":-3008,"msg":"Borrow not allowed. Your borrow amount has exceed maximum borrow amount."}
                    '-3009': BadRequest,  # {"code":-3009,"msg":"This asset are not allowed to transfer into margin account currently."}
                    '-3010': ExchangeError,  # {"code":-3010,"msg":"Repay not allowed. Repay amount exceeds borrow amount."}
                    '-3011': BadRequest,  # {"code":-3011,"msg":"Your input date is invalid."}
                    '-3012': ExchangeError,  # {"code":-3012,"msg":"Borrow is banned for self asset."}
                    '-3013': BadRequest,  # {"code":-3013,"msg":"Borrow amount less than minimum borrow amount."}
                    '-3014': AccountSuspended,  # {"code":-3014,"msg":"Borrow is banned for self account."}
                    '-3015': ExchangeError,  # {"code":-3015,"msg":"Repay amount exceeds borrow amount."}
                    '-3016': BadRequest,  # {"code":-3016,"msg":"Repay amount less than minimum repay amount."}
                    '-3017': ExchangeError,  # {"code":-3017,"msg":"This asset are not allowed to transfer into margin account currently."}
                    '-3018': AccountSuspended,  # {"code":-3018,"msg":"Transferring in has been banned for self account."}
                    '-3019': AccountSuspended,  # {"code":-3019,"msg":"Transferring out has been banned for self account."}
                    '-3020': InsufficientFunds,  # {"code":-3020,"msg":"Transfer out amount exceeds max amount."}
                    '-3021': BadRequest,  # {"code":-3021,"msg":"Margin account are not allowed to trade self trading pair."}
                    '-3022': AccountSuspended,  # {"code":-3022,"msg":"You account's trading is banned."}
                    '-3023': BadRequest,  # {"code":-3023,"msg":"You can't transfer out/place order under current margin level."}
                    '-3024': ExchangeError,  # {"code":-3024,"msg":"The unpaid debt is too small after self repayment."}
                    '-3025': BadRequest,  # {"code":-3025,"msg":"Your input date is invalid."}
                    '-3026': BadRequest,  # {"code":-3026,"msg":"Your input param is invalid."}
                    '-3027': BadSymbol,  # {"code":-3027,"msg":"Not a valid margin asset."}
                    '-3028': BadSymbol,  # {"code":-3028,"msg":"Not a valid margin pair."}
                    '-3029': ExchangeError,  # {"code":-3029,"msg":"Transfer failed."}
                    '-3036': AccountSuspended,  # {"code":-3036,"msg":"This account is not allowed to repay."}
                    '-3037': ExchangeError,  # {"code":-3037,"msg":"PNL is clearing. Wait a second."}
                    '-3038': BadRequest,  # {"code":-3038,"msg":"Listen key not found."}
                    '-3041': InsufficientFunds,  # {"code":-3041,"msg":"Balance is not enough"}
                    '-3042': BadRequest,  # {"code":-3042,"msg":"PriceIndex not available for self margin pair."}
                    '-3043': BadRequest,  # {"code":-3043,"msg":"Transferring in not allowed."}
                    '-3044': DDoSProtection,  # {"code":-3044,"msg":"System busy."}
                    '-3045': ExchangeError,  # {"code":-3045,"msg":"The system doesn't have enough asset now."}
                    '-3999': ExchangeError,  # {"code":-3999,"msg":"This function is only available for invited users."}
                    '-4001': BadRequest,  # {"code":-4001 ,"msg":"Invalid operation."}
                    '-4002': BadRequest,  # {"code":-4002 ,"msg":"Invalid get."}
                    '-4003': BadRequest,  # {"code":-4003 ,"msg":"Your input email is invalid."}
                    '-4004': AuthenticationError,  # {"code":-4004,"msg":"You don't login or auth."}
                    '-4005': RateLimitExceeded,  # {"code":-4005 ,"msg":"Too many new requests."}
                    '-4006': BadRequest,  # {"code":-4006 ,"msg":"Support main account only."}
                    '-4007': BadRequest,  # {"code":-4007 ,"msg":"Address validation is not passed."}
                    '-4008': BadRequest,  # {"code":-4008 ,"msg":"Address tag validation is not passed."}
                    '-4010': BadRequest,  # {"code":-4010 ,"msg":"White list mail has been confirmed."}  # [TODO] possible bug: it should probably be "has not been confirmed"
                    '-4011': BadRequest,  # {"code":-4011 ,"msg":"White list mail is invalid."}
                    '-4012': BadRequest,  # {"code":-4012 ,"msg":"White list is not opened."}
                    '-4013': AuthenticationError,  # {"code":-4013 ,"msg":"2FA is not opened."}
                    '-4014': PermissionDenied,  # {"code":-4014 ,"msg":"Withdraw is not allowed within 2 min login."}
                    '-4015': ExchangeError,  # {"code":-4015 ,"msg":"Withdraw is limited."}
                    '-4016': PermissionDenied,  # {"code":-4016 ,"msg":"Within 24 hours after password modification, withdrawal is prohibited."}
                    '-4017': PermissionDenied,  # {"code":-4017 ,"msg":"Within 24 hours after the release of 2FA, withdrawal is prohibited."}
                    '-4018': BadSymbol,  # {"code":-4018,"msg":"We don't have self asset."}
                    '-4019': BadSymbol,  # {"code":-4019,"msg":"Current asset is not open for withdrawal."}
                    '-4021': BadRequest,  # {"code":-4021,"msg":"Asset withdrawal must be an %s multiple of %s."}
                    '-4022': BadRequest,  # {"code":-4022,"msg":"Not less than the minimum pick-up quantity %s."}
                    '-4023': ExchangeError,  # {"code":-4023,"msg":"Within 24 hours, the withdrawal exceeds the maximum amount."}
                    '-4024': InsufficientFunds,  # {"code":-4024,"msg":"You don't have self asset."}
                    '-4025': InsufficientFunds,  # {"code":-4025,"msg":"The number of hold asset is less than zero."}
                    '-4026': InsufficientFunds,  # {"code":-4026,"msg":"You have insufficient balance."}
                    '-4027': ExchangeError,  # {"code":-4027,"msg":"Failed to obtain tranId."}
                    '-4028': BadRequest,  # {"code":-4028,"msg":"The amount of withdrawal must be greater than the Commission."}
                    '-4029': BadRequest,  # {"code":-4029,"msg":"The withdrawal record does not exist."}
                    '-4030': ExchangeError,  # {"code":-4030,"msg":"Confirmation of successful asset withdrawal. [TODO] possible bug in docs"}
                    '-4031': ExchangeError,  # {"code":-4031,"msg":"Cancellation failed."}
                    '-4032': ExchangeError,  # {"code":-4032,"msg":"Withdraw verification exception."}
                    '-4033': BadRequest,  # {"code":-4033,"msg":"Illegal address."}
                    '-4034': ExchangeError,  # {"code":-4034,"msg":"The address is suspected of fake."}
                    '-4035': PermissionDenied,  # {"code":-4035,"msg":"This address is not on the whitelist. Please join and try again."}
                    '-4036': BadRequest,  # {"code":-4036,"msg":"The new address needs to be withdrawn in {0} hours."}
                    '-4037': ExchangeError,  # {"code":-4037,"msg":"Re-sending Mail failed."}
                    '-4038': ExchangeError,  # {"code":-4038,"msg":"Please try again in 5 minutes."}
                    '-4039': BadRequest,  # {"code":-4039,"msg":"The user does not exist."}
                    '-4040': BadRequest,  # {"code":-4040,"msg":"This address not charged."}
                    '-4041': ExchangeError,  # {"code":-4041,"msg":"Please try again in one minute."}
                    '-4042': ExchangeError,  # {"code":-4042,"msg":"This asset cannot get deposit address again."}
                    '-4043': BadRequest,  # {"code":-4043,"msg":"More than 100 recharge addresses were used in 24 hours."}
                    '-4044': BadRequest,  # {"code":-4044,"msg":"This is a blacklist country."}
                    '-4045': ExchangeError,  # {"code":-4045,"msg":"Failure to acquire assets."}
                    '-4046': AuthenticationError,  # {"code":-4046,"msg":"Agreement not confirmed."}
                    '-4047': BadRequest,  # {"code":-4047,"msg":"Time interval must be within 0-90 days"}
                    '-5001': BadRequest,  # {"code":-5001,"msg":"Don't allow transfer to micro assets."}
                    '-5002': InsufficientFunds,  # {"code":-5002,"msg":"You have insufficient balance."}
                    '-5003': InsufficientFunds,  # {"code":-5003,"msg":"You don't have self asset."}
                    '-5004': BadRequest,  # {"code":-5004,"msg":"The residual balances of %s have exceeded 0.001BTC, Please re-choose."}
                    '-5005': InsufficientFunds,  # {"code":-5005,"msg":"The residual balances of %s is too low, Please re-choose."}
                    '-5006': BadRequest,  # {"code":-5006,"msg":"Only transfer once in 24 hours."}
                    '-5007': BadRequest,  # {"code":-5007,"msg":"Quantity must be greater than zero."}
                    '-5008': InsufficientFunds,  # {"code":-5008,"msg":"Insufficient amount of returnable assets."}
                    '-5009': BadRequest,  # {"code":-5009,"msg":"Product does not exist."}
                    '-5010': ExchangeError,  # {"code":-5010,"msg":"Asset transfer fail."}
                    '-5011': BadRequest,  # {"code":-5011,"msg":"future account not exists."}
                    '-5012': ExchangeError,  # {"code":-5012,"msg":"Asset transfer is in pending."}
                    '-5013': InsufficientFunds,  # {"code":-5013,"msg":"Asset transfer failed: insufficient balance""}  # undocumented
                    '-5021': BadRequest,  # {"code":-5021,"msg":"This parent sub have no relation"}
                    '-6001': BadRequest,  # {"code":-6001,"msg":"Daily product not exists."}
                    '-6003': BadRequest,  # {"code":-6003,"msg":"Product not exist or you don't have permission"}
                    '-6004': ExchangeError,  # {"code":-6004,"msg":"Product not in purchase status"}
                    '-6005': InvalidOrder,  # {"code":-6005,"msg":"Smaller than min purchase limit"}
                    '-6006': BadRequest,  # {"code":-6006,"msg":"Redeem amount error"}
                    '-6007': BadRequest,  # {"code":-6007,"msg":"Not in redeem time"}
                    '-6008': BadRequest,  # {"code":-6008,"msg":"Product not in redeem status"}
                    '-6009': RateLimitExceeded,  # {"code":-6009,"msg":"Request frequency too high"}
                    '-6011': BadRequest,  # {"code":-6011,"msg":"Exceeding the maximum num allowed to purchase per user"}
                    '-6012': InsufficientFunds,  # {"code":-6012,"msg":"Balance not enough"}
                    '-6013': ExchangeError,  # {"code":-6013,"msg":"Purchasing failed"}
                    '-6014': BadRequest,  # {"code":-6014,"msg":"Exceed up-limit allowed to purchased"}
                    '-6015': BadRequest,  # {"code":-6015,"msg":"Empty request body"}
                    '-6016': BadRequest,  # {"code":-6016,"msg":"Parameter err"}
                    '-6017': BadRequest,  # {"code":-6017,"msg":"Not in whitelist"}
                    '-6018': BadRequest,  # {"code":-6018,"msg":"Asset not enough"}
                    '-6019': AuthenticationError,  # {"code":-6019,"msg":"Need confirm"}
                    '-6020': BadRequest,  # {"code":-6020,"msg":"Project not exists"}
                    '-7001': BadRequest,  # {"code":-7001,"msg":"Date range is not supported."}
                    '-7002': BadRequest,  # {"code":-7002,"msg":"Data request type is not supported."}
                    '-9000': InsufficientFunds,  # {"code":-9000,"msg":"user have no avaliable amount"}"
                    '-10017': BadRequest,  # {"code":-10017,"msg":"Repay amount should not be larger than liability."}
                    '-11008': InsufficientFunds,  # {"code":-11008,"msg":"Exceeding the account's maximum borrowable limit."}  # undocumented
                    '-12014': RateLimitExceeded,  # {"code":-12014,"msg":"More than 1 request in 3 seconds"}
                    '-13000': BadRequest,  # {"code":-13000,"msg":"Redeption of the token is forbiden now"}
                    '-13001': BadRequest,  # {"code":-13001,"msg":"Exceeds individual 24h redemption limit of the token"}
                    '-13002': BadRequest,  # {"code":-13002,"msg":"Exceeds total 24h redemption limit of the token"}
                    '-13003': BadRequest,  # {"code":-13003,"msg":"Subscription of the token is forbiden now"}
                    '-13004': BadRequest,  # {"code":-13004,"msg":"Exceeds individual 24h subscription limit of the token"}
                    '-13005': BadRequest,  # {"code":-13005,"msg":"Exceeds total 24h subscription limit of the token"}
                    '-13006': InvalidOrder,  # {"code":-13006,"msg":"Subscription amount is too small"}
                    '-13007': AuthenticationError,  # {"code":-13007,"msg":"The Agreement is not signed"}
                    '-21001': BadRequest,  # {"code":-21001,"msg":"USER_IS_NOT_UNIACCOUNT"}
                    '-21002': BadRequest,  # {"code":-21002,"msg":"UNI_ACCOUNT_CANT_TRANSFER_FUTURE"}
                    '-21003': BadRequest,  # {"code":-21003,"msg":"NET_ASSET_MUST_LTE_RATIO"}
                    '100001003': BadRequest,  # {"code":100001003,"msg":"Verification failed"}  # undocumented
                    '2202': InsufficientFunds,  # {"code":2202,"msg":"Insufficient balance","data":{"code":-2010,"msg":"Account has insufficient balance for requested action."},"timestamp":1662733681161}
                    '3210': InvalidOrder,  # {"code":3210,"msg":"The total volume is too low","data":{"code":-1013,"msg":"Filter failure: MIN_NOTIONAL"},"timestamp":1662734704462}
                    '3203': InvalidOrder,  # {"code":3203,"msg":"Incorrect Order Quantity","timestamp":1662734809758}
                    '3211': InvalidOrder,  # {"code":3211,"msg":"The total volume must be greater than 10","timestamp":1662739358179}
                    '3207': InvalidOrder,  # {"code":3207,"msg":"The price cannot be lower than 12.18","timestamp":1662739502856}
                    '3218': OrderNotFound,  # {"code":3218,"msg":"Order does not exist","timestamp":1662739749275}
                },
                'broad': {
                    'has no operation privilege': PermissionDenied,
                    'MAX_POSITION': InvalidOrder,  # {"code":-2010,"msg":"Filter failure: MAX_POSITION"}
                },
            },
        })

    def nonce(self):
        return self.milliseconds() - self.options['timeDifference']

    async def fetch_time(self, params={}):
        """
        :see: https://www.tokocrypto.com/apidocs/#check-server-time
        fetches the current integer timestamp in milliseconds from the exchange server
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns int: the current integer timestamp in milliseconds from the exchange server
        """
        response = await self.publicGetOpenV1CommonTime(params)
        #
        #
        #
        return self.safe_integer(response, 'serverTime')

    async def fetch_markets(self, params={}) -> List[Market]:
        """
        :see: https://www.tokocrypto.com/apidocs/#get-all-supported-trading-symbol
        retrieves data on all markets for tokocrypto
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: an array of objects representing market data
        """
        response = await self.publicGetOpenV1CommonSymbols(params)
        #
        #     {
        #         "code":0,
        #         "msg":"Success",
        #         "data":{
        #             "list":[
        #                 {
        #                     "type":1,
        #                     "symbol":"1INCH_BTC",
        #                     "baseAsset":"1INCH",
        #                     "basePrecision":8,
        #                     "quoteAsset":"BTC",
        #                     "quotePrecision":8,
        #                     "filters":[
        #                         {"filterType":"PRICE_FILTER","minPrice":"0.00000001","maxPrice":"1000.00000000","tickSize":"0.00000001","applyToMarket":false},
        #                         {"filterType":"PERCENT_PRICE","multiplierUp":5,"multiplierDown":0.2,"avgPriceMins":"5","applyToMarket":false},
        #                         {"filterType":"LOT_SIZE","minQty":"0.10000000","maxQty":"90000000.00000000","stepSize":"0.10000000","applyToMarket":false},
        #                         {"filterType":"MIN_NOTIONAL","avgPriceMins":"5","minNotional":"0.00010000","applyToMarket":true},
        #                         {"filterType":"ICEBERG_PARTS","applyToMarket":false,"limit":"10"},
        #                         {"filterType":"MARKET_LOT_SIZE","minQty":"0.00000000","maxQty":"79460.14117231","stepSize":"0.00000000","applyToMarket":false},
        #                         {"filterType":"TRAILING_DELTA","applyToMarket":false},
        #                         {"filterType":"MAX_NUM_ORDERS","applyToMarket":false},
        #                         {"filterType":"MAX_NUM_ALGO_ORDERS","applyToMarket":false,"maxNumAlgoOrders":"5"}
        #                     ],
        #                     "orderTypes":["LIMIT","LIMIT_MAKER","MARKET","STOP_LOSS_LIMIT","TAKE_PROFIT_LIMIT"],
        #                     "icebergEnable":1,
        #                     "ocoEnable":1,
        #                     "spotTradingEnable":1,
        #                     "marginTradingEnable":1,
        #                     "permissions":["SPOT","MARGIN"]
        #                 },
        #             ]
        #         },
        #         "timestamp":1659492212507
        #     }
        #
        if self.options['adjustForTimeDifference']:
            await self.load_time_difference()
        data = self.safe_value(response, 'data', {})
        list = self.safe_value(data, 'list', [])
        result = []
        for i in range(0, len(list)):
            market = list[i]
            baseId = self.safe_string(market, 'baseAsset')
            quoteId = self.safe_string(market, 'quoteAsset')
            id = self.safe_string(market, 'symbol')
            lowercaseId = self.safe_string_lower(market, 'symbol')
            settleId = self.safe_string(market, 'marginAsset')
            base = self.safe_currency_code(baseId)
            quote = self.safe_currency_code(quoteId)
            settle = self.safe_currency_code(settleId)
            symbol = base + '/' + quote
            filters = self.safe_value(market, 'filters', [])
            filtersByType = self.index_by(filters, 'filterType')
            status = self.safe_string(market, 'spotTradingEnable')
            active = (status == '1')
            permissions = self.safe_value(market, 'permissions', [])
            for j in range(0, len(permissions)):
                if permissions[j] == 'TRD_GRP_003':
                    active = False
                    break
            isMarginTradingAllowed = self.safe_bool(market, 'isMarginTradingAllowed', False)
            entry: dict = {
                'id': id,
                'lowercaseId': lowercaseId,
                'symbol': symbol,
                'base': base,
                'quote': quote,
                'settle': settle,
                'baseId': baseId,
                'quoteId': quoteId,
                'settleId': settleId,
                'type': 'spot',
                'spot': True,
                'margin': isMarginTradingAllowed,
                'swap': False,
                'future': False,
                'delivery': False,
                'option': False,
                'active': active,
                'contract': False,
                'linear': None,
                'inverse': None,
                'contractSize': None,
                'expiry': None,
                'expiryDatetime': None,
                'strike': None,
                'optionType': None,
                'precision': {
                    'amount': self.parse_number(self.parse_precision(self.safe_string(market, 'quantityPrecision'))),
                    'price': self.parse_number(self.parse_precision(self.safe_string(market, 'pricePrecision'))),
                    'base': self.parse_number(self.parse_precision(self.safe_string(market, 'baseAssetPrecision'))),
                    'quote': self.parse_number(self.parse_precision(self.safe_string(market, 'quotePrecision'))),
                },
                'limits': {
                    'leverage': {
                        'min': None,
                        'max': None,
                    },
                    'amount': {
                        'min': None,
                        'max': None,
                    },
                    'price': {
                        'min': None,
                        'max': None,
                    },
                    'cost': {
                        'min': None,
                        'max': None,
                    },
                },
                'created': None,
                'info': market,
            }
            if 'PRICE_FILTER' in filtersByType:
                filter = self.safe_value(filtersByType, 'PRICE_FILTER', {})
                entry['precision']['price'] = self.safe_number(filter, 'tickSize')
                # PRICE_FILTER reports zero values for maxPrice
                # since they updated filter types in November 2018
                # https://github.com/ccxt_zagrava/ccxt_zagrava/issues/4286
                # therefore limits['price']['max'] doesn't have any meaningful value except None
                entry['limits']['price'] = {
                    'min': self.safe_number(filter, 'minPrice'),
                    'max': self.safe_number(filter, 'maxPrice'),
                }
                entry['precision']['price'] = filter['tickSize']
            if 'LOT_SIZE' in filtersByType:
                filter = self.safe_value(filtersByType, 'LOT_SIZE', {})
                entry['precision']['amount'] = self.safe_number(filter, 'stepSize')
                entry['limits']['amount'] = {
                    'min': self.safe_number(filter, 'minQty'),
                    'max': self.safe_number(filter, 'maxQty'),
                }
            if 'MARKET_LOT_SIZE' in filtersByType:
                filter = self.safe_value(filtersByType, 'MARKET_LOT_SIZE', {})
                entry['limits']['market'] = {
                    'min': self.safe_number(filter, 'minQty'),
                    'max': self.safe_number(filter, 'maxQty'),
                }
            if 'MIN_NOTIONAL' in filtersByType:
                filter = self.safe_value(filtersByType, 'MIN_NOTIONAL', {})
                entry['limits']['cost']['min'] = self.safe_number_2(filter, 'minNotional', 'notional')
            result.append(entry)
        return result

    async def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """
        :see: https://www.tokocrypto.com/apidocs/#order-book
        fetches information on open orders with bid(buy) and ask(sell) prices, volumes and other data
        :param str symbol: unified symbol of the market to fetch the order book for
        :param int [limit]: the maximum amount of order book entries to return
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: A dictionary of `order book structures <https://docs.ccxt_zagrava.com/#/?id=order-book-structure>` indexed by market symbols
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {}
        if limit is not None:
            request['limit'] = limit  # default 100, max 5000, see https://github.com/binance/binance-spot-api-docs/blob/master/rest-api.md#order-book
        response = None
        if market['quote'] == 'USDT':
            request['symbol'] = market['baseId'] + market['quoteId']
            response = await self.binanceGetDepth(self.extend(request, params))
        else:
            request['symbol'] = market['id']
            response = await self.publicGetOpenV1MarketDepth(self.extend(request, params))
        #
        # future
        #
        #     {
        #         "lastUpdateId":333598053905,
        #         "E":1618631511986,
        #         "T":1618631511964,
        #         "bids":[
        #             ["2493.56","20.189"],
        #             ["2493.54","1.000"],
        #             ["2493.51","0.005"]
        #         ],
        #         "asks":[
        #             ["2493.57","0.877"],
        #             ["2493.62","0.063"],
        #             ["2493.71","12.054"],
        #         ]
        #     }
        # type not 1
        #     {
        #         "code":0,
        #         "msg":"Success",
        #         "data":{
        #            "lastUpdateId":3204783,
        #            "bids":[],
        #            "asks": []
        #         },
        #         "timestamp":1692262634599
        #     }
        data = self.safe_value(response, 'data', response)
        timestamp = self.safe_integer_2(response, 'T', 'timestamp')
        orderbook = self.parse_order_book(data, symbol, timestamp)
        orderbook['nonce'] = self.safe_integer(data, 'lastUpdateId')
        return orderbook

    def parse_trade(self, trade: dict, market: Market = None) -> Trade:
        #
        # aggregate trades
        # https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#compressedaggregate-trades-list
        #
        #     {
        #         "a": 26129,         # Aggregate tradeId
        #         "p": "0.01633102",  # Price
        #         "q": "4.70443515",  # Quantity
        #         "f": 27781,         # First tradeId
        #         "l": 27781,         # Last tradeId
        #         "T": 1498793709153,  # Timestamp
        #         "m": True,          # Was the buyer the maker?
        #         "M": True           # Was the trade the best price match?
        #     }
        #
        # recent public trades and old public trades
        # https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#recent-trades-list
        # https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#old-trade-lookup-market_data
        #
        #     {
        #         "id": 28457,
        #         "price": "4.00000100",
        #         "qty": "12.00000000",
        #         "time": 1499865549590,
        #         "isBuyerMaker": True,
        #         "isBestMatch": True
        #     }
        #
        # private trades
        # https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#account-trade-list-user_data
        #
        #     {
        #         "symbol": "BNBBTC",
        #         "id": 28457,
        #         "orderId": 100234,
        #         "price": "4.00000100",
        #         "qty": "12.00000000",
        #         "commission": "10.10000000",
        #         "commissionAsset": "BNB",
        #         "time": 1499865549590,
        #         "isBuyer": True,
        #         "isMaker": False,
        #         "isBestMatch": True
        #     }
        #
        # futures trades
        # https://binance-docs.github.io/apidocs/futures/en/#account-trade-list-user_data
        #
        #     {
        #       "accountId": 20,
        #       "buyer": False,
        #       "commission": "-0.07819010",
        #       "commissionAsset": "USDT",
        #       "counterPartyId": 653,
        #       "id": 698759,
        #       "maker": False,
        #       "orderId": 25851813,
        #       "price": "7819.01",
        #       "qty": "0.002",
        #       "quoteQty": "0.01563",
        #       "realizedPnl": "-0.91539999",
        #       "side": "SELL",
        #       "symbol": "BTCUSDT",
        #       "time": 1569514978020
        #     }
        #     {
        #       "symbol": "BTCUSDT",
        #       "id": 477128891,
        #       "orderId": 13809777875,
        #       "side": "SELL",
        #       "price": "38479.55",
        #       "qty": "0.001",
        #       "realizedPnl": "-0.00009534",
        #       "marginAsset": "USDT",
        #       "quoteQty": "38.47955",
        #       "commission": "-0.00076959",
        #       "commissionAsset": "USDT",
        #       "time": 1612733566708,
        #       "positionSide": "BOTH",
        #       "maker": True,
        #       "buyer": False
        #     }
        #
        # {respType: FULL}
        #
        #     {
        #       "price": "4000.00000000",
        #       "qty": "1.00000000",
        #       "commission": "4.00000000",
        #       "commissionAsset": "USDT",
        #       "tradeId": "1234",
        #     }
        #
        timestamp = self.safe_integer_2(trade, 'T', 'time')
        price = self.safe_string_2(trade, 'p', 'price')
        amount = self.safe_string_2(trade, 'q', 'qty')
        cost = self.safe_string_2(trade, 'quoteQty', 'baseQty')  # inverse futures
        marketId = self.safe_string(trade, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        id = self.safe_string_2(trade, 't', 'a')
        id = self.safe_string_2(trade, 'id', 'tradeId', id)
        side: Str = None
        orderId = self.safe_string(trade, 'orderId')
        buyerMaker = self.safe_value_2(trade, 'm', 'isBuyerMaker')
        takerOrMaker: Str = None
        if buyerMaker is not None:
            side = 'sell' if buyerMaker else 'buy'  # self is reversed intentionally
            takerOrMaker = 'taker'
        elif 'side' in trade:
            side = self.safe_string_lower(trade, 'side')
        else:
            if 'isBuyer' in trade:
                side = 'buy' if trade['isBuyer'] else 'sell'  # self is a True side
        fee = None
        if 'commission' in trade:
            fee = {
                'cost': self.safe_string(trade, 'commission'),
                'currency': self.safe_currency_code(self.safe_string(trade, 'commissionAsset')),
            }
        if 'isMaker' in trade:
            takerOrMaker = 'maker' if trade['isMaker'] else 'taker'
        if 'maker' in trade:
            takerOrMaker = 'maker' if trade['maker'] else 'taker'
        return self.safe_trade({
            'info': trade,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'symbol': symbol,
            'id': id,
            'order': orderId,
            'type': None,
            'side': side,
            'takerOrMaker': takerOrMaker,
            'price': price,
            'amount': amount,
            'cost': cost,
            'fee': fee,
        }, market)

    async def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """
        :see: https://www.tokocrypto.com/apidocs/#recent-trades-list
        :see: https://www.tokocrypto.com/apidocs/#compressedaggregate-trades-list
        get the list of most recent trades for a particular symbol
        :param str symbol: unified symbol of the market to fetch trades for
        :param int [since]: timestamp in ms of the earliest trade to fetch
        :param int [limit]: the maximum amount of trades to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Trade[]: a list of `trade structures <https://docs.ccxt_zagrava.com/#/?id=public-trades>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': self.get_market_id_by_type(market),
            # 'fromId': 123,    # ID to get aggregate trades from INCLUSIVE.
            # 'startTime': 456,  # Timestamp in ms to get aggregate trades from INCLUSIVE.
            # 'endTime': 789,   # Timestamp in ms to get aggregate trades until INCLUSIVE.
            # 'limit': 500,     # default = 500, maximum = 1000
        }
        if market['quote'] != 'USDT':
            if limit is not None:
                request['limit'] = limit
            responseInner = self.publicGetOpenV1MarketTrades(self.extend(request, params))
            #
            #    {
            #       "code": 0,
            #       "msg": "success",
            #       "data": {
            #           "list": [
            #                {
            #                    "id": 28457,
            #                    "price": "4.00000100",
            #                    "qty": "12.00000000",
            #                    "time": 1499865549590,
            #                    "isBuyerMaker": True,
            #                    "isBestMatch": True
            #                }
            #            ]
            #        },
            #        "timestamp": 1571921637091
            #    }
            #
            data = self.safe_dict(responseInner, 'data', {})
            list = self.safe_list(data, 'list', [])
            return self.parse_trades(list, market, since, limit)
        if limit is not None:
            request['limit'] = limit  # default = 500, maximum = 1000
        defaultMethod = 'binanceGetTrades'
        method = self.safe_string(self.options, 'fetchTradesMethod', defaultMethod)
        response = None
        if (method == 'binanceGetAggTrades') and (since is not None):
            request['startTime'] = since
            # https://github.com/ccxt_zagrava/ccxt_zagrava/issues/6400
            # https://github.com/binance-exchange/binance-official-api-docs/blob/master/rest-api.md#compressedaggregate-trades-list
            request['endTime'] = self.sum(since, 3600000)
            response = await self.binanceGetAggTrades(self.extend(request, params))
        else:
            response = await self.binanceGetTrades(self.extend(request, params))
        #
        # Caveats:
        # - default limit(500) applies only if no other parameters set, trades up
        #   to the maximum limit may be returned to satisfy other parameters
        # - if both limit and time window is set and time window contains more
        #   trades than the limit then the last trades from the window are returned
        # - 'tradeId' accepted and returned by self method is "aggregate" trade id
        #   which is different from actual trade id
        # - setting both fromId and time window results in error
        #
        # aggregate trades
        #
        #     [
        #         {
        #             "a": 26129,         # Aggregate tradeId
        #             "p": "0.01633102",  # Price
        #             "q": "4.70443515",  # Quantity
        #             "f": 27781,         # First tradeId
        #             "l": 27781,         # Last tradeId
        #             "T": 1498793709153,  # Timestamp
        #             "m": True,          # Was the buyer the maker?
        #             "M": True           # Was the trade the best price match?
        #         }
        #     ]
        #
        # recent public trades and historical public trades
        #
        #     [
        #         {
        #             "id": 28457,
        #             "price": "4.00000100",
        #             "qty": "12.00000000",
        #             "time": 1499865549590,
        #             "isBuyerMaker": True,
        #             "isBestMatch": True
        #         }
        #     ]
        #
        return self.parse_trades(response, market, since, limit)

    def parse_ticker(self, ticker: dict, market: Market = None) -> Ticker:
        #
        #     {
        #         "symbol": "ETHBTC",
        #         "priceChange": "0.00068700",
        #         "priceChangePercent": "2.075",
        #         "weightedAvgPrice": "0.03342681",
        #         "prevClosePrice": "0.03310300",
        #         "lastPrice": "0.03378900",
        #         "lastQty": "0.07700000",
        #         "bidPrice": "0.03378900",
        #         "bidQty": "7.16800000",
        #         "askPrice": "0.03379000",
        #         "askQty": "24.00000000",
        #         "openPrice": "0.03310200",
        #         "highPrice": "0.03388900",
        #         "lowPrice": "0.03306900",
        #         "volume": "205478.41000000",
        #         "quoteVolume": "6868.48826294",
        #         "openTime": 1601469986932,
        #         "closeTime": 1601556386932,
        #         "firstId": 196098772,
        #         "lastId": 196186315,
        #         "count": 87544
        #     }
        #
        # coinm
        #     {
        #         "baseVolume": "214549.95171161",
        #         "closeTime": "1621965286847",
        #         "count": "1283779",
        #         "firstId": "152560106",
        #         "highPrice": "39938.3",
        #         "lastId": "153843955",
        #         "lastPrice": "37993.4",
        #         "lastQty": "1",
        #         "lowPrice": "36457.2",
        #         "openPrice": "37783.4",
        #         "openTime": "1621878840000",
        #         "pair": "BTCUSD",
        #         "priceChange": "210.0",
        #         "priceChangePercent": "0.556",
        #         "symbol": "BTCUSD_PERP",
        #         "volume": "81990451",
        #         "weightedAvgPrice": "38215.08713747"
        #     }
        #
        timestamp = self.safe_integer(ticker, 'closeTime')
        marketId = self.safe_string(ticker, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        last = self.safe_string(ticker, 'lastPrice')
        isCoinm = ('baseVolume' in ticker)
        baseVolume = None
        quoteVolume = None
        if isCoinm:
            baseVolume = self.safe_string(ticker, 'baseVolume')
            quoteVolume = self.safe_string(ticker, 'volume')
        else:
            baseVolume = self.safe_string(ticker, 'volume')
            quoteVolume = self.safe_string(ticker, 'quoteVolume')
        return self.safe_ticker({
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'high': self.safe_string(ticker, 'highPrice'),
            'low': self.safe_string(ticker, 'lowPrice'),
            'bid': self.safe_string(ticker, 'bidPrice'),
            'bidVolume': self.safe_string(ticker, 'bidQty'),
            'ask': self.safe_string(ticker, 'askPrice'),
            'askVolume': self.safe_string(ticker, 'askQty'),
            'vwap': self.safe_string(ticker, 'weightedAvgPrice'),
            'open': self.safe_string(ticker, 'openPrice'),
            'close': last,
            'last': last,
            'previousClose': self.safe_string(ticker, 'prevClosePrice'),  # previous day close
            'change': self.safe_string(ticker, 'priceChange'),
            'percentage': self.safe_string(ticker, 'priceChangePercent'),
            'average': None,
            'baseVolume': baseVolume,
            'quoteVolume': quoteVolume,
            'info': ticker,
        }, market)

    async def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """
        :see: https://binance-docs.github.io/apidocs/spot/en/#24hr-ticker-price-change-statistics
        fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
        :param str[]|None symbols: unified symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        response = await self.binanceGetTicker24hr(params)
        return self.parse_tickers(response, symbols)

    def get_market_id_by_type(self, market):
        if market['quote'] == 'USDT':
            return market['baseId'] + market['quoteId']
        return market['id']

    async def fetch_ticker(self, symbol: str, params={}) -> Ticker:
        """
        :see: https://binance-docs.github.io/apidocs/spot/en/#24hr-ticker-price-change-statistics
        fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific market
        :param str symbol: unified symbol of the market to fetch the ticker for
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `ticker structure <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['baseId'] + market['quoteId'],
        }
        response = await self.binanceGetTicker24hr(self.extend(request, params))
        if isinstance(response, list):
            firstTicker = self.safe_dict(response, 0, {})
            return self.parse_ticker(firstTicker, market)
        return self.parse_ticker(response, market)

    async def fetch_bids_asks(self, symbols: Strings = None, params={}):
        """
        :see: https://binance-docs.github.io/apidocs/spot/en/#symbol-order-book-ticker
        fetches the bid and ask price and volume for multiple markets
        :param str[]|None symbols: unified symbols of the markets to fetch the bids and asks for, all markets are returned if not assigned
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a dictionary of `ticker structures <https://docs.ccxt_zagrava.com/#/?id=ticker-structure>`
        """
        await self.load_markets()
        response = await self.binanceGetTickerBookTicker(params)
        return self.parse_tickers(response, symbols)

    def parse_ohlcv(self, ohlcv, market: Market = None) -> list:
        # when api method = publicGetKlines or fapiPublicGetKlines or dapiPublicGetKlines
        #     [
        #         1591478520000,  # open time
        #         "0.02501300",  # open
        #         "0.02501800",  # high
        #         "0.02500000",  # low
        #         "0.02500000",  # close
        #         "22.19000000",  # volume
        #         1591478579999,  # close time
        #         "0.55490906",  # quote asset volume
        #         40,            # number of trades
        #         "10.92900000",  # taker buy base asset volume
        #         "0.27336462",  # taker buy quote asset volume
        #         "0"            # ignore
        #     ]
        #
        #  when api method = fapiPublicGetMarkPriceKlines or fapiPublicGetIndexPriceKlines
        #     [
        #         [
        #         1591256460000,          # Open time
        #         "9653.29201333",        # Open
        #         "9654.56401333",        # High
        #         "9653.07367333",        # Low
        #         "9653.07367333",        # Close(or latest price)
        #         "0",                    # Ignore
        #         1591256519999,          # Close time
        #         "0",                    # Ignore
        #         60,                     # Number of bisic data
        #         "0",                    # Ignore
        #         "0",                    # Ignore
        #         "0"                     # Ignore
        #         ]
        #     ]
        #
        return [
            self.safe_integer(ohlcv, 0),
            self.safe_number(ohlcv, 1),
            self.safe_number(ohlcv, 2),
            self.safe_number(ohlcv, 3),
            self.safe_number(ohlcv, 4),
            self.safe_number(ohlcv, 5),
        ]

    async def fetch_ohlcv(self, symbol: str, timeframe='1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """
        :see: https://binance-docs.github.io/apidocs/spot/en/#kline-candlestick-data
        fetches historical candlestick data containing the open, high, low, and close price, and the volume of a market
        :param str symbol: unified symbol of the market to fetch OHLCV data for
        :param str timeframe: the length of time each candle represents
        :param int [since]: timestamp in ms of the earliest candle to fetch
        :param int [limit]: the maximum amount of candles to fetch
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.price]: "mark" or "index" for mark price and index price candles
        :param int [params.until]: timestamp in ms of the latest candle to fetch
        :returns int[][]: A list of candles ordered, open, high, low, close, volume
        """
        await self.load_markets()
        market = self.market(symbol)
        # binance docs say that the default limit 500, max 1500 for futures, max 1000 for spot markets
        # the reality is that the time range wider than 500 candles won't work right
        defaultLimit = 500
        maxLimit = 1500
        price = self.safe_string(params, 'price')
        until = self.safe_integer(params, 'until')
        params = self.omit(params, ['price', 'until'])
        limit = defaultLimit if (limit is None) else min(limit, maxLimit)
        request: dict = {
            'interval': self.safe_string(self.timeframes, timeframe, timeframe),
            'limit': limit,
        }
        if price == 'index':
            request['pair'] = market['id']   # Index price takes self argument instead of symbol
        else:
            request['symbol'] = self.get_market_id_by_type(market)
        # duration = self.parse_timeframe(timeframe)
        if since is not None:
            request['startTime'] = since
        if until is not None:
            request['endTime'] = until
        response = None
        if market['quote'] == 'USDT':
            response = await self.binanceGetKlines(self.extend(request, params))
        else:
            response = await self.publicGetOpenV1MarketKlines(self.extend(request, params))
        #
        #     [
        #         [1591478520000,"0.02501300","0.02501800","0.02500000","0.02500000","22.19000000",1591478579999,"0.55490906",40,"10.92900000","0.27336462","0"],
        #         [1591478580000,"0.02499600","0.02500900","0.02499400","0.02500300","21.34700000",1591478639999,"0.53370468",24,"7.53800000","0.18850725","0"],
        #         [1591478640000,"0.02500800","0.02501100","0.02500300","0.02500800","154.14200000",1591478699999,"3.85405839",97,"5.32300000","0.13312641","0"],
        #     ]
        #
        data = self.safe_list(response, 'data', response)
        return self.parse_ohlcvs(data, market, timeframe, since, limit)

    async def fetch_balance(self, params={}) -> Balances:
        """
        :see: https://www.tokocrypto.com/apidocs/#account-information-signed
        query for balance and get the amount of funds available for trading or funds locked in orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param str [params.type]: 'future', 'delivery', 'savings', 'funding', or 'spot'
        :param str [params.marginMode]: 'cross' or 'isolated', for margin trading, uses self.options.defaultMarginMode if not passed, defaults to None/None/None
        :param str[]|None [params.symbols]: unified market symbols, only used in isolated margin mode
        :returns dict: a `balance structure <https://docs.ccxt_zagrava.com/#/?id=balance-structure>`
        """
        await self.load_markets()
        defaultType = self.safe_string_2(self.options, 'fetchBalance', 'defaultType', 'spot')
        type = self.safe_string(params, 'type', defaultType)
        defaultMarginMode = self.safe_string_2(self.options, 'marginMode', 'defaultMarginMode')
        marginMode = self.safe_string_lower(params, 'marginMode', defaultMarginMode)
        request: dict = {}
        response = await self.privateGetOpenV1AccountSpot(self.extend(request, params))
        #
        # spot
        #
        #     {
        #         "code":0,
        #         "msg":"Success",
        #         "data":{
        #             "makerCommission":"0.00100000",
        #             "takerCommission":"0.00100000",
        #             "buyerCommission":"0.00000000",
        #             "sellerCommission":"0.00000000",
        #             "canTrade":1,
        #             "canWithdraw":1,
        #             "canDeposit":1,
        #             "status":1,
        #             "accountAssets":[
        #                 {"asset":"1INCH","free":"0","locked":"0"},
        #                 {"asset":"AAVE","free":"0","locked":"0"},
        #                 {"asset":"ACA","free":"0","locked":"0"}
        #             ],
        #         },
        #         "timestamp":1659666786943
        #     }
        #
        return self.parse_balance_custom(response, type, marginMode)

    def parse_balance_custom(self, response, type=None, marginMode=None):
        timestamp = self.safe_integer(response, 'updateTime')
        result: dict = {
            'info': response,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
        }
        data = self.safe_value(response, 'data', {})
        balances = self.safe_value(data, 'accountAssets', [])
        for i in range(0, len(balances)):
            balance = balances[i]
            currencyId = self.safe_string(balance, 'asset')
            code = self.safe_currency_code(currencyId)
            account = self.account()
            account['free'] = self.safe_string(balance, 'free')
            account['used'] = self.safe_string(balance, 'locked')
            result[code] = account
        return self.safe_balance(result)

    def parse_order_status(self, status: Str):
        statuses: dict = {
            '-2': 'open',
            '0': 'open',  # NEW
            '1': 'open',  # PARTIALLY_FILLED
            '2': 'closed',  # FILLED
            '3': 'canceled',  # CANCELED
            '4': 'canceling',  # PENDING_CANCEL(currently unused)
            '5': 'rejected',  # REJECTED
            '6': 'expired',  # EXPIRED
            'NEW': 'open',
            'PARTIALLY_FILLED': 'open',
            'FILLED': 'closed',
            'CANCELED': 'canceled',
            'PENDING_CANCEL': 'canceling',  # currently unused
            'REJECTED': 'rejected',
            'EXPIRED': 'expired',
        }
        return self.safe_string(statuses, status, status)

    def parse_order(self, order: dict, market: Market = None) -> Order:
        #
        # spot
        #
        #     {
        #         "symbol": "LTCBTC",
        #         "orderId": 1,
        #         "clientOrderId": "myOrder1",
        #         "price": "0.1",
        #         "origQty": "1.0",
        #         "executedQty": "0.0",
        #         "cummulativeQuoteQty": "0.0",
        #         "status": "NEW",
        #         "timeInForce": "GTC",
        #         "type": "LIMIT",
        #         "side": "BUY",
        #         "stopPrice": "0.0",
        #         "icebergQty": "0.0",
        #         "time": 1499827319559,
        #         "updateTime": 1499827319559,
        #         "isWorking": True
        #     }
        # createOrder
        #     {
        #         "orderId": "145265071",
        #         "bOrderListId": "0",
        #         "clientId": "49c09c3c2cd54419a59c05441f517b3c",
        #         "bOrderId": "35247529",
        #         "symbol": "USDT_BIDR",
        #         "symbolType": "1",
        #         "side": "0",
        #         "type": "1",
        #         "price": "11915",
        #         "origQty": "2",
        #         "origQuoteQty": "23830.00",
        #         "executedQty": "0.00000000",
        #         "executedPrice": "0",
        #         "executedQuoteQty": "0.00",
        #         "timeInForce": "1",
        #         "stopPrice": "0",
        #         "icebergQty": "0",
        #         "status": "0",
        #         "createTime": "1662711074372"
        #     }
        #
        # createOrder with {"newOrderRespType": "FULL"}
        #
        #     {
        #       "symbol": "BTCUSDT",
        #       "orderId": 5403233939,
        #       "orderListId": -1,
        #       "clientOrderId": "x-R4BD3S825e669e75b6c14f69a2c43e",
        #       "transactTime": 1617151923742,
        #       "price": "0.00000000",
        #       "origQty": "0.00050000",
        #       "executedQty": "0.00050000",
        #       "cummulativeQuoteQty": "29.47081500",
        #       "status": "FILLED",
        #       "timeInForce": "GTC",
        #       "type": "MARKET",
        #       "side": "BUY",
        #       "fills": [
        #         {
        #           "price": "58941.63000000",
        #           "qty": "0.00050000",
        #           "commission": "0.00007050",
        #           "commissionAsset": "BNB",
        #           "tradeId": 737466631
        #         }
        #       ]
        #     }
        #
        # delivery
        #
        #     {
        #       "orderId": "18742727411",
        #       "symbol": "ETHUSD_PERP",
        #       "pair": "ETHUSD",
        #       "status": "FILLED",
        #       "clientOrderId": "x-xcKtGhcu3e2d1503fdd543b3b02419",
        #       "price": "0",
        #       "avgPrice": "4522.14",
        #       "origQty": "1",
        #       "executedQty": "1",
        #       "cumBase": "0.00221134",
        #       "timeInForce": "GTC",
        #       "type": "MARKET",
        #       "reduceOnly": False,
        #       "closePosition": False,
        #       "side": "SELL",
        #       "positionSide": "BOTH",
        #       "stopPrice": "0",
        #       "workingType": "CONTRACT_PRICE",
        #       "priceProtect": False,
        #       "origType": "MARKET",
        #       "time": "1636061952660",
        #       "updateTime": "1636061952660"
        #     }
        #
        status = self.parse_order_status(self.safe_string(order, 'status'))
        marketId = self.safe_string(order, 'symbol')
        symbol = self.safe_symbol(marketId, market)
        filled = self.safe_string(order, 'executedQty', '0')
        timestamp = self.safe_integer(order, 'createTime')
        average = self.safe_string(order, 'avgPrice')
        price = self.safe_string_2(order, 'price', 'executedPrice')
        amount = self.safe_string(order, 'origQty')
        # - Spot/Margin market: cummulativeQuoteQty
        #   Note self is not the actual cost, since Binance futures uses leverage to calculate margins.
        cost = self.safe_string_n(order, ['cummulativeQuoteQty', 'cumQuote', 'executedQuoteQty', 'cumBase'])
        id = self.safe_string(order, 'orderId')
        type = self.parse_order_type(self.safe_string_lower(order, 'type'))
        side = self.safe_string_lower(order, 'side')
        if side == '0':
            side = 'buy'
        elif side == '1':
            side = 'sell'
        fills = self.safe_value(order, 'fills', [])
        clientOrderId = self.safe_string_2(order, 'clientOrderId', 'clientId')
        timeInForce = self.safe_string(order, 'timeInForce')
        if timeInForce == 'GTX':
            # GTX means "Good Till Crossing" and is an equivalent way of saying Post Only
            timeInForce = 'PO'
        postOnly = (type == 'limit_maker') or (timeInForce == 'PO')
        stopPriceString = self.safe_string(order, 'stopPrice')
        stopPrice = self.parse_number(self.omit_zero(stopPriceString))
        return self.safe_order({
            'info': order,
            'id': id,
            'clientOrderId': clientOrderId,
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'lastTradeTimestamp': None,
            'symbol': symbol,
            'type': type,
            'timeInForce': timeInForce,
            'postOnly': postOnly,
            'reduceOnly': self.safe_value(order, 'reduceOnly'),
            'side': side,
            'price': price,
            'stopPrice': stopPrice,
            'triggerPrice': stopPrice,
            'amount': amount,
            'cost': cost,
            'average': average,
            'filled': filled,
            'remaining': None,
            'status': status,
            'fee': None,
            'trades': fills,
        }, market)

    def parse_order_type(self, status):
        statuses: dict = {
            '2': 'market',
            '1': 'limit',
            '4': 'limit',
            '7': 'limit',
        }
        return self.safe_string(statuses, status, status)

    async def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        """
        create a trade order
        :see: https://www.tokocrypto.com/apidocs/#new-order--signed
        :see: https://www.tokocrypto.com/apidocs/#account-trade-list-signed
        :param str symbol: unified symbol of the market to create an order in
        :param str type: 'market' or 'limit'
        :param str side: 'buy' or 'sell'
        :param float amount: how much of currency you want to trade in units of base currency
        :param float [price]: the price at which the order is to be fulfilled, in units of the quote currency, ignored in market orders
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param float [params.triggerPrice]: the price at which a trigger order would be triggered
        :param float [params.cost]: for spot market buy orders, the quote quantity that can be used alternative for the amount
        :returns dict: an `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        await self.load_markets()
        market = self.market(symbol)
        clientOrderId = self.safe_string_2(params, 'clientOrderId', 'clientId')
        postOnly = self.safe_bool(params, 'postOnly', False)
        # only supported for spot/margin api
        if postOnly:
            type = 'LIMIT_MAKER'
        params = self.omit(params, ['clientId', 'clientOrderId'])
        initialUppercaseType = type.upper()
        uppercaseType = initialUppercaseType
        stopPrice = self.safe_value_2(params, 'triggerPrice', 'stopPrice')
        if stopPrice is not None:
            params = self.omit(params, ['triggerPrice', 'stopPrice'])
            if uppercaseType == 'MARKET':
                uppercaseType = 'STOP_LOSS'
            elif uppercaseType == 'LIMIT':
                uppercaseType = 'STOP_LOSS_LIMIT'
        validOrderTypes = self.safe_value(market['info'], 'orderTypes')
        if not self.in_array(uppercaseType, validOrderTypes):
            if initialUppercaseType != uppercaseType:
                raise InvalidOrder(self.id + ' stopPrice parameter is not allowed for ' + symbol + ' ' + type + ' orders')
            else:
                raise InvalidOrder(self.id + ' ' + type + ' is not a valid order type for the ' + symbol + ' market')
        reverseOrderTypeMapping: dict = {
            'LIMIT': 1,
            'MARKET': 2,
            'STOP_LOSS': 3,
            'STOP_LOSS_LIMIT': 4,
            'TAKE_PROFIT': 5,
            'TAKE_PROFIT_LIMIT': 6,
            'LIMIT_MAKER': 7,
        }
        request: dict = {
            'symbol': market['baseId'] + '_' + market['quoteId'],
            'type': self.safe_string(reverseOrderTypeMapping, uppercaseType),
        }
        if side == 'buy':
            request['side'] = 0
        elif side == 'sell':
            request['side'] = 1
        if clientOrderId is None:
            broker = self.safe_value(self.options, 'broker')
            if broker is not None:
                brokerId = self.safe_string(broker, 'marketType')
                if brokerId is not None:
                    request['clientId'] = brokerId + self.uuid22()
        else:
            request['clientId'] = clientOrderId
        # additional required fields depending on the order type
        priceIsRequired = False
        stopPriceIsRequired = False
        quantityIsRequired = False
        #
        # spot/margin
        #
        #     LIMIT                timeInForce, quantity, price
        #     MARKET               quantity or quoteOrderQty
        #     STOP_LOSS            quantity, stopPrice
        #     STOP_LOSS_LIMIT      timeInForce, quantity, price, stopPrice
        #     TAKE_PROFIT          quantity, stopPrice
        #     TAKE_PROFIT_LIMIT    timeInForce, quantity, price, stopPrice
        #     LIMIT_MAKER          quantity, price
        #
        if uppercaseType == 'MARKET':
            if side == 'buy':
                precision = market['precision']['price']
                quoteAmount = None
                createMarketBuyOrderRequiresPrice = True
                createMarketBuyOrderRequiresPrice, params = self.handle_option_and_params(params, 'createOrder', 'createMarketBuyOrderRequiresPrice', True)
                cost = self.safe_number_2(params, 'cost', 'quoteOrderQty')
                params = self.omit(params, ['cost', 'quoteOrderQty'])
                if cost is not None:
                    quoteAmount = cost
                elif createMarketBuyOrderRequiresPrice:
                    if price is None:
                        raise InvalidOrder(self.id + ' createOrder() requires the price argument for market buy orders to calculate the total cost to spend(amount * price), alternatively set the createMarketBuyOrderRequiresPrice option or param to False and pass the cost to spend(quote quantity) in the amount argument')
                    else:
                        amountString = self.number_to_string(amount)
                        priceString = self.number_to_string(price)
                        quoteAmount = Precise.string_mul(amountString, priceString)
                else:
                    quoteAmount = amount
                request['quoteOrderQty'] = self.decimal_to_precision(quoteAmount, TRUNCATE, precision, self.precisionMode)
            else:
                quantityIsRequired = True
        elif uppercaseType == 'LIMIT':
            priceIsRequired = True
            quantityIsRequired = True
        elif (uppercaseType == 'STOP_LOSS') or (uppercaseType == 'TAKE_PROFIT'):
            stopPriceIsRequired = True
            quantityIsRequired = True
            if market['linear'] or market['inverse']:
                priceIsRequired = True
        elif (uppercaseType == 'STOP_LOSS_LIMIT') or (uppercaseType == 'TAKE_PROFIT_LIMIT'):
            quantityIsRequired = True
            stopPriceIsRequired = True
            priceIsRequired = True
        elif uppercaseType == 'LIMIT_MAKER':
            priceIsRequired = True
            quantityIsRequired = True
        if quantityIsRequired:
            request['quantity'] = self.amount_to_precision(symbol, amount)
        if priceIsRequired:
            if price is None:
                raise InvalidOrder(self.id + ' createOrder() requires a price argument for a ' + type + ' order')
            request['price'] = self.price_to_precision(symbol, price)
        if stopPriceIsRequired:
            if stopPrice is None:
                raise InvalidOrder(self.id + ' createOrder() requires a stopPrice extra param for a ' + type + ' order')
            else:
                request['stopPrice'] = self.price_to_precision(symbol, stopPrice)
        response = await self.privatePostOpenV1Orders(self.extend(request, params))
        #
        #     {
        #         "code": 0,
        #         "msg": "Success",
        #         "data": {
        #             "orderId": 145264846,
        #             "bOrderListId": 0,
        #             "clientId": "4ee2ab5e55e74b358eaf98079c670d17",
        #             "bOrderId": 35247499,
        #             "symbol": "USDT_BIDR",
        #             "symbolType": 1,
        #             "side": 0,
        #             "type": 1,
        #             "price": "11915",
        #             "origQty": "2",
        #             "origQuoteQty": "23830.00",
        #             "executedQty": "0.00000000",
        #             "executedPrice": "0",
        #             "executedQuoteQty": "0.00",
        #             "timeInForce": 1,
        #             "stopPrice": 0,
        #             "icebergQty": "0",
        #             "status": 0,
        #             "createTime": 1662710994848
        #         },
        #         "timestamp": 1662710994975
        #     }
        #
        rawOrder = self.safe_dict(response, 'data', {})
        return self.parse_order(rawOrder, market)

    async def fetch_order(self, id: str, symbol: Str = None, params={}):
        """
        :see: https://www.tokocrypto.com/apidocs/#all-orders-signed
        fetches information on an order made by the user
        :param str symbol: unified symbol of the market the order was made in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        request: dict = {
            'orderId': id,
        }
        response = await self.privateGetOpenV1Orders(self.extend(request, params))
        #
        #     {
        #         "code": 0,
        #         "msg": "Success",
        #         "data": {
        #             "list": [{
        #                 "orderId": "145221985",
        #                 "clientId": "201515331fd64d03aedbe687a38152e3",
        #                 "bOrderId": "35239632",
        #                 "bOrderListId": "0",
        #                 "symbol": "USDT_BIDR",
        #                 "symbolType": 1,
        #                 "side": 0,
        #                 "type": 1,
        #                 "price": "11907",
        #                 "origQty": "2",
        #                 "origQuoteQty": "23814",
        #                 "executedQty": "0",
        #                 "executedPrice": "0",
        #                 "executedQuoteQty": "0",
        #                 "timeInForce": 1,
        #                 "stopPrice": "0",
        #                 "icebergQty": "0",
        #                 "status": 0,
        #                 "createTime": 1662699360000
        #             }]
        #         },
        #         "timestamp": 1662710056523
        #     }
        #
        data = self.safe_value(response, 'data', {})
        list = self.safe_value(data, 'list', [])
        rawOrder = self.safe_dict(list, 0, {})
        return self.parse_order(rawOrder)

    async def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        :see: https://www.tokocrypto.com/apidocs/#all-orders-signed
        fetches information on multiple orders made by the user
        :param str symbol: unified market symbol of the market orders were made in
        :param int [since]: the earliest time in ms to fetch orders for
        :param int [limit]: the maximum number of order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Order[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOrders() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
            # 'type': -1,  # -1 = all, 1 = open, 2 = closed
            # 'side': 1,  # or 2
            # 'startTime': since,
            # 'endTime': self.milliseconds(),
            # 'fromId': 'starting order ID',  # if defined, the "direct" field becomes mandatory
            # 'direct': 'prev',  # prev, next
            # 'limit': 500,  # default 500, max 1000
        }
        if since is not None:
            request['startTime'] = since
        if limit is not None:
            request['limit'] = limit
        response = await self.privateGetOpenV1Orders(self.extend(request, params))
        #
        #     {
        #         "code": 0,
        #         "msg": "success",
        #         "data": {
        #             "list": [
        #                 {
        #                     "orderId": "4",  # order id
        #                     "bOrderId": "100001",  # binance order id
        #                     "bOrderListId": -1,  # Unless part of an OCO, the value will always be -1.
        #                     "clientId": "1aa4f99ad7bc4fab903395afd25d0597",  # client custom order id
        #                     "symbol": "ADA_USDT",
        #                     "symbolType": 1,
        #                     "side": 1,
        #                     "type": 1,
        #                     "price": "0.1",
        #                     "origQty": "10",
        #                     "origQuoteQty": "1",
        #                     "executedQty": "0",
        #                     "executedPrice": "0",
        #                     "executedQuoteQty": "0",
        #                     "timeInForce": 1,
        #                     "stopPrice": "0.0000000000000000",
        #                     "icebergQty": "0.0000000000000000",
        #                     "status": 0,
        #                     "isWorking": 0,
        #                     "createTime": 1572692016811
        #                 }
        #             ]
        #         },
        #         "timestamp": 1572860756458
        #     }
        #
        data = self.safe_value(response, 'data', {})
        orders = self.safe_list(data, 'list', [])
        return self.parse_orders(orders, market, since, limit)

    async def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        :see: https://www.tokocrypto.com/apidocs/#all-orders-signed
        fetch all unfilled currently open orders
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch open orders for
        :param int [limit]: the maximum number of  open orders structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Order[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        request: dict = {'type': 1}  # -1 = all, 1 = open, 2 = closed
        return await self.fetch_orders(symbol, since, limit, self.extend(request, params))

    async def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """
        :see: https://www.tokocrypto.com/apidocs/#all-orders-signed
        fetches information on multiple closed orders made by the user
        :param str symbol: unified market symbol of the market orders were made in
        :param int [since]: the earliest time in ms to fetch orders for
        :param int [limit]: the maximum number of order structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Order[]: a list of `order structures <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        request: dict = {'type': 2}  # -1 = all, 1 = open, 2 = closed
        return await self.fetch_orders(symbol, since, limit, self.extend(request, params))

    async def cancel_order(self, id: str, symbol: Str = None, params={}):
        """
        :see: https://www.tokocrypto.com/apidocs/#cancel-order-signed
        cancels an open order
        :param str id: order id
        :param str symbol: unified symbol of the market the order was made in
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: An `order structure <https://docs.ccxt_zagrava.com/#/?id=order-structure>`
        """
        request: dict = {
            'orderId': id,
        }
        response = await self.privatePostOpenV1OrdersCancel(self.extend(request, params))
        #
        #     {
        #         "code": 0,
        #         "msg": "Success",
        #         "data": {
        #             "orderId": "145221985",
        #             "bOrderListId": "0",
        #             "clientId": "201515331fd64d03aedbe687a38152e3",
        #             "bOrderId": "35239632",
        #             "symbol": "USDT_BIDR",
        #             "symbolType": 1,
        #             "type": 1,
        #             "side": 0,
        #             "price": "11907.0000000000000000",
        #             "origQty": "2.0000000000000000",
        #             "origQuoteQty": "23814.0000000000000000",
        #             "executedPrice": "0.0000000000000000",
        #             "executedQty": "0.00000000",
        #             "executedQuoteQty": "0.00",
        #             "timeInForce": 1,
        #             "stopPrice": "0.0000000000000000",
        #             "icebergQty": "0.0000000000000000",
        #             "status": 3
        #         },
        #         "timestamp": 1662710683634
        #     }
        #
        rawOrder = self.safe_dict(response, 'data', {})
        return self.parse_order(rawOrder)

    async def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """
        :see: https://www.tokocrypto.com/apidocs/#account-trade-list-signed
        fetch all trades made by the user
        :param str symbol: unified market symbol
        :param int [since]: the earliest time in ms to fetch trades for
        :param int [limit]: the maximum number of trades structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns Trade[]: a list of `trade structures <https://docs.ccxt_zagrava.com/#/?id=trade-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a symbol argument')
        await self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['id'],
        }
        endTime = self.safe_integer_2(params, 'until', 'endTime')
        if since is not None:
            request['startTime'] = since
        if endTime is not None:
            request['endTime'] = endTime
            params = self.omit(params, ['endTime', 'until'])
        if limit is not None:
            request['limit'] = limit
        response = await self.privateGetOpenV1OrdersTrades(self.extend(request, params))
        #
        #     {
        #         "code": 0,
        #         "msg": "success",
        #         "data": {
        #             "list": [
        #                 {
        #                     "tradeId": "3",
        #                     "orderId": "2",
        #                     "symbol": "ADA_USDT",
        #                     "price": "0.04398",
        #                     "qty": "250",
        #                     "quoteQty": "10.995",
        #                     "commission": "0.25",
        #                     "commissionAsset": "ADA",
        #                     "isBuyer": 1,
        #                     "isMaker": 0,
        #                     "isBestMatch": 1,
        #                     "time": "1572920872276"
        #                 }
        #             ]
        #         },
        #         "timestamp": 1573723498893
        #     }
        #
        data = self.safe_value(response, 'data', {})
        trades = self.safe_list(data, 'list', [])
        return self.parse_trades(trades, market, since, limit)

    async def fetch_deposit_address(self, code: str, params={}):
        """
        :see: https://www.tokocrypto.com/apidocs/#deposit-address-signed
        fetch the deposit address for a currency associated with self account
        :param str code: unified currency code
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: an `address structure <https://docs.ccxt_zagrava.com/#/?id=address-structure>`
        """
        await self.load_markets()
        currency = self.currency(code)
        request: dict = {
            'asset': currency['id'],
            # 'network': 'ETH',  # 'BSC', 'XMR', you can get network and isDefault in networkList in the response of sapiGetCapitalConfigDetail
        }
        networks = self.safe_value(self.options, 'networks', {})
        network = self.safe_string_upper(params, 'network')  # self line allows the user to specify either ERC20 or ETH
        network = self.safe_string(networks, network, network)  # handle ERC20>ETH alias
        if network is not None:
            request['network'] = network
            params = self.omit(params, 'network')
        # has support for the 'network' parameter
        # https://binance-docs.github.io/apidocs/spot/en/#deposit-address-supporting-network-user_data
        response = await self.privateGetOpenV1DepositsAddress(self.extend(request, params))
        #
        #     {
        #         "code":0,
        #         "msg":"Success",
        #         "data":{
        #             "uid":"182395",
        #             "asset":"USDT",
        #             "network":"ETH",
        #             "address":"0x101a925704f6ff13295ab8dd7a60988d116aaedf",
        #             "addressTag":"",
        #             "status":1
        #         },
        #         "timestamp":1660685915746
        #     }
        #
        data = self.safe_value(response, 'data', {})
        address = self.safe_string(data, 'address')
        tag = self.safe_string(data, 'addressTag', '')
        if len(tag) == 0:
            tag = None
        self.check_address(address)
        return {
            'currency': code,
            'address': address,
            'tag': tag,
            'network': self.safe_string(data, 'network'),
            'info': response,
        }

    async def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        :see: https://www.tokocrypto.com/apidocs/#deposit-history-signed
        fetch all deposits made to an account
        :param str code: unified currency code
        :param int [since]: the earliest time in ms to fetch deposits for
        :param int [limit]: the maximum number of deposits structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :param int [params.until]: the latest time in ms to fetch deposits for
        :returns dict[]: a list of `transaction structures <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        await self.load_markets()
        currency = None
        request: dict = {}
        until = self.safe_integer(params, 'until')
        if code is not None:
            currency = self.currency(code)
            request['coin'] = currency['id']
        if since is not None:
            request['startTime'] = since
            # max 3 months range https://github.com/ccxt_zagrava/ccxt_zagrava/issues/6495
            endTime = self.sum(since, 7776000000)
            if until is not None:
                endTime = min(endTime, until)
            request['endTime'] = endTime
        if limit is not None:
            request['limit'] = limit
        response = await self.privateGetOpenV1Deposits(self.extend(request, params))
        #
        #     {
        #         "code":0,
        #         "msg":"Success",
        #         "data":{
        #             "list":[
        #                 {
        #                     "id":5167969,
        #                     "asset":"BIDR",
        #                     "network":"BSC",
        #                     "address":"0x101a925704f6ff13295ab8dd7a60988d116aaedf",
        #                     "addressTag":"",
        #                     "txId":"113409337867",
        #                     "amount":"15000",
        #                     "transferType":1,
        #                     "status":1,
        #                     "insertTime":"1659429390000"
        #                 },
        #             ]
        #         },
        #         "timestamp":1659758865998
        #     }
        #
        data = self.safe_value(response, 'data', {})
        deposits = self.safe_list(data, 'list', [])
        return self.parse_transactions(deposits, currency, since, limit)

    async def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """
        :see: https://www.tokocrypto.com/apidocs/#withdraw-signed
        fetch all withdrawals made from an account
        :param str code: unified currency code
        :param int [since]: the earliest time in ms to fetch withdrawals for
        :param int [limit]: the maximum number of withdrawals structures to retrieve
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict[]: a list of `transaction structures <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        await self.load_markets()
        request: dict = {}
        currency = None
        if code is not None:
            currency = self.currency(code)
            request['coin'] = currency['id']
        if since is not None:
            request['startTime'] = since
            # max 3 months range https://github.com/ccxt_zagrava/ccxt_zagrava/issues/6495
            request['endTime'] = self.sum(since, 7776000000)
        if limit is not None:
            request['limit'] = limit
        response = await self.privateGetOpenV1Withdraws(self.extend(request, params))
        #
        #     {
        #         "code":0,
        #         "msg":"Success",
        #         "data":{
        #             "list":[
        #                 {
        #                     "id":4245859,
        #                     "clientId":"198",
        #                     "asset":"BIDR",
        #                     "network":"BSC",
        #                     "address":"0xff1c75149cc492e7d5566145b859fcafc900b6e9",
        #                     "addressTag":"",
        #                     "amount":"10000",
        #                     "fee":"0",
        #                     "txId":"113501794501",
        #                     "transferType":1,
        #                     "status":10,
        #                     "createTime":1659521314413
        #                 }
        #             ]
        #         },
        #         "timestamp":1659759062187
        #     }
        #
        data = self.safe_value(response, 'data', {})
        withdrawals = self.safe_list(data, 'list', [])
        return self.parse_transactions(withdrawals, currency, since, limit)

    def parse_transaction_status_by_type(self, status, type=None):
        statusesByType: dict = {
            'deposit': {
                '0': 'pending',
                '1': 'ok',
            },
            'withdrawal': {
                '0': 'pending',  # Email Sent
                '1': 'canceled',  # Cancelled(different from 1 = ok in deposits)
                '2': 'pending',  # Awaiting Approval
                '3': 'failed',  # Rejected
                '4': 'pending',  # Processing
                '5': 'failed',  # Failure
                '10': 'ok',  # Completed
            },
        }
        statuses = self.safe_value(statusesByType, type, {})
        return self.safe_string(statuses, status, status)

    def parse_transaction(self, transaction: dict, currency: Currency = None) -> Transaction:
        #
        # fetchDeposits
        #
        #     {
        #         "id": 5167969,
        #         "asset": "BIDR",
        #         "network": "BSC",
        #         "address": "0x101a925704f6ff13295ab8dd7a60988d116aaedf",
        #         "addressTag": "",
        #         "txId": "113409337867",
        #         "amount": "15000",
        #         "transferType": 1,
        #         "status": 1,
        #         "insertTime": "1659429390000"
        #     }
        #
        # fetchWithdrawals
        #
        #     {
        #         "id": 4245859,
        #         "clientId": "198",
        #         "asset": "BIDR",
        #         "network": "BSC",
        #         "address": "0xff1c75149cc492e7d5566145b859fcafc900b6e9",
        #         "addressTag": "",
        #         "amount": "10000",
        #         "fee": "0",
        #         "txId": "113501794501",
        #         "transferType": 1,
        #         "status": 10,
        #         "createTime": 1659521314413
        #     }
        #
        # withdraw
        #
        #     {
        #         "code": 0,
        #         "msg": "成功",
        #         "data": {
        #             "withdrawId":"12"
        #         },
        #         "timestamp": 1571745049095
        #     }
        #
        address = self.safe_string(transaction, 'address')
        tag = self.safe_string(transaction, 'addressTag')  # set but unused
        if tag is not None:
            if len(tag) < 1:
                tag = None
        txid = self.safe_string(transaction, 'txId')
        if (txid is not None) and (txid.find('Internal transfer ') >= 0):
            txid = txid[18:]
        currencyId = self.safe_string_2(transaction, 'coin', 'fiatCurrency')
        code = self.safe_currency_code(currencyId, currency)
        timestamp = None
        insertTime = self.safe_integer(transaction, 'insertTime')
        createTime = self.safe_integer_2(transaction, 'createTime', 'timestamp')
        type = self.safe_string(transaction, 'type')
        if type is None:
            if (insertTime is not None) and (createTime is None):
                type = 'deposit'
                timestamp = insertTime
            elif (insertTime is None) and (createTime is not None):
                type = 'withdrawal'
                timestamp = createTime
        feeCost = self.safe_number_2(transaction, 'transactionFee', 'totalFee')
        fee = {
            'currency': None,
            'cost': None,
            'rate': None,
        }
        if feeCost is not None:
            fee['currency'] = code
            fee['cost'] = feeCost
        internalRaw = self.safe_integer(transaction, 'transferType')
        internal = False
        if internalRaw is not None:
            internal = True
        id = self.safe_string(transaction, 'id')
        if id is None:
            data = self.safe_value(transaction, 'data', {})
            id = self.safe_string(data, 'withdrawId')
            type = 'withdrawal'
        return {
            'info': transaction,
            'id': id,
            'txid': txid,
            'type': type,
            'currency': code,
            'network': self.safe_string(transaction, 'network'),
            'amount': self.safe_number(transaction, 'amount'),
            'status': self.parse_transaction_status_by_type(self.safe_string(transaction, 'status'), type),
            'timestamp': timestamp,
            'datetime': self.iso8601(timestamp),
            'address': address,
            'addressFrom': None,
            'addressTo': address,
            'tag': tag,
            'tagFrom': None,
            'tagTo': tag,
            'updated': self.safe_integer_2(transaction, 'successTime', 'updateTime'),
            'comment': None,
            'internal': internal,
            'fee': fee,
        }

    async def withdraw(self, code: str, amount: float, address: str, tag=None, params={}):
        """
        :see: https://www.tokocrypto.com/apidocs/#withdraw-signed
        make a withdrawal
        :param str code: unified currency code
        :param float amount: the amount to withdraw
        :param str address: the address to withdraw to
        :param str tag:
        :param dict [params]: extra parameters specific to the exchange API endpoint
        :returns dict: a `transaction structure <https://docs.ccxt_zagrava.com/#/?id=transaction-structure>`
        """
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        await self.load_markets()
        self.check_address(address)
        currency = self.currency(code)
        request: dict = {
            'asset': currency['id'],
            # 'clientId': 'string',  # # client's custom id for withdraw order, server does not check it's uniqueness, automatically generated if not sent
            # 'network': 'string',
            'address': address,
            # 'addressTag': 'string',  # for coins like XRP, XMR, etc
            'amount': self.number_to_string(amount),
        }
        if tag is not None:
            request['addressTag'] = tag
        networkCode, query = self.handle_network_code_and_params(params)
        networkId = self.network_code_to_id(networkCode)
        if networkId is not None:
            request['network'] = networkId.upper()
        response = await self.privatePostOpenV1Withdraws(self.extend(request, query))
        #
        #     {
        #         "code": 0,
        #         "msg": "成功",
        #         "data": {
        #             "withdrawId":"12"
        #         },
        #         "timestamp": 1571745049095
        #     }
        #
        return self.parse_transaction(response, currency)

    def sign(self, path, api='public', method='GET', params={}, headers=None, body=None):
        if not (api in self.urls['api']['rest']):
            raise NotSupported(self.id + ' does not have a testnet/sandbox URL for ' + api + ' endpoints')
        url = self.urls['api']['rest'][api]
        url += '/' + path
        if api == 'wapi':
            url += '.html'
        userDataStream = (path == 'userDataStream') or (path == 'listenKey')
        if userDataStream:
            if self.apiKey:
                # v1 special case for userDataStream
                headers = {
                    'X-MBX-APIKEY': self.apiKey,
                    'Content-Type': 'application/x-www-form-urlencoded',
                }
                if method != 'GET':
                    body = self.urlencode(params)
            else:
                raise AuthenticationError(self.id + ' userDataStream endpoint requires `apiKey` credential')
        elif (api == 'private') or (api == 'sapi' and path != 'system/status') or (api == 'sapiV3') or (api == 'wapi' and path != 'systemStatus') or (api == 'dapiPrivate') or (api == 'dapiPrivateV2') or (api == 'fapiPrivate') or (api == 'fapiPrivateV2'):
            self.check_required_credentials()
            query = None
            defaultRecvWindow = self.safe_integer(self.options, 'recvWindow')
            extendedParams = self.extend({
                'timestamp': self.nonce(),
            }, params)
            if defaultRecvWindow is not None:
                extendedParams['recvWindow'] = defaultRecvWindow
            recvWindow = self.safe_integer(params, 'recvWindow')
            if recvWindow is not None:
                extendedParams['recvWindow'] = recvWindow
            if (api == 'sapi') and (path == 'asset/dust'):
                query = self.urlencode_with_array_repeat(extendedParams)
            elif (path == 'batchOrders') or (path.find('sub-account') >= 0) or (path == 'capital/withdraw/apply') or (path.find('staking') >= 0):
                query = self.rawencode(extendedParams)
            else:
                query = self.urlencode(extendedParams)
            signature = self.hmac(self.encode(query), self.encode(self.secret), hashlib.sha256)
            query += '&' + 'signature=' + signature
            headers = {
                'X-MBX-APIKEY': self.apiKey,
            }
            if (method == 'GET') or (method == 'DELETE') or (api == 'wapi'):
                url += '?' + query
            else:
                body = query
                headers['Content-Type'] = 'application/x-www-form-urlencoded'
        else:
            if params:
                url += '?' + self.urlencode(params)
        return {'url': url, 'method': method, 'body': body, 'headers': headers}

    def handle_errors(self, code: int, reason: str, url: str, method: str, headers: dict, body: str, response, requestHeaders, requestBody):
        if (code == 418) or (code == 429):
            raise DDoSProtection(self.id + ' ' + str(code) + ' ' + reason + ' ' + body)
        # error response in a form: {"code": -1013, "msg": "Invalid quantity."}
        # following block cointains legacy checks against message patterns in "msg" property
        # will switch "code" checks eventually, when we know all of them
        if code >= 400:
            if body.find('Price * QTY is zero or less') >= 0:
                raise InvalidOrder(self.id + ' order cost = amount * price is zero or less ' + body)
            if body.find('LOT_SIZE') >= 0:
                raise InvalidOrder(self.id + ' order amount should be evenly divisible by lot size ' + body)
            if body.find('PRICE_FILTER') >= 0:
                raise InvalidOrder(self.id + ' order price is invalid, i.e. exceeds allowed price precision, exceeds min price or max price limits or is invalid value in general, use self.price_to_precision(symbol, amount) ' + body)
        if response is None:
            return None  # fallback to default error handler
        # check success value for wapi endpoints
        # response in format {'msg': 'The coin does not exist.', 'success': True/false}
        success = self.safe_bool(response, 'success', True)
        if not success:
            messageInner = self.safe_string(response, 'msg')
            parsedMessage = None
            if messageInner is not None:
                try:
                    parsedMessage = json.loads(messageInner)
                except Exception as e:
                    # do nothing
                    parsedMessage = None
                if parsedMessage is not None:
                    response = parsedMessage
        message = self.safe_string(response, 'msg')
        if message is not None:
            self.throw_exactly_matched_exception(self.exceptions['exact'], message, self.id + ' ' + message)
            self.throw_broadly_matched_exception(self.exceptions['broad'], message, self.id + ' ' + message)
        # checks against error codes
        error = self.safe_string(response, 'code')
        if error is not None:
            # https://github.com/ccxt_zagrava/ccxt_zagrava/issues/6501
            # https://github.com/ccxt_zagrava/ccxt_zagrava/issues/7742
            if (error == '200') or Precise.string_equals(error, '0'):
                return None
            # a workaround for {"code":-2015,"msg":"Invalid API-key, IP, or permissions for action."}
            # despite that their message is very confusing, it is raised by Binance
            # on a temporary ban, the API key is valid, but disabled for a while
            if (error == '-2015') and self.options['hasAlreadyAuthenticatedSuccessfully']:
                raise DDoSProtection(self.id + ' ' + body)
            feedback = self.id + ' ' + body
            if message == 'No need to change margin type.':
                # not an error
                # https://github.com/ccxt_zagrava/ccxt_zagrava/issues/11268
                # https://github.com/ccxt_zagrava/ccxt_zagrava/pull/11624
                # POST https://fapi.binance.com/fapi/v1/marginType 400 Bad Request
                # binanceusdm {"code":-4046,"msg":"No need to change margin type."}
                raise MarginModeAlreadySet(feedback)
            self.throw_exactly_matched_exception(self.exceptions['exact'], error, feedback)
            raise ExchangeError(feedback)
        if not success:
            raise ExchangeError(self.id + ' ' + body)
        return None

    def calculate_rate_limiter_cost(self, api, method, path, params, config={}):
        if ('noCoin' in config) and not ('coin' in params):
            return config['noCoin']
        elif ('noSymbol' in config) and not ('symbol' in params):
            return config['noSymbol']
        elif ('noPoolId' in config) and not ('poolId' in params):
            return config['noPoolId']
        elif ('byLimit' in config) and ('limit' in params):
            limit = params['limit']
            byLimit = config['byLimit']
            for i in range(0, len(byLimit)):
                entry = byLimit[i]
                if limit <= entry[0]:
                    return entry[1]
        return self.safe_integer(config, 'cost', 1)
