<?php

namespace ccxt;

// 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

use Exception; // a common import
use ccxt\abstract\tokocrypto as Exchange;

class tokocrypto extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'tokocrypto',
            'name' => 'Tokocrypto',
            'countries' => array( 'ID' ), // Indonesia
            'certified' => false,
            'pro' => false,
            'version' => 'v1',
            // new metainfo interface
            'has' => array(
                'CORS' => null,
                'spot' => true,
                'margin' => true,
                'swap' => false,
                'future' => false,
                'option' => false,
                'addMargin' => null,
                'borrowMargin' => null,
                'cancelAllOrders' => false,
                'cancelOrder' => true,
                'cancelOrders' => null,
                'createDepositAddress' => false,
                'createMarketBuyOrderWithCost' => true,
                'createMarketOrderWithCost' => false,
                'createMarketSellOrderWithCost' => false,
                'createOrder' => true,
                'createReduceOnlyOrder' => null,
                'createStopLimitOrder' => true,
                'createStopMarketOrder' => true,
                'createStopOrder' => true,
                'fetchAccounts' => false,
                'fetchBalance' => true,
                'fetchBidsAsks' => true,
                'fetchBorrowInterest' => null,
                'fetchBorrowRateHistories' => null,
                'fetchBorrowRateHistory' => null,
                'fetchCanceledOrders' => false,
                'fetchClosedOrder' => false,
                'fetchClosedOrders' => 'emulated',
                'fetchCrossBorrowRate' => false,
                'fetchCrossBorrowRates' => false,
                'fetchCurrencies' => false,
                'fetchDeposit' => false,
                'fetchDepositAddress' => true,
                'fetchDepositAddresses' => false,
                'fetchDepositAddressesByNetwork' => false,
                'fetchDeposits' => true,
                'fetchDepositsWithdrawals' => false,
                'fetchFundingHistory' => false,
                'fetchFundingRate' => false,
                'fetchFundingRateHistory' => false,
                'fetchFundingRates' => false,
                'fetchIndexOHLCV' => false,
                'fetchIsolatedBorrowRate' => false,
                'fetchIsolatedBorrowRates' => false,
                'fetchL3OrderBook' => false,
                'fetchLedger' => null,
                'fetchLeverage' => false,
                'fetchLeverageTiers' => false,
                'fetchMarketLeverageTiers' => 'emulated',
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => false,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenInterestHistory' => false,
                'fetchOpenOrder' => false,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrderBooks' => false,
                'fetchOrders' => true,
                'fetchOrderTrades' => false,
                'fetchPosition' => false,
                'fetchPositions' => false,
                'fetchPositionsRisk' => false,
                'fetchPremiumIndexOHLCV' => false,
                'fetchStatus' => false,
                'fetchTicker' => false,
                'fetchTickers' => false,
                'fetchTime' => true,
                'fetchTrades' => true,
                'fetchTradingFee' => false,
                'fetchTradingFees' => false,
                'fetchTradingLimits' => false,
                'fetchTransactionFee' => false,
                'fetchTransactionFees' => false,
                'fetchTransactions' => false,
                'fetchTransfers' => false,
                'fetchWithdrawal' => false,
                'fetchWithdrawals' => true,
                'fetchWithdrawalWhitelist' => false,
                'reduceMargin' => false,
                'repayCrossMargin' => false,
                'repayIsolatedMargin' => false,
                'setLeverage' => false,
                'setMargin' => false,
                'setMarginMode' => false,
                'setPositionMode' => false,
                'signIn' => false,
                'transfer' => false,
                'withdraw' => true,
            ),
            'timeframes' => array(
                '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' => array(
                'logo' => 'https://user-images.githubusercontent.com/1294454/183870484-d3398d0c-f6a1-4cce-91b8-d58792308716.jpg',
                'api' => array(
                    'rest' => array(
                        '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' => array(
                'binance' => array(
                    'get' => array(
                        'ping' => 1,
                        'time' => 1,
                        'depth' => array( 'cost' => 1, 'byLimit' => array( array( 100, 1 ), array( 500, 5 ), array( 1000, 10 ), array( 5000, 50 ) ) ),
                        'trades' => 1,
                        'aggTrades' => 1,
                        'historicalTrades' => 5,
                        'klines' => 1,
                        'ticker/24hr' => array( 'cost' => 1, 'noSymbol' => 40 ),
                        'ticker/price' => array( 'cost' => 1, 'noSymbol' => 2 ),
                        'ticker/bookTicker' => array( 'cost' => 1, 'noSymbol' => 2 ),
                        'exchangeInfo' => 10,
                    ),
                    'put' => array(
                        'userDataStream' => 1,
                    ),
                    'post' => array(
                        'userDataStream' => 1,
                    ),
                    'delete' => array(
                        'userDataStream' => 1,
                    ),
                ),
                'public' => array(
                    'get' => array(
                        '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' => array(
                    'get' => array(
                        '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' => array(
                        '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' => array(
                'trading' => array(
                    'tierBased' => true,
                    'percentage' => true,
                    'taker' => $this->parse_number('0.0075'), // 0.1% trading fee, zero fees for all trading pairs before November 1
                    'maker' => $this->parse_number('0.0075'), // 0.1% trading fee, zero fees for all trading pairs before November 1
                ),
            ),
            'precisionMode' => TICK_SIZE,
            'options' => array(
                // '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' => array(
                    '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' => array(
                    'ERC20' => 'ETH',
                    'TRC20' => 'TRX',
                    'BEP2' => 'BNB',
                    'BEP20' => 'BSC',
                    'OMNI' => 'OMNI',
                    'EOS' => 'EOS',
                    'SPL' => 'SOL',
                ),
                'reverseNetworks' => array(
                    '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' => array(
                    'ETH' => array( 'ERC20' => 'ETH' ),
                    'TRX' => array( 'TRC20' => 'TRX' ),
                ),
                'legalMoney' => array(
                    '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' => array(
                'exact' => array(
                    'System is under maintenance.' => '\\ccxt\\OnMaintenance', // array("code":1,"msg":"System is under maintenance.")
                    'System abnormality' => '\\ccxt\\ExchangeError', // array("code":-1000,"msg":"System abnormality")
                    'You are not authorized to execute this request.' => '\\ccxt\\PermissionDenied', // array("msg":"You are not authorized to execute this request.")
                    'API key does not exist' => '\\ccxt\\AuthenticationError',
                    'Order would trigger immediately.' => '\\ccxt\\OrderImmediatelyFillable',
                    'Stop price would trigger immediately.' => '\\ccxt\\OrderImmediatelyFillable', // array("code":-2010,"msg":"Stop price would trigger immediately.")
                    'Order would immediately match and take.' => '\\ccxt\\OrderImmediatelyFillable', // array("code":-2010,"msg":"Order would immediately match and take.")
                    'Account has insufficient balance for requested action.' => '\\ccxt\\InsufficientFunds',
                    'Rest API trading is not enabled.' => '\\ccxt\\ExchangeNotAvailable',
                    "You don't have permission." => '\\ccxt\\PermissionDenied', // array("msg":"You don't have permission.","success":false)
                    'Market is closed.' => '\\ccxt\\ExchangeNotAvailable', // array("code":-1013,"msg":"Market is closed.")
                    'Too many requests. Please try again later.' => '\\ccxt\\DDoSProtection', // array("msg":"Too many requests. Please try again later.","success":false)
                    'This action disabled is on this account.' => '\\ccxt\\AccountSuspended', // array("code":-2010,"msg":"This action disabled is on this account.")
                    '-1000' => '\\ccxt\\ExchangeNotAvailable', // array("code":-1000,"msg":"An unknown error occured while processing the request.")
                    '-1001' => '\\ccxt\\ExchangeNotAvailable', // array("code":-1001,"msg":"'Internal error; unable to process your request. Please try again.'")
                    '-1002' => '\\ccxt\\AuthenticationError', // array("code":-1002,"msg":"'You are not authorized to execute this request.'")
                    '-1003' => '\\ccxt\\RateLimitExceeded', // array("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' => '\\ccxt\\DDoSProtection', // array("code":-1004,"msg":"Server is busy, please wait and try again")
                    '-1005' => '\\ccxt\\PermissionDenied', // array("code":-1005,"msg":"No such IP has been white listed")
                    '-1006' => '\\ccxt\\BadResponse', // array("code":-1006,"msg":"An unexpected response was received from the message bus. Execution status unknown.")
                    '-1007' => '\\ccxt\\RequestTimeout', // array("code":-1007,"msg":"Timeout waiting for response from backend server. Send status unknown; execution status unknown.")
                    '-1010' => '\\ccxt\\BadResponse', // array("code":-1010,"msg":"ERROR_MSG_RECEIVED.")
                    '-1011' => '\\ccxt\\PermissionDenied', // array("code":-1011,"msg":"This IP cannot access this route.")
                    '-1013' => '\\ccxt\\InvalidOrder', // array("code":-1013,"msg":"createOrder -> 'invalid quantity'/'invalid price'/MIN_NOTIONAL")
                    '-1014' => '\\ccxt\\InvalidOrder', // array("code":-1014,"msg":"Unsupported order combination.")
                    '-1015' => '\\ccxt\\RateLimitExceeded', // array("code":-1015,"msg":"'Too many new orders; current limit is %s orders per %s.'")
                    '-1016' => '\\ccxt\\ExchangeNotAvailable', // array("code":-1016,"msg":"'This service is no longer available.',")
                    '-1020' => '\\ccxt\\BadRequest', // array("code":-1020,"msg":"'This operation is not supported.'")
                    '-1021' => '\\ccxt\\InvalidNonce', // array("code":-1021,"msg":"'your time is ahead of server'")
                    '-1022' => '\\ccxt\\AuthenticationError', // array("code":-1022,"msg":"Signature for this request is not valid.")
                    '-1023' => '\\ccxt\\BadRequest', // array("code":-1023,"msg":"Start time is greater than end time.")
                    '-1099' => '\\ccxt\\AuthenticationError', // array("code":-1099,"msg":"Not found, authenticated, or authorized")
                    '-1100' => '\\ccxt\\BadRequest', // array("code":-1100,"msg":"createOrder(symbol, 1, asdf) -> 'Illegal characters found in parameter 'price'")
                    '-1101' => '\\ccxt\\BadRequest', // array("code":-1101,"msg":"Too many parameters; expected %s and received %s.")
                    '-1102' => '\\ccxt\\BadRequest', // array("code":-1102,"msg":"Param %s or %s must be sent, but both were empty")
                    '-1103' => '\\ccxt\\BadRequest', // array("code":-1103,"msg":"An unknown parameter was sent.")
                    '-1104' => '\\ccxt\\BadRequest', // array("code":-1104,"msg":"Not all sent parameters were read, read 8 parameters but was sent 9")
                    '-1105' => '\\ccxt\\BadRequest', // array("code":-1105,"msg":"Parameter %s was empty.")
                    '-1106' => '\\ccxt\\BadRequest', // array("code":-1106,"msg":"Parameter %s sent when not required.")
                    '-1108' => '\\ccxt\\BadRequest', // array("code":-1108,"msg":"Invalid asset.")
                    '-1109' => '\\ccxt\\AuthenticationError', // array("code":-1109,"msg":"Invalid account.")
                    '-1110' => '\\ccxt\\BadRequest', // array("code":-1110,"msg":"Invalid symbolType.")
                    '-1111' => '\\ccxt\\BadRequest', // array("code":-1111,"msg":"Precision is over the maximum defined for this asset.")
                    '-1112' => '\\ccxt\\InvalidOrder', // array("code":-1112,"msg":"No orders on book for symbol.")
                    '-1113' => '\\ccxt\\BadRequest', // array("code":-1113,"msg":"Withdrawal amount must be negative.")
                    '-1114' => '\\ccxt\\BadRequest', // array("code":-1114,"msg":"TimeInForce parameter sent when not required.")
                    '-1115' => '\\ccxt\\BadRequest', // array("code":-1115,"msg":"Invalid timeInForce.")
                    '-1116' => '\\ccxt\\BadRequest', // array("code":-1116,"msg":"Invalid orderType.")
                    '-1117' => '\\ccxt\\BadRequest', // array("code":-1117,"msg":"Invalid side.")
                    '-1118' => '\\ccxt\\BadRequest', // array("code":-1118,"msg":"New client order ID was empty.")
                    '-1119' => '\\ccxt\\BadRequest', // array("code":-1119,"msg":"Original client order ID was empty.")
                    '-1120' => '\\ccxt\\BadRequest', // array("code":-1120,"msg":"Invalid interval.")
                    '-1121' => '\\ccxt\\BadSymbol', // array("code":-1121,"msg":"Invalid symbol.")
                    '-1125' => '\\ccxt\\AuthenticationError', // array("code":-1125,"msg":"This listenKey does not exist.")
                    '-1127' => '\\ccxt\\BadRequest', // array("code":-1127,"msg":"More than %s hours between startTime and endTime.")
                    '-1128' => '\\ccxt\\BadRequest', // array("code":-1128,"msg":"array("code":-1128,"msg":"Combination of optional parameters invalid.")")
                    '-1130' => '\\ccxt\\BadRequest', // array("code":-1130,"msg":"Data sent for paramter %s is not valid.")
                    '-1131' => '\\ccxt\\BadRequest', // array("code":-1131,"msg":"recvWindow must be less than 60000")
                    '-1136' => '\\ccxt\\BadRequest', // array("code":-1136,"msg":"Invalid newOrderRespType")
                    '-2008' => '\\ccxt\\AuthenticationError', // array("code":-2008,"msg":"Invalid Api-Key ID.")
                    '-2010' => '\\ccxt\\ExchangeError', // array("code":-2010,"msg":"generic error code for createOrder -> 'Account has insufficient balance for requested action.', array("code":-2010,"msg":"Rest API trading is not enabled."), etc...")
                    '-2011' => '\\ccxt\\OrderNotFound', // array("code":-2011,"msg":"cancelOrder(1, 'BTC/USDT') -> 'UNKNOWN_ORDER'")
                    '-2013' => '\\ccxt\\OrderNotFound', // array("code":-2013,"msg":"fetchOrder (1, 'BTC/USDT') -> 'Order does not exist'")
                    '-2014' => '\\ccxt\\AuthenticationError', // array("code":-2014,"msg":"API-key format invalid.")
                    '-2015' => '\\ccxt\\AuthenticationError', // array("code":-2015,"msg":"Invalid API-key, IP, or permissions for action.")
                    '-2016' => '\\ccxt\\BadRequest', // array("code":-2016,"msg":"No trading window could be found for the symbol. Try ticker/24hrs instead.")
                    '-2018' => '\\ccxt\\InsufficientFunds', // array("code":-2018,"msg":"Balance is insufficient")
                    '-2019' => '\\ccxt\\InsufficientFunds', // array("code":-2019,"msg":"Margin is insufficient.")
                    '-2020' => '\\ccxt\\OrderNotFillable', // array("code":-2020,"msg":"Unable to fill.")
                    '-2021' => '\\ccxt\\OrderImmediatelyFillable', // array("code":-2021,"msg":"Order would immediately trigger.")
                    '-2022' => '\\ccxt\\InvalidOrder', // array("code":-2022,"msg":"ReduceOnly Order is rejected.")
                    '-2023' => '\\ccxt\\InsufficientFunds', // array(is_array(liquidation mode now.") && array_key_exists("code":-2023,"msg":"User, liquidation mode now."))
                    '-2024' => '\\ccxt\\InsufficientFunds', // array("code":-2024,"msg":"Position is not sufficient.")
                    '-2025' => '\\ccxt\\InvalidOrder', // array("code":-2025,"msg":"Reach max open order limit.")
                    '-2026' => '\\ccxt\\InvalidOrder', // array("code":-2026,"msg":"This OrderType is not supported when reduceOnly.")
                    '-2027' => '\\ccxt\\InvalidOrder', // array("code":-2027,"msg":"Exceeded the maximum allowable position at current leverage.")
                    '-2028' => '\\ccxt\\InsufficientFunds', // array("code":-2028,"msg":"Leverage is smaller than permitted => insufficient margin balance")
                    '-3000' => '\\ccxt\\ExchangeError', // array("code":-3000,"msg":"Internal server error.")
                    '-3001' => '\\ccxt\\AuthenticationError', // array("code":-3001,"msg":"Please enable 2FA first.")
                    '-3002' => '\\ccxt\\BadSymbol', // array("code":-3002,"msg":"We don't have this asset.")
                    '-3003' => '\\ccxt\\BadRequest', // array("code":-3003,"msg":"Margin account does not exist.")
                    '-3004' => '\\ccxt\\ExchangeError', // array("code":-3004,"msg":"Trade not allowed.")
                    '-3005' => '\\ccxt\\InsufficientFunds', // array("code":-3005,"msg":"Transferring out not allowed. Transfer out amount exceeds max amount.")
                    '-3006' => '\\ccxt\\InsufficientFunds', // array("code":-3006,"msg":"Your borrow amount has exceed maximum borrow amount.")
                    '-3007' => '\\ccxt\\ExchangeError', // array("code":-3007,"msg":"You have pending transaction, please try again later..")
                    '-3008' => '\\ccxt\\InsufficientFunds', // array("code":-3008,"msg":"Borrow not allowed. Your borrow amount has exceed maximum borrow amount.")
                    '-3009' => '\\ccxt\\BadRequest', // array("code":-3009,"msg":"This asset are not allowed to transfer into margin account currently.")
                    '-3010' => '\\ccxt\\ExchangeError', // array("code":-3010,"msg":"Repay not allowed. Repay amount exceeds borrow amount.")
                    '-3011' => '\\ccxt\\BadRequest', // array("code":-3011,"msg":"Your input date is invalid.")
                    '-3012' => '\\ccxt\\ExchangeError', // array("code":-3012,"msg":"Borrow is banned for this asset.")
                    '-3013' => '\\ccxt\\BadRequest', // array("code":-3013,"msg":"Borrow amount less than minimum borrow amount.")
                    '-3014' => '\\ccxt\\AccountSuspended', // array("code":-3014,"msg":"Borrow is banned for this account.")
                    '-3015' => '\\ccxt\\ExchangeError', // array("code":-3015,"msg":"Repay amount exceeds borrow amount.")
                    '-3016' => '\\ccxt\\BadRequest', // array("code":-3016,"msg":"Repay amount less than minimum repay amount.")
                    '-3017' => '\\ccxt\\ExchangeError', // array("code":-3017,"msg":"This asset are not allowed to transfer into margin account currently.")
                    '-3018' => '\\ccxt\\AccountSuspended', // array(is_array(has been banned for this account.") && array_key_exists("code":-3018,"msg":"Transferring, has been banned for this account."))
                    '-3019' => '\\ccxt\\AccountSuspended', // array("code":-3019,"msg":"Transferring out has been banned for this account.")
                    '-3020' => '\\ccxt\\InsufficientFunds', // array("code":-3020,"msg":"Transfer out amount exceeds max amount.")
                    '-3021' => '\\ccxt\\BadRequest', // array("code":-3021,"msg":"Margin account are not allowed to trade this trading pair.")
                    '-3022' => '\\ccxt\\AccountSuspended', // array("code":-3022,"msg":"You account's trading is banned.")
                    '-3023' => '\\ccxt\\BadRequest', // array("code":-3023,"msg":"You can't transfer out/place order under current margin level.")
                    '-3024' => '\\ccxt\\ExchangeError', // array("code":-3024,"msg":"The unpaid debt is too small after this repayment.")
                    '-3025' => '\\ccxt\\BadRequest', // array("code":-3025,"msg":"Your input date is invalid.")
                    '-3026' => '\\ccxt\\BadRequest', // array("code":-3026,"msg":"Your input param is invalid.")
                    '-3027' => '\\ccxt\\BadSymbol', // array("code":-3027,"msg":"Not a valid margin asset.")
                    '-3028' => '\\ccxt\\BadSymbol', // array("code":-3028,"msg":"Not a valid margin pair.")
                    '-3029' => '\\ccxt\\ExchangeError', // array("code":-3029,"msg":"Transfer failed.")
                    '-3036' => '\\ccxt\\AccountSuspended', // array("code":-3036,"msg":"This account is not allowed to repay.")
                    '-3037' => '\\ccxt\\ExchangeError', // array("code":-3037,"msg":"PNL is clearing. Wait a second.")
                    '-3038' => '\\ccxt\\BadRequest', // array("code":-3038,"msg":"Listen key not found.")
                    '-3041' => '\\ccxt\\InsufficientFunds', // array("code":-3041,"msg":"Balance is not enough")
                    '-3042' => '\\ccxt\\BadRequest', // array("code":-3042,"msg":"PriceIndex not available for this margin pair.")
                    '-3043' => '\\ccxt\\BadRequest', // array(is_array(not allowed.") && array_key_exists("code":-3043,"msg":"Transferring, not allowed."))
                    '-3044' => '\\ccxt\\DDoSProtection', // array("code":-3044,"msg":"System busy.")
                    '-3045' => '\\ccxt\\ExchangeError', // array("code":-3045,"msg":"The system doesn't have enough asset now.")
                    '-3999' => '\\ccxt\\ExchangeError', // array("code":-3999,"msg":"This function is only available for invited users.")
                    '-4001' => '\\ccxt\\BadRequest', // array("code":-4001 ,"msg":"Invalid operation.")
                    '-4002' => '\\ccxt\\BadRequest', // array("code":-4002 ,"msg":"Invalid get.")
                    '-4003' => '\\ccxt\\BadRequest', // array("code":-4003 ,"msg":"Your input email is invalid.")
                    '-4004' => '\\ccxt\\AuthenticationError', // array("code":-4004,"msg":"You don't login or auth.")
                    '-4005' => '\\ccxt\\RateLimitExceeded', // array("code":-4005 ,"msg":"Too many new requests.")
                    '-4006' => '\\ccxt\\BadRequest', // array("code":-4006 ,"msg":"Support main account only.")
                    '-4007' => '\\ccxt\\BadRequest', // array("code":-4007 ,"msg":"Address validation is not passed.")
                    '-4008' => '\\ccxt\\BadRequest', // array("code":-4008 ,"msg":"Address tag validation is not passed.")
                    '-4010' => '\\ccxt\\BadRequest', // array("code":-4010 ,"msg":"White list mail has been confirmed.") // [TODO] possible bug => it should probably be "has not been confirmed"
                    '-4011' => '\\ccxt\\BadRequest', // array("code":-4011 ,"msg":"White list mail is invalid.")
                    '-4012' => '\\ccxt\\BadRequest', // array("code":-4012 ,"msg":"White list is not opened.")
                    '-4013' => '\\ccxt\\AuthenticationError', // array("code":-4013 ,"msg":"2FA is not opened.")
                    '-4014' => '\\ccxt\\PermissionDenied', // array("code":-4014 ,"msg":"Withdraw is not allowed within 2 min login.")
                    '-4015' => '\\ccxt\\ExchangeError', // array("code":-4015 ,"msg":"Withdraw is limited.")
                    '-4016' => '\\ccxt\\PermissionDenied', // array("code":-4016 ,"msg":"Within 24 hours after password modification, withdrawal is prohibited.")
                    '-4017' => '\\ccxt\\PermissionDenied', // array("code":-4017 ,"msg":"Within 24 hours after the release of 2FA, withdrawal is prohibited.")
                    '-4018' => '\\ccxt\\BadSymbol', // array("code":-4018,"msg":"We don't have this asset.")
                    '-4019' => '\\ccxt\\BadSymbol', // array("code":-4019,"msg":"Current asset is not open for withdrawal.")
                    '-4021' => '\\ccxt\\BadRequest', // array("code":-4021,"msg":"Asset withdrawal must be an %s multiple of %s.")
                    '-4022' => '\\ccxt\\BadRequest', // array("code":-4022,"msg":"Not less than the minimum pick-up quantity %s.")
                    '-4023' => '\\ccxt\\ExchangeError', // array("code":-4023,"msg":"Within 24 hours, the withdrawal exceeds the maximum amount.")
                    '-4024' => '\\ccxt\\InsufficientFunds', // array("code":-4024,"msg":"You don't have this asset.")
                    '-4025' => '\\ccxt\\InsufficientFunds', // array("code":-4025,"msg":"The number of hold asset is less than zero.")
                    '-4026' => '\\ccxt\\InsufficientFunds', // array("code":-4026,"msg":"You have insufficient balance.")
                    '-4027' => '\\ccxt\\ExchangeError', // array("code":-4027,"msg":"Failed to obtain tranId.")
                    '-4028' => '\\ccxt\\BadRequest', // array("code":-4028,"msg":"The amount of withdrawal must be greater than the Commission.")
                    '-4029' => '\\ccxt\\BadRequest', // array("code":-4029,"msg":"The withdrawal record does not exist.")
                    '-4030' => '\\ccxt\\ExchangeError', // array("code":-4030,"msg":"Confirmation of successful asset withdrawal. [TODO] possible bug in docs")
                    '-4031' => '\\ccxt\\ExchangeError', // array("code":-4031,"msg":"Cancellation failed.")
                    '-4032' => '\\ccxt\\ExchangeError', // array("code":-4032,"msg":"Withdraw verification exception.")
                    '-4033' => '\\ccxt\\BadRequest', // array("code":-4033,"msg":"Illegal address.")
                    '-4034' => '\\ccxt\\ExchangeError', // array("code":-4034,"msg":"The address is suspected of fake.")
                    '-4035' => '\\ccxt\\PermissionDenied', // array("code":-4035,"msg":"This address is not on the whitelist. Please join and try again.")
                    '-4036' => '\\ccxt\\BadRequest', // array("code":-4036,"msg":"The new address needs to be withdrawn in {0} hours.")
                    '-4037' => '\\ccxt\\ExchangeError', // array("code":-4037,"msg":"Re-sending Mail failed.")
                    '-4038' => '\\ccxt\\ExchangeError', // array("code":-4038,"msg":"Please try again in 5 minutes.")
                    '-4039' => '\\ccxt\\BadRequest', // array("code":-4039,"msg":"The user does not exist.")
                    '-4040' => '\\ccxt\\BadRequest', // array("code":-4040,"msg":"This address not charged.")
                    '-4041' => '\\ccxt\\ExchangeError', // array("code":-4041,"msg":"Please try again in one minute.")
                    '-4042' => '\\ccxt\\ExchangeError', // array("code":-4042,"msg":"This asset cannot get deposit address again.")
                    '-4043' => '\\ccxt\\BadRequest', // array("code":-4043,"msg":"More than 100 recharge addresses were used in 24 hours.")
                    '-4044' => '\\ccxt\\BadRequest', // array("code":-4044,"msg":"This is a blacklist country.")
                    '-4045' => '\\ccxt\\ExchangeError', // array("code":-4045,"msg":"Failure to acquire assets.")
                    '-4046' => '\\ccxt\\AuthenticationError', // array("code":-4046,"msg":"Agreement not confirmed.")
                    '-4047' => '\\ccxt\\BadRequest', // array("code":-4047,"msg":"Time interval must be within 0-90 days")
                    '-5001' => '\\ccxt\\BadRequest', // array("code":-5001,"msg":"Don't allow transfer to micro assets.")
                    '-5002' => '\\ccxt\\InsufficientFunds', // array("code":-5002,"msg":"You have insufficient balance.")
                    '-5003' => '\\ccxt\\InsufficientFunds', // array("code":-5003,"msg":"You don't have this asset.")
                    '-5004' => '\\ccxt\\BadRequest', // array("code":-5004,"msg":"The residual balances of %s have exceeded 0.001BTC, Please re-choose.")
                    '-5005' => '\\ccxt\\InsufficientFunds', // array("code":-5005,"msg":"The residual balances of %s is too low, Please re-choose.")
                    '-5006' => '\\ccxt\\BadRequest', // array("code":-5006,"msg":"Only transfer once in 24 hours.")
                    '-5007' => '\\ccxt\\BadRequest', // array("code":-5007,"msg":"Quantity must be greater than zero.")
                    '-5008' => '\\ccxt\\InsufficientFunds', // array("code":-5008,"msg":"Insufficient amount of returnable assets.")
                    '-5009' => '\\ccxt\\BadRequest', // array("code":-5009,"msg":"Product does not exist.")
                    '-5010' => '\\ccxt\\ExchangeError', // array("code":-5010,"msg":"Asset transfer fail.")
                    '-5011' => '\\ccxt\\BadRequest', // array("code":-5011,"msg":"future account not exists.")
                    '-5012' => '\\ccxt\\ExchangeError', // array("code":-5012,"msg":"Asset transfer is in pending.")
                    '-5013' => '\\ccxt\\InsufficientFunds', // array("code":-5013,"msg":"Asset transfer failed => insufficient balance"") // undocumented
                    '-5021' => '\\ccxt\\BadRequest', // array("code":-5021,"msg":"This parent sub have no relation")
                    '-6001' => '\\ccxt\\BadRequest', // array("code":-6001,"msg":"Daily product not exists.")
                    '-6003' => '\\ccxt\\BadRequest', // array("code":-6003,"msg":"Product not exist or you don't have permission")
                    '-6004' => '\\ccxt\\ExchangeError', // array("code":-6004,"msg":"Product not in purchase status")
                    '-6005' => '\\ccxt\\InvalidOrder', // array("code":-6005,"msg":"Smaller than min purchase limit")
                    '-6006' => '\\ccxt\\BadRequest', // array("code":-6006,"msg":"Redeem amount error")
                    '-6007' => '\\ccxt\\BadRequest', // array(is_array(redeem time") && array_key_exists("code":-6007,"msg":"Not, redeem time"))
                    '-6008' => '\\ccxt\\BadRequest', // array("code":-6008,"msg":"Product not in redeem status")
                    '-6009' => '\\ccxt\\RateLimitExceeded', // array("code":-6009,"msg":"Request frequency too high")
                    '-6011' => '\\ccxt\\BadRequest', // array("code":-6011,"msg":"Exceeding the maximum num allowed to purchase per user")
                    '-6012' => '\\ccxt\\InsufficientFunds', // array("code":-6012,"msg":"Balance not enough")
                    '-6013' => '\\ccxt\\ExchangeError', // array("code":-6013,"msg":"Purchasing failed")
                    '-6014' => '\\ccxt\\BadRequest', // array("code":-6014,"msg":"Exceed up-limit allowed to purchased")
                    '-6015' => '\\ccxt\\BadRequest', // array("code":-6015,"msg":"Empty request body")
                    '-6016' => '\\ccxt\\BadRequest', // array("code":-6016,"msg":"Parameter err")
                    '-6017' => '\\ccxt\\BadRequest', // array(is_array(whitelist") && array_key_exists("code":-6017,"msg":"Not, whitelist"))
                    '-6018' => '\\ccxt\\BadRequest', // array("code":-6018,"msg":"Asset not enough")
                    '-6019' => '\\ccxt\\AuthenticationError', // array("code":-6019,"msg":"Need confirm")
                    '-6020' => '\\ccxt\\BadRequest', // array("code":-6020,"msg":"Project not exists")
                    '-7001' => '\\ccxt\\BadRequest', // array("code":-7001,"msg":"Date range is not supported.")
                    '-7002' => '\\ccxt\\BadRequest', // array("code":-7002,"msg":"Data request type is not supported.")
                    '-9000' => '\\ccxt\\InsufficientFunds', // array("code":-9000,"msg":"user have no avaliable amount")"
                    '-10017' => '\\ccxt\\BadRequest', // array("code":-10017,"msg":"Repay amount should not be larger than liability.")
                    '-11008' => '\\ccxt\\InsufficientFunds', // array("code":-11008,"msg":"Exceeding the account's maximum borrowable limit.") // undocumented
                    '-12014' => '\\ccxt\\RateLimitExceeded', // array("code":-12014,"msg":"More than 1 request in 3 seconds")
                    '-13000' => '\\ccxt\\BadRequest', // array("code":-13000,"msg":"Redeption of the token is forbiden now")
                    '-13001' => '\\ccxt\\BadRequest', // array("code":-13001,"msg":"Exceeds individual 24h redemption limit of the token")
                    '-13002' => '\\ccxt\\BadRequest', // array("code":-13002,"msg":"Exceeds total 24h redemption limit of the token")
                    '-13003' => '\\ccxt\\BadRequest', // array("code":-13003,"msg":"Subscription of the token is forbiden now")
                    '-13004' => '\\ccxt\\BadRequest', // array("code":-13004,"msg":"Exceeds individual 24h subscription limit of the token")
                    '-13005' => '\\ccxt\\BadRequest', // array("code":-13005,"msg":"Exceeds total 24h subscription limit of the token")
                    '-13006' => '\\ccxt\\InvalidOrder', // array("code":-13006,"msg":"Subscription amount is too small")
                    '-13007' => '\\ccxt\\AuthenticationError', // array("code":-13007,"msg":"The Agreement is not signed")
                    '-21001' => '\\ccxt\\BadRequest', // array("code":-21001,"msg":"USER_IS_NOT_UNIACCOUNT")
                    '-21002' => '\\ccxt\\BadRequest', // array("code":-21002,"msg":"UNI_ACCOUNT_CANT_TRANSFER_FUTURE")
                    '-21003' => '\\ccxt\\BadRequest', // array("code":-21003,"msg":"NET_ASSET_MUST_LTE_RATIO")
                    '100001003' => '\\ccxt\\BadRequest', // array("code":100001003,"msg":"Verification failed") // undocumented
                    '2202' => '\\ccxt\\InsufficientFunds', // array("code":2202,"msg":"Insufficient balance","data":array("code":-2010,"msg":"Account has insufficient balance for requested action."),"timestamp":1662733681161)
                    '3210' => '\\ccxt\\InvalidOrder', // array("code":3210,"msg":"The total volume is too low","data":array("code":-1013,"msg":"Filter failure => MIN_NOTIONAL"),"timestamp":1662734704462)
                    '3203' => '\\ccxt\\InvalidOrder', // array("code":3203,"msg":"Incorrect Order Quantity","timestamp":1662734809758)
                    '3211' => '\\ccxt\\InvalidOrder', // array("code":3211,"msg":"The total volume must be greater than 10","timestamp":1662739358179)
                    '3207' => '\\ccxt\\InvalidOrder', // array("code":3207,"msg":"The price cannot be lower than 12.18","timestamp":1662739502856)
                    '3218' => '\\ccxt\\OrderNotFound', // array("code":3218,"msg":"Order does not exist","timestamp":1662739749275)
                ),
                'broad' => array(
                    'has no operation privilege' => '\\ccxt\\PermissionDenied',
                    'MAX_POSITION' => '\\ccxt\\InvalidOrder', // array("code":-2010,"msg":"Filter failure => MAX_POSITION")
                ),
            ),
            'features' => array(
                'spot' => array(
                    'sandbox' => false,
                    'createOrder' => array(
                        'marginMode' => false,
                        'triggerPrice' => true,
                        'triggerDirection' => false,
                        'triggerPriceType' => null,
                        'stopLossPrice' => false, // todo
                        'takeProfitPrice' => false, // todo
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            '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' => null,
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => 1000,
                        'daysBack' => 100000, // todo
                        'untilDays' => 100000, // todo
                        'symbolRequired' => true,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => 1000,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => true,
                    ),
                    'fetchOrders' => array(
                        'marginMode' => false,
                        'limit' => 1000,
                        'daysBack' => 100000,
                        'untilDays' => 100000,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => true,
                    ),
                    'fetchClosedOrders' => array(
                        'marginMode' => false,
                        'limit' => 1000,
                        'daysBack' => 100000, // todo
                        'daysBackCanceled' => 1, // todo
                        'untilDays' => 100000, // todo
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => true,
                    ),
                    'fetchOHLCV' => array(
                        'limit' => 1000,
                    ),
                ),
                'swap' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
                'future' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
            ),
        ));
    }

    public function nonce() {
        return $this->milliseconds() - $this->options['timeDifference'];
    }

    public function fetch_time($params = array ()): ?int {
        /**
         *
         * @see https://www.tokocrypto.com/apidocs/#check-server-time
         *
         * fetches the current integer timestamp in milliseconds from the exchange server
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {int} the current integer timestamp in milliseconds from the exchange server
         */
        $response = $this->publicGetOpenV1CommonTime ($params);
        //
        // {
        //     "code" => 0,
        //     "msg" => "Success",
        //     "data" => null,
        //     "timestamp" => 1737378074159
        // }
        //
        return $this->safe_integer($response, 'timestamp');
    }

    public function fetch_markets($params = array ()): array {
        /**
         *
         * @see https://www.tokocrypto.com/apidocs/#get-all-supported-trading-$symbol
         *
         * retrieves $data on all markets for tokocrypto
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} an array of objects representing $market $data
         */
        $response = $this->publicGetOpenV1CommonSymbols ($params);
        //
        //     {
        //         "code":0,
        //         "msg":"Success",
        //         "data":array(
        //             "list":array(
        //                 array(
        //                     "type":1,
        //                     "symbol":"1INCH_BTC",
        //                     "baseAsset":"1INCH",
        //                     "basePrecision":8,
        //                     "quoteAsset":"BTC",
        //                     "quotePrecision":8,
        //                     "filters":[
        //                         array("filterType":"PRICE_FILTER","minPrice":"0.00000001","maxPrice":"1000.00000000","tickSize":"0.00000001","applyToMarket":false),
        //                         array("filterType":"PERCENT_PRICE","multiplierUp":5,"multiplierDown":0.2,"avgPriceMins":"5","applyToMarket":false),
        //                         array("filterType":"LOT_SIZE","minQty":"0.10000000","maxQty":"90000000.00000000","stepSize":"0.10000000","applyToMarket":false),
        //                         array("filterType":"MIN_NOTIONAL","avgPriceMins":"5","minNotional":"0.00010000","applyToMarket":true),
        //                         array("filterType":"ICEBERG_PARTS","applyToMarket":false,"limit":"10"),
        //                         array("filterType":"MARKET_LOT_SIZE","minQty":"0.00000000","maxQty":"79460.14117231","stepSize":"0.00000000","applyToMarket":false),
        //                         array("filterType":"TRAILING_DELTA","applyToMarket":false),
        //                         array("filterType":"MAX_NUM_ORDERS","applyToMarket":false),
        //                         array("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 ($this->options['adjustForTimeDifference']) {
            $this->load_time_difference();
        }
        $data = $this->safe_value($response, 'data', array());
        $list = $this->safe_value($data, 'list', array());
        $result = array();
        for ($i = 0; $i < count($list); $i++) {
            $market = $list[$i];
            $baseId = $this->safe_string($market, 'baseAsset');
            $quoteId = $this->safe_string($market, 'quoteAsset');
            $id = $this->safe_string($market, 'symbol');
            $lowercaseId = $this->safe_string_lower($market, 'symbol');
            $settleId = $this->safe_string($market, 'marginAsset');
            $base = $this->safe_currency_code($baseId);
            $quote = $this->safe_currency_code($quoteId);
            $settle = $this->safe_currency_code($settleId);
            $symbol = $base . '/' . $quote;
            $filters = $this->safe_value($market, 'filters', array());
            $filtersByType = $this->index_by($filters, 'filterType');
            $status = $this->safe_string($market, 'spotTradingEnable');
            $active = ($status === '1');
            $permissions = $this->safe_value($market, 'permissions', array());
            for ($j = 0; $j < count($permissions); $j++) {
                if ($permissions[$j] === 'TRD_GRP_003') {
                    $active = false;
                    break;
                }
            }
            $isMarginTradingAllowed = $this->safe_bool($market, 'isMarginTradingAllowed', false);
            $entry = array(
                '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' => null,
                'inverse' => null,
                'contractSize' => null,
                'expiry' => null,
                'expiryDatetime' => null,
                'strike' => null,
                'optionType' => null,
                'precision' => array(
                    'amount' => $this->parse_number($this->parse_precision($this->safe_string($market, 'quantityPrecision'))),
                    'price' => $this->parse_number($this->parse_precision($this->safe_string($market, 'pricePrecision'))),
                    'base' => $this->parse_number($this->parse_precision($this->safe_string($market, 'baseAssetPrecision'))),
                    'quote' => $this->parse_number($this->parse_precision($this->safe_string($market, 'quotePrecision'))),
                ),
                'limits' => array(
                    'leverage' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'amount' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'price' => array(
                        'min' => null,
                        'max' => null,
                    ),
                    'cost' => array(
                        'min' => null,
                        'max' => null,
                    ),
                ),
                'created' => null,
                'info' => $market,
            );
            if (is_array($filtersByType) && array_key_exists('PRICE_FILTER', $filtersByType)) {
                $filter = $this->safe_value($filtersByType, 'PRICE_FILTER', array());
                $entry['precision']['price'] = $this->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 null
                $entry['limits']['price'] = array(
                    'min' => $this->safe_number($filter, 'minPrice'),
                    'max' => $this->safe_number($filter, 'maxPrice'),
                );
                $entry['precision']['price'] = $filter['tickSize'];
            }
            if (is_array($filtersByType) && array_key_exists('LOT_SIZE', $filtersByType)) {
                $filter = $this->safe_value($filtersByType, 'LOT_SIZE', array());
                $entry['precision']['amount'] = $this->safe_number($filter, 'stepSize');
                $entry['limits']['amount'] = array(
                    'min' => $this->safe_number($filter, 'minQty'),
                    'max' => $this->safe_number($filter, 'maxQty'),
                );
            }
            if (is_array($filtersByType) && array_key_exists('MARKET_LOT_SIZE', $filtersByType)) {
                $filter = $this->safe_value($filtersByType, 'MARKET_LOT_SIZE', array());
                $entry['limits']['market'] = array(
                    'min' => $this->safe_number($filter, 'minQty'),
                    'max' => $this->safe_number($filter, 'maxQty'),
                );
            }
            if (is_array($filtersByType) && array_key_exists('MIN_NOTIONAL', $filtersByType)) {
                $filter = $this->safe_value($filtersByType, 'MIN_NOTIONAL', array());
                $entry['limits']['cost']['min'] = $this->safe_number_2($filter, 'minNotional', 'notional');
            }
            $result[] = $entry;
        }
        return $result;
    }

    public function fetch_order_book(string $symbol, ?int $limit = null, $params = array ()): array {
        /**
         *
         * @see https://www.tokocrypto.com/apidocs/#order-book
         *
         * fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other $data
         * @param {string} $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 {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} A dictionary of ~@link https://docs.ccxt.com/#/?id=order-book-structure order book structures~ indexed by $market symbols
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array();
        if ($limit !== null) {
            $request['limit'] = $limit; // default 100, max 5000, see https://github.com/binance/binance-spot-api-docs/blob/master/rest-api.md#order-book
        }
        $response = null;
        if ($market['quote'] === 'USDT') {
            $request['symbol'] = $market['baseId'] . $market['quoteId'];
            $response = $this->binanceGetDepth ($this->extend($request, $params));
        } else {
            $request['symbol'] = $market['id'];
            $response = $this->publicGetOpenV1MarketDepth ($this->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":array(
        //            "lastUpdateId":3204783,
        //            "bids":array(),
        //            "asks" => array()
        //         ),
        //         "timestamp":1692262634599
        //     }
        $data = $this->safe_value($response, 'data', $response);
        $timestamp = $this->safe_integer_2($response, 'T', 'timestamp');
        $orderbook = $this->parse_order_book($data, $symbol, $timestamp);
        $orderbook['nonce'] = $this->safe_integer($data, 'lastUpdateId');
        return $orderbook;
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // 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
        //     }
        //
        // array( respType => FULL )
        //
        //     {
        //       "price" => "4000.00000000",
        //       "qty" => "1.00000000",
        //       "commission" => "4.00000000",
        //       "commissionAsset" => "USDT",
        //       "tradeId" => "1234",
        //     }
        //
        $timestamp = $this->safe_integer_2($trade, 'T', 'time');
        $price = $this->safe_string_2($trade, 'p', 'price');
        $amount = $this->safe_string_2($trade, 'q', 'qty');
        $cost = $this->safe_string_2($trade, 'quoteQty', 'baseQty');  // inverse futures
        $marketId = $this->safe_string($trade, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $id = $this->safe_string_2($trade, 't', 'a');
        $id = $this->safe_string_2($trade, 'id', 'tradeId', $id);
        $side = null;
        $orderId = $this->safe_string($trade, 'orderId');
        $buyerMaker = $this->safe_value_2($trade, 'm', 'isBuyerMaker');
        $takerOrMaker = null;
        if ($buyerMaker !== null) {
            $side = $buyerMaker ? 'sell' : 'buy'; // this is reversed intentionally
            $takerOrMaker = 'taker';
        } elseif (is_array($trade) && array_key_exists('side', $trade)) {
            $side = $this->safe_string_lower($trade, 'side');
        } else {
            if (is_array($trade) && array_key_exists('isBuyer', $trade)) {
                $side = $trade['isBuyer'] ? 'buy' : 'sell'; // this is a true $side
            }
        }
        $fee = null;
        if (is_array($trade) && array_key_exists('commission', $trade)) {
            $fee = array(
                'cost' => $this->safe_string($trade, 'commission'),
                'currency' => $this->safe_currency_code($this->safe_string($trade, 'commissionAsset')),
            );
        }
        if (is_array($trade) && array_key_exists('isMaker', $trade)) {
            $takerOrMaker = $trade['isMaker'] ? 'maker' : 'taker';
        }
        if (is_array($trade) && array_key_exists('maker', $trade)) {
            $takerOrMaker = $trade['maker'] ? 'maker' : 'taker';
        }
        return $this->safe_trade(array(
            'info' => $trade,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'id' => $id,
            'order' => $orderId,
            'type' => null,
            'side' => $side,
            'takerOrMaker' => $takerOrMaker,
            'price' => $price,
            'amount' => $amount,
            'cost' => $cost,
            'fee' => $fee,
        ), $market);
    }

    public function fetch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         *
         * @see https://www.tokocrypto.com/apidocs/#recent-trades-$list
         * @see https://www.tokocrypto.com/apidocs/#compressedaggregate-trades-$list
         *
         * get the $list of most recent trades for a particular $symbol
         * @param {string} $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 {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Trade[]} a $list of ~@link https://docs.ccxt.com/#/?id=public-trades trade structures~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $this->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 !== null) {
                $request['limit'] = $limit;
            }
            $responseInner = $this->publicGetOpenV1MarketTrades ($this->extend($request, $params));
            //
            //    {
            //       "code" => 0,
            //       "msg" => "success",
            //       "data" => {
            //           "list" => array(
            //                array(
            //                    "id" => 28457,
            //                    "price" => "4.00000100",
            //                    "qty" => "12.00000000",
            //                    "time" => 1499865549590,
            //                    "isBuyerMaker" => true,
            //                    "isBestMatch" => true
            //                }
            //            )
            //        ),
            //        "timestamp" => 1571921637091
            //    }
            //
            $data = $this->safe_dict($responseInner, 'data', array());
            $list = $this->safe_list($data, 'list', array());
            return $this->parse_trades($list, $market, $since, $limit);
        }
        if ($limit !== null) {
            $request['limit'] = $limit; // default = 500, maximum = 1000
        }
        $defaultMethod = 'binanceGetTrades';
        $method = $this->safe_string($this->options, 'fetchTradesMethod', $defaultMethod);
        $response = null;
        if (($method === 'binanceGetAggTrades') && ($since !== null)) {
            $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'] = $this->sum($since, 3600000);
            $response = $this->binanceGetAggTrades ($this->extend($request, $params));
        } else {
            $response = $this->binanceGetTrades ($this->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 this $method is "aggregate" trade id
        //   which is different from actual trade id
        // - setting both fromId and time window results in error
        //
        // aggregate trades
        //
        //     array(
        //         {
        //             "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
        //
        //     array(
        //         {
        //             "id" => 28457,
        //             "price" => "4.00000100",
        //             "qty" => "12.00000000",
        //             "time" => 1499865549590,
        //             "isBuyerMaker" => true,
        //             "isBestMatch" => true
        //         }
        //     )
        //
        return $this->parse_trades($response, $market, $since, $limit);
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        //
        //     {
        //         "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 = $this->safe_integer($ticker, 'closeTime');
        $marketId = $this->safe_string($ticker, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $last = $this->safe_string($ticker, 'lastPrice');
        $isCoinm = (is_array($ticker) && array_key_exists('baseVolume', $ticker));
        $baseVolume = null;
        $quoteVolume = null;
        if ($isCoinm) {
            $baseVolume = $this->safe_string($ticker, 'baseVolume');
            $quoteVolume = $this->safe_string($ticker, 'volume');
        } else {
            $baseVolume = $this->safe_string($ticker, 'volume');
            $quoteVolume = $this->safe_string($ticker, 'quoteVolume');
        }
        return $this->safe_ticker(array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_string($ticker, 'highPrice'),
            'low' => $this->safe_string($ticker, 'lowPrice'),
            'bid' => $this->safe_string($ticker, 'bidPrice'),
            'bidVolume' => $this->safe_string($ticker, 'bidQty'),
            'ask' => $this->safe_string($ticker, 'askPrice'),
            'askVolume' => $this->safe_string($ticker, 'askQty'),
            'vwap' => $this->safe_string($ticker, 'weightedAvgPrice'),
            'open' => $this->safe_string($ticker, 'openPrice'),
            'close' => $last,
            'last' => $last,
            'previousClose' => $this->safe_string($ticker, 'prevClosePrice'), // previous day close
            'change' => $this->safe_string($ticker, 'priceChange'),
            'percentage' => $this->safe_string($ticker, 'priceChangePercent'),
            'average' => null,
            'baseVolume' => $baseVolume,
            'quoteVolume' => $quoteVolume,
            'info' => $ticker,
        ), $market);
    }

    public function fetch_tickers(?array $symbols = null, $params = array ()): array {
        /**
         *
         * @see https://binance-docs.github.io/apidocs/spot/en/#24hr-ticker-price-change-statistics
         *
         * fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each market
         * @param {string[]|null} $symbols unified $symbols of the markets to fetch the ticker for, all market tickers are returned if not assigned
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structures~
         */
        $this->load_markets();
        $response = $this->binanceGetTicker24hr ($params);
        return $this->parse_tickers($response, $symbols);
    }

    public function get_market_id_by_type($market) {
        if ($market['quote'] === 'USDT') {
            return $market['baseId'] . $market['quoteId'];
        }
        return $market['id'];
    }

    public function fetch_ticker(string $symbol, $params = array ()): array {
        /**
         *
         * @see https://binance-docs.github.io/apidocs/spot/en/#24hr-ticker-price-change-statistics
         *
         * fetches a price ticker, a statistical calculation with the information calculated over the past 24 hours for a specific $market
         * @param {string} $symbol unified $symbol of the $market to fetch the ticker for
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['baseId'] . $market['quoteId'],
        );
        $response = $this->binanceGetTicker24hr ($this->extend($request, $params));
        if (gettype($response) === 'array' && array_keys($response) === array_keys(array_keys($response))) {
            $firstTicker = $this->safe_dict($response, 0, array());
            return $this->parse_ticker($firstTicker, $market);
        }
        return $this->parse_ticker($response, $market);
    }

    public function fetch_bids_asks(?array $symbols = null, $params = array ()) {
        /**
         *
         * @see https://binance-docs.github.io/apidocs/spot/en/#symbol-order-book-ticker
         *
         * fetches the bid and ask price and volume for multiple markets
         * @param {string[]|null} $symbols unified $symbols of the markets to fetch the bids and asks for, all markets are returned if not assigned
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=ticker-structure ticker structures~
         */
        $this->load_markets();
        $response = $this->binanceGetTickerBookTicker ($params);
        return $this->parse_tickers($response, $symbols);
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        // when api method = publicGetKlines || fapiPublicGetKlines || dapiPublicGetKlines
        //     array(
        //         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 || fapiPublicGetIndexPriceKlines
        //     array(
        //         array(
        //         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 array(
            $this->safe_integer($ohlcv, 0),
            $this->safe_number($ohlcv, 1),
            $this->safe_number($ohlcv, 2),
            $this->safe_number($ohlcv, 3),
            $this->safe_number($ohlcv, 4),
            $this->safe_number($ohlcv, 5),
        );
    }

    public function fetch_ohlcv(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         *
         * @see https://binance-docs.github.io/apidocs/spot/en/#kline-candlestick-$data
         *
         * fetches historical candlestick $data containing the open, high, low, and close $price, and the volume of a $market
         * @param {string} $symbol unified $symbol of the $market to fetch OHLCV $data for
         * @param {string} $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 {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$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
         * @return {int[][]} A list of candles ordered, open, high, low, close, volume
         */
        $this->load_markets();
        $market = $this->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 = $this->safe_string($params, 'price');
        $until = $this->safe_integer($params, 'until');
        $params = $this->omit($params, array( 'price', 'until' ));
        $limit = ($limit === null) ? $defaultLimit : min ($limit, $maxLimit);
        $request = array(
            'interval' => $this->safe_string($this->timeframes, $timeframe, $timeframe),
            'limit' => $limit,
        );
        if ($price === 'index') {
            $request['pair'] = $market['id'];   // Index $price takes this argument instead of $symbol
        } else {
            $request['symbol'] = $this->get_market_id_by_type($market);
        }
        // $duration = $this->parse_timeframe($timeframe);
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        if ($until !== null) {
            $request['endTime'] = $until;
        }
        $response = null;
        if ($market['quote'] === 'USDT') {
            $response = $this->binanceGetKlines ($this->extend($request, $params));
        } else {
            $response = $this->publicGetOpenV1MarketKlines ($this->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 = $this->safe_list($response, 'data', $response);
        return $this->parse_ohlcvs($data, $market, $timeframe, $since, $limit);
    }

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

    public function parse_balance_custom($response, $type = null, $marginMode = null) {
        $timestamp = $this->safe_integer($response, 'updateTime');
        $result = array(
            'info' => $response,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
        );
        $data = $this->safe_value($response, 'data', array());
        $balances = $this->safe_value($data, 'accountAssets', array());
        for ($i = 0; $i < count($balances); $i++) {
            $balance = $balances[$i];
            $currencyId = $this->safe_string($balance, 'asset');
            $code = $this->safe_currency_code($currencyId);
            $account = $this->account();
            $account['free'] = $this->safe_string($balance, 'free');
            $account['used'] = $this->safe_string($balance, 'locked');
            $result[$code] = $account;
        }
        return $this->safe_balance($result);
    }

    public function parse_order_status(?string $status) {
        $statuses = array(
            '-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 $this->safe_string($statuses, $status, $status);
    }

    public function parse_order(array $order, ?array $market = null): array {
        //
        // 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 array( "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" => array(
        //         {
        //           "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 = $this->parse_order_status($this->safe_string($order, 'status'));
        $marketId = $this->safe_string($order, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $filled = $this->safe_string($order, 'executedQty', '0');
        $timestamp = $this->safe_integer($order, 'createTime');
        $average = $this->safe_string($order, 'avgPrice');
        $price = $this->safe_string_2($order, 'price', 'executedPrice');
        $amount = $this->safe_string($order, 'origQty');
        // - Spot/Margin $market => cummulativeQuoteQty
        //   Note this is not the actual $cost, since Binance futures uses leverage to calculate margins.
        $cost = $this->safe_string_n($order, array( 'cummulativeQuoteQty', 'cumQuote', 'executedQuoteQty', 'cumBase' ));
        $id = $this->safe_string($order, 'orderId');
        $type = $this->parse_order_type($this->safe_string_lower($order, 'type'));
        $side = $this->safe_string_lower($order, 'side');
        if ($side === '0') {
            $side = 'buy';
        } elseif ($side === '1') {
            $side = 'sell';
        }
        $fills = $this->safe_value($order, 'fills', array());
        $clientOrderId = $this->safe_string_2($order, 'clientOrderId', 'clientId');
        $timeInForce = $this->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') || ($timeInForce === 'PO');
        return $this->safe_order(array(
            'info' => $order,
            'id' => $id,
            'clientOrderId' => $clientOrderId,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'symbol' => $symbol,
            'type' => $type,
            'timeInForce' => $timeInForce,
            'postOnly' => $postOnly,
            'reduceOnly' => $this->safe_value($order, 'reduceOnly'),
            'side' => $side,
            'price' => $price,
            'triggerPrice' => $this->parse_number($this->omit_zero($this->safe_string($order, 'stopPrice'))),
            'amount' => $amount,
            'cost' => $cost,
            'average' => $average,
            'filled' => $filled,
            'remaining' => null,
            'status' => $status,
            'fee' => null,
            'trades' => $fills,
        ), $market);
    }

    public function parse_order_type($status) {
        $statuses = array(
            '2' => 'market',
            '1' => 'limit',
            '4' => 'limit',
            '7' => 'limit',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        /**
         * create a trade order
         *
         * @see https://www.tokocrypto.com/apidocs/#new-order--signed
         *
         * @param {string} $symbol unified $symbol of the $market to create an order in
         * @param {string} $type 'market' or 'limit'
         * @param {string} $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 {array} [$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
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $clientOrderId = $this->safe_string_2($params, 'clientOrderId', 'clientId');
        $postOnly = $this->safe_bool($params, 'postOnly', false);
        // only supported for spot/margin api
        if ($postOnly) {
            $type = 'LIMIT_MAKER';
        }
        $params = $this->omit($params, array( 'clientId', 'clientOrderId' ));
        $initialUppercaseType = strtoupper($type);
        $uppercaseType = $initialUppercaseType;
        $triggerPrice = $this->safe_value_2($params, 'triggerPrice', 'stopPrice');
        if ($triggerPrice !== null) {
            $params = $this->omit($params, array( 'triggerPrice', 'stopPrice' ));
            if ($uppercaseType === 'MARKET') {
                $uppercaseType = 'STOP_LOSS';
            } elseif ($uppercaseType === 'LIMIT') {
                $uppercaseType = 'STOP_LOSS_LIMIT';
            }
        }
        $validOrderTypes = $this->safe_value($market['info'], 'orderTypes');
        if (!$this->in_array($uppercaseType, $validOrderTypes)) {
            if ($initialUppercaseType !== $uppercaseType) {
                throw new InvalidOrder($this->id . ' $triggerPrice parameter is not allowed for ' . $symbol . ' ' . $type . ' orders');
            } else {
                throw new InvalidOrder($this->id . ' ' . $type . ' is not a valid order $type for the ' . $symbol . ' market');
            }
        }
        $reverseOrderTypeMapping = array(
            'LIMIT' => 1,
            'MARKET' => 2,
            'STOP_LOSS' => 3,
            'STOP_LOSS_LIMIT' => 4,
            'TAKE_PROFIT' => 5,
            'TAKE_PROFIT_LIMIT' => 6,
            'LIMIT_MAKER' => 7,
        );
        $request = array(
            'symbol' => $market['baseId'] . '_' . $market['quoteId'],
            'type' => $this->safe_string($reverseOrderTypeMapping, $uppercaseType),
        );
        if ($side === 'buy') {
            $request['side'] = 0;
        } elseif ($side === 'sell') {
            $request['side'] = 1;
        }
        if ($clientOrderId === null) {
            $broker = $this->safe_value($this->options, 'broker');
            if ($broker !== null) {
                $brokerId = $this->safe_string($broker, 'marketType');
                if ($brokerId !== null) {
                    $request['clientId'] = $brokerId . $this->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 = null;
                $createMarketBuyOrderRequiresPrice = true;
                list($createMarketBuyOrderRequiresPrice, $params) = $this->handle_option_and_params($params, 'createOrder', 'createMarketBuyOrderRequiresPrice', true);
                $cost = $this->safe_number_2($params, 'cost', 'quoteOrderQty');
                $params = $this->omit($params, array( 'cost', 'quoteOrderQty' ));
                if ($cost !== null) {
                    $quoteAmount = $cost;
                } elseif ($createMarketBuyOrderRequiresPrice) {
                    if ($price === null) {
                        throw new InvalidOrder($this->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 = $this->number_to_string($amount);
                        $priceString = $this->number_to_string($price);
                        $quoteAmount = Precise::string_mul($amountString, $priceString);
                    }
                } else {
                    $quoteAmount = $amount;
                }
                $request['quoteOrderQty'] = $this->decimal_to_precision($quoteAmount, TRUNCATE, $precision, $this->precisionMode);
            } else {
                $quantityIsRequired = true;
            }
        } elseif ($uppercaseType === 'LIMIT') {
            $priceIsRequired = true;
            $quantityIsRequired = true;
        } elseif (($uppercaseType === 'STOP_LOSS') || ($uppercaseType === 'TAKE_PROFIT')) {
            $triggerPriceIsRequired = true;
            $quantityIsRequired = true;
            if ($market['linear'] || $market['inverse']) {
                $priceIsRequired = true;
            }
        } elseif (($uppercaseType === 'STOP_LOSS_LIMIT') || ($uppercaseType === 'TAKE_PROFIT_LIMIT')) {
            $quantityIsRequired = true;
            $triggerPriceIsRequired = true;
            $priceIsRequired = true;
        } elseif ($uppercaseType === 'LIMIT_MAKER') {
            $priceIsRequired = true;
            $quantityIsRequired = true;
        }
        if ($quantityIsRequired) {
            $request['quantity'] = $this->amount_to_precision($symbol, $amount);
        }
        if ($priceIsRequired) {
            if ($price === null) {
                throw new InvalidOrder($this->id . ' createOrder() requires a $price argument for a ' . $type . ' order');
            }
            $request['price'] = $this->price_to_precision($symbol, $price);
        }
        if ($triggerPriceIsRequired) {
            if ($triggerPrice === null) {
                throw new InvalidOrder($this->id . ' createOrder() requires a $triggerPrice extra param for a ' . $type . ' order');
            } else {
                $request['stopPrice'] = $this->price_to_precision($symbol, $triggerPrice);
            }
        }
        $response = $this->privatePostOpenV1Orders ($this->extend($request, $params));
        //
        //     {
        //         "code" => 0,
        //         "msg" => "Success",
        //         "data" => array(
        //             "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 = $this->safe_dict($response, 'data', array());
        return $this->parse_order($rawOrder, $market);
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         *
         * @see https://www.tokocrypto.com/apidocs/#query-order-signed
         *
         * fetches information on an order made by the user
         * @param {string} $id order $id
         * @param {string} $symbol unified $symbol of the market the order was made in
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        $request = array(
            'orderId' => $id,
        );
        $response = $this->privateGetOpenV1Orders ($this->extend($request, $params));
        //
        //     {
        //         "code" => 0,
        //         "msg" => "Success",
        //         "data" => array(
        //             "list" => [array(
        //                 "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 = $this->safe_value($response, 'data', array());
        $list = $this->safe_value($data, 'list', array());
        $rawOrder = $this->safe_dict($list, 0, array());
        return $this->parse_order($rawOrder);
    }

    public function fetch_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         *
         * @see https://www.tokocrypto.com/apidocs/#all-$orders-signed
         *
         * fetches information on multiple $orders made by the user
         * @param {string} $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 {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchOrders() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            // 'type' => -1, // -1 = all, 1 = open, 2 = closed
            // 'side' => 1, // or 2
            // 'startTime' => $since,
            // 'endTime' => $this->milliseconds(),
            // 'fromId' => 'starting order ID', // if defined, the "direct" field becomes mandatory
            // 'direct' => 'prev', // prev, next
            // 'limit' => 500, // default 500, max 1000
        );
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->privateGetOpenV1Orders ($this->extend($request, $params));
        //
        //     {
        //         "code" => 0,
        //         "msg" => "success",
        //         "data" => {
        //             "list" => array(
        //                 array(
        //                     "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 = $this->safe_value($response, 'data', array());
        $orders = $this->safe_list($data, 'list', array());
        return $this->parse_orders($orders, $market, $since, $limit);
    }

    public function fetch_open_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         *
         * @see https://www.tokocrypto.com/apidocs/#all-orders-signed
         *
         * fetch all unfilled currently open orders
         * @param {string} $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 {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $request = array( 'type' => 1 ); // -1 = all, 1 = open, 2 = closed
        return $this->fetch_orders($symbol, $since, $limit, $this->extend($request, $params));
    }

    public function fetch_closed_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         *
         * @see https://www.tokocrypto.com/apidocs/#all-orders-signed
         *
         * fetches information on multiple closed orders made by the user
         * @param {string} $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 {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $request = array( 'type' => 2 ); // -1 = all, 1 = open, 2 = closed
        return $this->fetch_orders($symbol, $since, $limit, $this->extend($request, $params));
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         *
         * @see https://www.tokocrypto.com/apidocs/#cancel-order-signed
         *
         * cancels an open order
         * @param {string} $id order $id
         * @param {string} $symbol unified $symbol of the market the order was made in
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
         */
        $request = array(
            'orderId' => $id,
        );
        $response = $this->privatePostOpenV1OrdersCancel ($this->extend($request, $params));
        //
        //     {
        //         "code" => 0,
        //         "msg" => "Success",
        //         "data" => array(
        //             "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 = $this->safe_dict($response, 'data', array());
        return $this->parse_order($rawOrder);
    }

    public function fetch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         *
         * @see https://www.tokocrypto.com/apidocs/#account-trade-list-signed
         *
         * fetch all $trades made by the user
         * @param {string} $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 {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchMyTrades() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $endTime = $this->safe_integer_2($params, 'until', 'endTime');
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        if ($endTime !== null) {
            $request['endTime'] = $endTime;
            $params = $this->omit($params, array( 'endTime', 'until' ));
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->privateGetOpenV1OrdersTrades ($this->extend($request, $params));
        //
        //     {
        //         "code" => 0,
        //         "msg" => "success",
        //         "data" => {
        //             "list" => array(
        //                 array(
        //                     "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 = $this->safe_value($response, 'data', array());
        $trades = $this->safe_list($data, 'list', array());
        return $this->parse_trades($trades, $market, $since, $limit);
    }

    public function fetch_deposit_address(string $code, $params = array ()): array {
        /**
         * fetch the deposit $address for a $currency associated with this account
         *
         * @see https://www.tokocrypto.com/apidocs/#deposit-$address-signed
         *
         * @param {string} $code unified $currency $code
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=$address-structure $address structure~
         */
        $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'asset' => $currency['id'],
            // 'network' => 'ETH', // 'BSC', 'XMR', you can get $network and isDefault in networkList in the $response of sapiGetCapitalConfigDetail
        );
        $networks = $this->safe_value($this->options, 'networks', array());
        $network = $this->safe_string_upper($params, 'network'); // this line allows the user to specify either ERC20 or ETH
        $network = $this->safe_string($networks, $network, $network); // handle ERC20>ETH alias
        if ($network !== null) {
            $request['network'] = $network;
            $params = $this->omit($params, 'network');
        }
        // has support for the 'network' parameter
        // https://binance-docs.github.io/apidocs/spot/en/#deposit-$address-supporting-$network-user_data
        $response = $this->privateGetOpenV1DepositsAddress ($this->extend($request, $params));
        //
        //     {
        //         "code":0,
        //         "msg":"Success",
        //         "data":array(
        //             "uid":"182395",
        //             "asset":"USDT",
        //             "network":"ETH",
        //             "address":"0x101a925704f6ff13295ab8dd7a60988d116aaedf",
        //             "addressTag":"",
        //             "status":1
        //         ),
        //         "timestamp":1660685915746
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $address = $this->safe_string($data, 'address');
        $tag = $this->safe_string($data, 'addressTag', '');
        if (strlen($tag) === 0) {
            $tag = null;
        }
        $this->check_address($address);
        return array(
            'info' => $response,
            'currency' => $code,
            'network' => $this->safe_string($data, 'network'),
            'address' => $address,
            'tag' => $tag,
        );
    }

    public function fetch_deposits(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         *
         * @see https://www.tokocrypto.com/apidocs/#deposit-history-signed
         *
         * fetch all $deposits made to an account
         * @param {string} $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 {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] the latest time in ms to fetch $deposits for
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        $this->load_markets();
        $currency = null;
        $request = array();
        $until = $this->safe_integer($params, 'until');
        if ($code !== null) {
            $currency = $this->currency($code);
            $request['coin'] = $currency['id'];
        }
        if ($since !== null) {
            $request['startTime'] = $since;
            // max 3 months range https://github.com/ccxt/ccxt/issues/6495
            $endTime = $this->sum($since, 7776000000);
            if ($until !== null) {
                $endTime = min ($endTime, $until);
            }
            $request['endTime'] = $endTime;
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->privateGetOpenV1Deposits ($this->extend($request, $params));
        //
        //     {
        //         "code":0,
        //         "msg":"Success",
        //         "data":array(
        //             "list":array(
        //                 array(
        //                     "id":5167969,
        //                     "asset":"BIDR",
        //                     "network":"BSC",
        //                     "address":"0x101a925704f6ff13295ab8dd7a60988d116aaedf",
        //                     "addressTag":"",
        //                     "txId":"113409337867",
        //                     "amount":"15000",
        //                     "transferType":1,
        //                     "status":1,
        //                     "insertTime":"1659429390000"
        //                 ),
        //             )
        //         ),
        //         "timestamp":1659758865998
        //     }
        //
        $data = $this->safe_value($response, 'data', array());
        $deposits = $this->safe_list($data, 'list', array());
        return $this->parse_transactions($deposits, $currency, $since, $limit);
    }

    public function fetch_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         *
         * @see https://www.tokocrypto.com/apidocs/#withdraw-signed
         *
         * fetch all $withdrawals made from an account
         * @param {string} $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 {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
         */
        $this->load_markets();
        $request = array();
        $currency = null;
        if ($code !== null) {
            $currency = $this->currency($code);
            $request['coin'] = $currency['id'];
        }
        if ($since !== null) {
            $request['startTime'] = $since;
            // max 3 months range https://github.com/ccxt/ccxt/issues/6495
            $request['endTime'] = $this->sum($since, 7776000000);
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->privateGetOpenV1Withdraws ($this->extend($request, $params));
        //
        //     {
        //         "code":0,
        //         "msg":"Success",
        //         "data":{
        //             "list":array(
        //                 array(
        //                     "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 = $this->safe_value($response, 'data', array());
        $withdrawals = $this->safe_list($data, 'list', array());
        return $this->parse_transactions($withdrawals, $currency, $since, $limit);
    }

    public function parse_transaction_status_by_type($status, $type = null) {
        $statusesByType = array(
            'deposit' => array(
                '0' => 'pending',
                '1' => 'ok',
            ),
            'withdrawal' => array(
                '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 = $this->safe_value($statusesByType, $type, array());
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_transaction(array $transaction, ?array $currency = null): array {
        //
        // 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" => array(
        //             "withdrawId":"12"
        //         ),
        //         "timestamp" => 1571745049095
        //     }
        //
        $address = $this->safe_string($transaction, 'address');
        $tag = $this->safe_string($transaction, 'addressTag'); // set but unused
        if ($tag !== null) {
            if (strlen($tag) < 1) {
                $tag = null;
            }
        }
        $txid = $this->safe_string($transaction, 'txId');
        if (($txid !== null) && (mb_strpos($txid, 'Internal transfer ') !== false)) {
            $txid = mb_substr($txid, 18);
        }
        $currencyId = $this->safe_string_2($transaction, 'coin', 'fiatCurrency');
        $code = $this->safe_currency_code($currencyId, $currency);
        $timestamp = null;
        $insertTime = $this->safe_integer($transaction, 'insertTime');
        $createTime = $this->safe_integer_2($transaction, 'createTime', 'timestamp');
        $type = $this->safe_string($transaction, 'type');
        if ($type === null) {
            if (($insertTime !== null) && ($createTime === null)) {
                $type = 'deposit';
                $timestamp = $insertTime;
            } elseif (($insertTime === null) && ($createTime !== null)) {
                $type = 'withdrawal';
                $timestamp = $createTime;
            }
        }
        $feeCost = $this->safe_number_2($transaction, 'transactionFee', 'totalFee');
        $fee = array(
            'currency' => null,
            'cost' => null,
            'rate' => null,
        );
        if ($feeCost !== null) {
            $fee['currency'] = $code;
            $fee['cost'] = $feeCost;
        }
        $internalRaw = $this->safe_integer($transaction, 'transferType');
        $internal = false;
        if ($internalRaw !== null) {
            $internal = true;
        }
        $id = $this->safe_string($transaction, 'id');
        if ($id === null) {
            $data = $this->safe_value($transaction, 'data', array());
            $id = $this->safe_string($data, 'withdrawId');
            $type = 'withdrawal';
        }
        return array(
            'info' => $transaction,
            'id' => $id,
            'txid' => $txid,
            'type' => $type,
            'currency' => $code,
            'network' => $this->safe_string($transaction, 'network'),
            'amount' => $this->safe_number($transaction, 'amount'),
            'status' => $this->parse_transaction_status_by_type($this->safe_string($transaction, 'status'), $type),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'address' => $address,
            'addressFrom' => null,
            'addressTo' => $address,
            'tag' => $tag,
            'tagFrom' => null,
            'tagTo' => $tag,
            'updated' => $this->safe_integer_2($transaction, 'successTime', 'updateTime'),
            'comment' => null,
            'internal' => $internal,
            'fee' => $fee,
        );
    }

    public function withdraw(string $code, float $amount, string $address, ?string $tag = null, $params = array ()): array {
        /**
         *
         * @see https://www.tokocrypto.com/apidocs/#withdraw-signed
         *
         * make a withdrawal
         * @param {string} $code unified $currency $code
         * @param {float} $amount the $amount to withdraw
         * @param {string} $address the $address to withdraw to
         * @param {string} $tag
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structure~
         */
        list($tag, $params) = $this->handle_withdraw_tag_and_params($tag, $params);
        $this->load_markets();
        $this->check_address($address);
        $currency = $this->currency($code);
        $request = array(
            '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' => $this->number_to_string($amount),
        );
        if ($tag !== null) {
            $request['addressTag'] = $tag;
        }
        list($networkCode, $query) = $this->handle_network_code_and_params($params);
        $networkId = $this->network_code_to_id($networkCode);
        if ($networkId !== null) {
            $request['network'] = strtoupper($networkId);
        }
        $response = $this->privatePostOpenV1Withdraws ($this->extend($request, $query));
        //
        //     {
        //         "code" => 0,
        //         "msg" => "成功",
        //         "data" => array(
        //             "withdrawId":"12"
        //         ),
        //         "timestamp" => 1571745049095
        //     }
        //
        return $this->parse_transaction($response, $currency);
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        if (!(is_array($this->urls['api']['rest']) && array_key_exists($api, $this->urls['api']['rest']))) {
            throw new NotSupported($this->id . ' does not have a testnet/sandbox URL for ' . $api . ' endpoints');
        }
        $url = $this->urls['api']['rest'][$api];
        $url .= '/' . $path;
        if ($api === 'wapi') {
            $url .= '.html';
        }
        $userDataStream = ($path === 'userDataStream') || ($path === 'listenKey');
        if ($userDataStream) {
            if ($this->apiKey) {
                // v1 special case for $userDataStream
                $headers = array(
                    'X-MBX-APIKEY' => $this->apiKey,
                    'Content-Type' => 'application/x-www-form-urlencoded',
                );
                if ($method !== 'GET') {
                    $body = $this->urlencode($params);
                }
            } else {
                throw new AuthenticationError($this->id . ' $userDataStream endpoint requires `apiKey` credential');
            }
        } elseif (($api === 'private') || ($api === 'sapi' && $path !== 'system/status') || ($api === 'sapiV3') || ($api === 'wapi' && $path !== 'systemStatus') || ($api === 'dapiPrivate') || ($api === 'dapiPrivateV2') || ($api === 'fapiPrivate') || ($api === 'fapiPrivateV2')) {
            $this->check_required_credentials();
            $query = null;
            $defaultRecvWindow = $this->safe_integer($this->options, 'recvWindow');
            $extendedParams = $this->extend(array(
                'timestamp' => $this->nonce(),
            ), $params);
            if ($defaultRecvWindow !== null) {
                $extendedParams['recvWindow'] = $defaultRecvWindow;
            }
            $recvWindow = $this->safe_integer($params, 'recvWindow');
            if ($recvWindow !== null) {
                $extendedParams['recvWindow'] = $recvWindow;
            }
            if (($api === 'sapi') && ($path === 'asset/dust')) {
                $query = $this->urlencode_with_array_repeat($extendedParams);
            } elseif (($path === 'batchOrders') || (mb_strpos($path, 'sub-account') !== false) || ($path === 'capital/withdraw/apply') || (mb_strpos($path, 'staking') !== false)) {
                $query = $this->rawencode($extendedParams);
            } else {
                $query = $this->urlencode($extendedParams);
            }
            $signature = $this->hmac($this->encode($query), $this->encode($this->secret), 'sha256');
            $query .= '&' . 'signature=' . $signature;
            $headers = array(
                'X-MBX-APIKEY' => $this->apiKey,
            );
            if (($method === 'GET') || ($method === 'DELETE') || ($api === 'wapi')) {
                $url .= '?' . $query;
            } else {
                $body = $query;
                $headers['Content-Type'] = 'application/x-www-form-urlencoded';
            }
        } else {
            if ($params) {
                $url .= '?' . $this->urlencode($params);
            }
        }
        return array( 'url' => $url, 'method' => $method, 'body' => $body, 'headers' => $headers );
    }

    public function handle_errors(int $code, string $reason, string $url, string $method, array $headers, string $body, $response, $requestHeaders, $requestBody) {
        if (($code === 418) || ($code === 429)) {
            throw new DDoSProtection($this->id . ' ' . (string) $code . ' ' . $reason . ' ' . $body);
        }
        // $error $response in a form => array( "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 (mb_strpos($body, 'Price * QTY is zero or less') !== false) {
                throw new InvalidOrder($this->id . ' order cost = amount * price is zero or less ' . $body);
            }
            if (mb_strpos($body, 'LOT_SIZE') !== false) {
                throw new InvalidOrder($this->id . ' order amount should be evenly divisible by lot size ' . $body);
            }
            if (mb_strpos($body, 'PRICE_FILTER') !== false) {
                throw new InvalidOrder($this->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 $this->price_to_precision(symbol, amount) ' . $body);
            }
        }
        if ($response === null) {
            return null; // fallback to default $error handler
        }
        // check $success value for wapi endpoints
        // $response in format array('msg' => 'The coin does not exist.', 'success' => true/false)
        $success = $this->safe_bool($response, 'success', true);
        if (!$success) {
            $messageInner = $this->safe_string($response, 'msg');
            $parsedMessage = null;
            if ($messageInner !== null) {
                try {
                    $parsedMessage = json_decode($messageInner, $as_associative_array = true);
                } catch (Exception $e) {
                    // do nothing
                    $parsedMessage = null;
                }
                if ($parsedMessage !== null) {
                    $response = $parsedMessage;
                }
            }
        }
        $message = $this->safe_string($response, 'msg');
        if ($message !== null) {
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $message, $this->id . ' ' . $message);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $message, $this->id . ' ' . $message);
        }
        // checks against $error codes
        $error = $this->safe_string($response, 'code');
        if ($error !== null) {
            // https://github.com/ccxt/ccxt/issues/6501
            // https://github.com/ccxt/ccxt/issues/7742
            if (($error === '200') || Precise::string_equals($error, '0')) {
                return null;
            }
            // a workaround for array("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') && $this->options['hasAlreadyAuthenticatedSuccessfully']) {
                throw new DDoSProtection($this->id . ' ' . $body);
            }
            $feedback = $this->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 array("code":-4046,"msg":"No need to change margin type.")
                throw new MarginModeAlreadySet($feedback);
            }
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $error, $feedback);
            throw new ExchangeError($feedback);
        }
        if (!$success) {
            throw new ExchangeError($this->id . ' ' . $body);
        }
        return null;
    }

    public function calculate_rate_limiter_cost($api, $method, $path, $params, $config = array ()) {
        if ((is_array($config) && array_key_exists('noCoin', $config)) && !(is_array($params) && array_key_exists('coin', $params))) {
            return $config['noCoin'];
        } elseif ((is_array($config) && array_key_exists('noSymbol', $config)) && !(is_array($params) && array_key_exists('symbol', $params))) {
            return $config['noSymbol'];
        } elseif ((is_array($config) && array_key_exists('noPoolId', $config)) && !(is_array($params) && array_key_exists('poolId', $params))) {
            return $config['noPoolId'];
        } elseif ((is_array($config) && array_key_exists('byLimit', $config)) && (is_array($params) && array_key_exists('limit', $params))) {
            $limit = $params['limit'];
            $byLimit = $config['byLimit'];
            for ($i = 0; $i < count($byLimit); $i++) {
                $entry = $byLimit[$i];
                if ($limit <= $entry[0]) {
                    return $entry[1];
                }
            }
        }
        return $this->safe_integer($config, 'cost', 1);
    }
}
