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

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

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


class tokocrypto(Exchange, ImplicitAPI):

    def describe(self) -> Any:
        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,
                'closeAllPositions': False,
                'closePosition': False,
                'createDepositAddress': False,
                'createMarketBuyOrderWithCost': True,
                'createMarketOrderWithCost': False,
                'createMarketSellOrderWithCost': False,
                'createOrder': True,
                'createReduceOnlyOrder': None,
                'createStopLimitOrder': True,
                'createStopLossOrder': False,
                'createStopMarketOrder': True,
                'createStopOrder': True,
                'createTakeProfitOrder': False,
                'fetchAccounts': False,
                'fetchAllGreeks': 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,
                'fetchFundingInterval': False,
                'fetchFundingIntervals': False,
                'fetchFundingRate': False,
                'fetchFundingRateHistory': False,
                'fetchFundingRates': False,
                'fetchGreeks': False,
                'fetchIndexOHLCV': False,
                'fetchIsolatedBorrowRate': False,
                'fetchIsolatedBorrowRates': False,
                'fetchL3OrderBook': False,
                'fetchLedger': None,
                'fetchLeverage': False,
                'fetchLeverageTiers': False,
                'fetchLongShortRatio': False,
                'fetchLongShortRatioHistory': False,
                'fetchMarketLeverageTiers': 'emulated',
                'fetchMarkets': True,
                'fetchMarkOHLCV': False,
                'fetchMarkPrice': False,
                'fetchMarkPrices': False,
                'fetchMySettlementHistory': False,
                'fetchMyTrades': True,
                'fetchOHLCV': True,
                'fetchOpenInterestHistory': False,
                'fetchOpenInterests': False,
                'fetchOpenOrder': False,
                'fetchOpenOrders': True,
                'fetchOption': False,
                'fetchOptionChain': False,
                'fetchOrder': True,
                'fetchOrderBook': True,
                'fetchOrderBooks': False,
                'fetchOrders': True,
                'fetchOrderTrades': False,
                'fetchPosition': False,
                'fetchPositionHistory': False,
                'fetchPositionMode': False,
                'fetchPositions': False,
                'fetchPositionsForSymbol': False,
                'fetchPositionsHistory': False,
                'fetchPositionsRisk': False,
                'fetchPremiumIndexOHLCV': False,
                'fetchSettlementHistory': 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,
                'fetchUnderlyingAssets': False,
                'fetchVolatilityHistory': 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"}
                },
            },
            'features': {
                'spot': {
                    'sandbox': False,
                    'createOrder': {
                        'marginMode': False,
                        'triggerPrice': True,
                        'triggerDirection': False,
                        'triggerPriceType': None,
                        'stopLossPrice': False,  # todo
                        'takeProfitPrice': False,  # todo
                        'attachedStopLossTakeProfit': None,
                        'timeInForce': {
                            'IOC': True,
                            'FOK': True,
                            'PO': True,
                            'GTD': False,
                        },
                        'hedged': False,
                        'trailing': False,
                        'leverage': False,
                        'marketBuyByCost': True,
                        'marketBuyRequiresPrice': True,
                        'selfTradePrevention': True,  # todo
                        'iceberg': True,  # todo
                    },
                    'createOrders': None,
                    'fetchMyTrades': {
                        'marginMode': False,
                        'limit': 1000,
                        'daysBack': 100000,  # todo
                        'untilDays': 100000,  # todo
                        'symbolRequired': True,
                    },
                    'fetchOrder': {
                        'marginMode': False,
                        'trigger': False,
                        'trailing': False,
                        'symbolRequired': False,
                    },
                    'fetchOpenOrders': {
                        'marginMode': False,
                        'limit': 1000,
                        'trigger': False,
                        'trailing': False,
                        'symbolRequired': True,
                    },
                    'fetchOrders': {
                        'marginMode': False,
                        'limit': 1000,
                        'daysBack': 100000,
                        'untilDays': 100000,
                        'trigger': False,
                        'trailing': False,
                        'symbolRequired': True,
                    },
                    'fetchClosedOrders': {
                        'marginMode': False,
                        'limit': 1000,
                        'daysBack': 100000,  # todo
                        'daysBackCanceled': 1,  # todo
                        'untilDays': 100000,  # todo
                        'trigger': False,
                        'trailing': False,
                        'symbolRequired': True,
                    },
                    'fetchOHLCV': {
                        'limit': 1000,
                    },
                },
                'swap': {
                    'linear': None,
                    'inverse': None,
                },
                'future': {
                    'linear': None,
                    'inverse': None,
                },
            },
        })

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

    def fetch_time(self, params={}) -> Int:
        """

        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 = self.publicGetOpenV1CommonTime(params)
        #
        # {
        #     "code": 0,
        #     "msg": "Success",
        #     "data": null,
        #     "timestamp": 1737378074159
        # }
        #
        return self.safe_integer(response, 'timestamp')

    def fetch_markets(self, params={}) -> List[Market]:
        """

        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 = 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']:
            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/ccxt/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

    def fetch_order_book(self, symbol: str, limit: Int = None, params={}) -> OrderBook:
        """

        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.com/?id=order-book-structure>` indexed by market symbols
        """
        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 = self.binanceGetDepth(self.extend(request, params))
        else:
            request['symbol'] = market['id']
            response = 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)

    def fetch_trades(self, symbol: str, since: Int = None, limit: Int = None, params={}) -> List[Trade]:
        """

        https://www.tokocrypto.com/apidocs/#recent-trades-list
        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.com/?id=public-trades>`
        """
        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/ccxt/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 = self.binanceGetAggTrades(self.extend(request, params))
        else:
            response = 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)

    def fetch_tickers(self, symbols: Strings = None, params={}) -> Tickers:
        """

        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.com/?id=ticker-structure>`
        """
        self.load_markets()
        response = 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']

    def fetch_ticker(self, symbol: str, params={}) -> Ticker:
        """

        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.com/?id=ticker-structure>`
        """
        self.load_markets()
        market = self.market(symbol)
        request: dict = {
            'symbol': market['baseId'] + market['quoteId'],
        }
        response = 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)

    def fetch_bids_asks(self, symbols: Strings = None, params={}):
        """

        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.com/?id=ticker-structure>`
        """
        self.load_markets()
        response = 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),
        ]

    def fetch_ohlcv(self, symbol: str, timeframe: str = '1m', since: Int = None, limit: Int = None, params={}) -> List[list]:
        """

        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
        """
        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 = self.binanceGetKlines(self.extend(request, params))
        else:
            response = 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)

    def fetch_balance(self, params={}) -> Balances:
        """

        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.com/?id=balance-structure>`
        """
        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 = 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')
        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,
            'triggerPrice': self.parse_number(self.omit_zero(self.safe_string(order, '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)

    def create_order(self, symbol: str, type: OrderType, side: OrderSide, amount: float, price: Num = None, params={}):
        """
        create a trade order

        https://www.tokocrypto.com/apidocs/#new-order--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.com/?id=order-structure>`
        """
        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
        triggerPrice = self.safe_value_2(params, 'triggerPrice', 'stopPrice')
        if triggerPrice 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 + ' triggerPrice 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
        triggerPriceIsRequired = 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'):
            triggerPriceIsRequired = True
            quantityIsRequired = True
            if market['linear'] or market['inverse']:
                priceIsRequired = True
        elif (uppercaseType == 'STOP_LOSS_LIMIT') or (uppercaseType == 'TAKE_PROFIT_LIMIT'):
            quantityIsRequired = True
            triggerPriceIsRequired = 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 triggerPriceIsRequired:
            if triggerPrice is None:
                raise InvalidOrder(self.id + ' createOrder() requires a triggerPrice extra param for a ' + type + ' order')
            else:
                request['stopPrice'] = self.price_to_precision(symbol, triggerPrice)
        response = 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)

    def fetch_order(self, id: str, symbol: Str = None, params={}):
        """

        https://www.tokocrypto.com/apidocs/#query-order-signed

        fetches information on an order made by the user
        :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.com/?id=order-structure>`
        """
        request: dict = {
            'orderId': id,
        }
        response = 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)

    def fetch_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """

        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.com/?id=order-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchOrders() requires a symbol argument')
        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 = 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)

    def fetch_open_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """

        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.com/?id=order-structure>`
        """
        request: dict = {'type': 1}  # -1 = all, 1 = open, 2 = closed
        return self.fetch_orders(symbol, since, limit, self.extend(request, params))

    def fetch_closed_orders(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Order]:
        """

        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.com/?id=order-structure>`
        """
        request: dict = {'type': 2}  # -1 = all, 1 = open, 2 = closed
        return self.fetch_orders(symbol, since, limit, self.extend(request, params))

    def cancel_order(self, id: str, symbol: Str = None, params={}):
        """

        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.com/?id=order-structure>`
        """
        request: dict = {
            'orderId': id,
        }
        response = 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)

    def fetch_my_trades(self, symbol: Str = None, since: Int = None, limit: Int = None, params={}):
        """

        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.com/?id=trade-structure>`
        """
        if symbol is None:
            raise ArgumentsRequired(self.id + ' fetchMyTrades() requires a symbol argument')
        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 = 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)

    def fetch_deposit_address(self, code: str, params={}) -> DepositAddress:
        """
        fetch the deposit address for a currency associated with self account

        https://www.tokocrypto.com/apidocs/#deposit-address-signed

        :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.com/?id=address-structure>`
        """
        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 = 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 {
            'info': response,
            'currency': code,
            'network': self.safe_string(data, 'network'),
            'address': address,
            'tag': tag,
        }

    def fetch_deposits(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """

        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.com/?id=transaction-structure>`
        """
        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/ccxt/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 = 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)

    def fetch_withdrawals(self, code: Str = None, since: Int = None, limit: Int = None, params={}) -> List[Transaction]:
        """

        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.com/?id=transaction-structure>`
        """
        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/ccxt/issues/6495
            request['endTime'] = self.sum(since, 7776000000)
        if limit is not None:
            request['limit'] = limit
        response = 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,
        }

    def withdraw(self, code: str, amount: float, address: str, tag: Str = None, params={}) -> Transaction:
        """

        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.com/?id=transaction-structure>`
        """
        tag, params = self.handle_withdraw_tag_and_params(tag, params)
        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 = 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/ccxt/issues/6501
            # https://github.com/ccxt/ccxt/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/ccxt/issues/11268
                # https://github.com/ccxt/ccxt/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)
