<?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\toobit as Exchange;

class toobit extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'toobit',
            'name' => 'Toobit',
            'countries' => array( 'KY' ), // Cayman Islands
            'version' => 'v1',
            'rateLimit' => 20, // 50 requests per second
            'certified' => false,
            'pro' => false,
            'has' => array(
                'CORS' => null,
                'spot' => true,
                'margin' => false,
                'swap' => true,
                'future' => false,
                'option' => false,
                'cancelAllOrders' => true,
                'cancelOrder' => true,
                'cancelOrders' => true,
                'createOrder' => true,
                'fetchBalance' => true,
                'fetchBidsAsks' => true,
                'fetchCurrencies' => true,
                'fetchDepositAddress' => true,
                'fetchDeposits' => true,
                'fetchFundingRateHistory' => true,
                'fetchFundingRates' => true,
                'fetchIndexOHLCV' => true,
                'fetchLastPrices' => true,
                'fetchLedger' => true,
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => true,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrders' => true,
                'fetchStatus' => true,
                'fetchTickers' => true,
                'fetchTime' => true,
                'fetchTrades' => true,
                'fetchWithdrawals' => true,
                'setMarginMode' => true,
                'transfer' => true,
                'withdraw' => true,
            ),
            'urls' => array(
                'logo' => 'https://github.com/user-attachments/assets/3fc13870-5406-431b-8be0-2aab69c4f225',
                'api' => array(
                    'common' => 'https://api.toobit.com',
                    'private' => 'https://api.toobit.com',
                ),
                'www' => 'https://www.toobit.com/',
                'doc' => array(
                    'https://toobit-docs.github.io/apidocs/spot/v1/en/',
                    'https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/',
                ),
                'referral' => array(
                    'url' => 'https://www.toobit.com/en-US/r?i=IFFPy0',
                    'discount' => 0.1,
                ),
                'fees' => 'https://www.toobit.com/fee',
            ),
            'api' => array(
                'common' => array(
                    'get' => array(
                        'api/v1/time' => 1,
                        'api/v1/ping' => 1,
                        'api/v1/exchangeInfo' => 1,
                        'quote/v1/depth' => 1, // todo => by limit 1-10
                        'quote/v1/depth/merged' => 1,
                        'quote/v1/trades' => 1,
                        'quote/v1/klines' => 1,
                        'quote/v1/index/klines' => 1,
                        'quote/v1/markPrice/klines' => 1,
                        'quote/v1/markPrice' => 1,
                        'quote/v1/index' => 1,
                        'quote/v1/ticker/24hr' => 40, // todo => 1-40 depenidng noSymbol
                        'quote/v1/contract/ticker/24hr' => 40, // todo => 1-40 depenidng noSymbol
                        'quote/v1/ticker/price' => 1,
                        'quote/v1/ticker/bookTicker' => 1,
                        'api/v1/futures/fundingRate' => 1,
                        'api/v1/futures/historyFundingRate' => 1,
                    ),
                ),
                'private' => array(
                    'get' => array(
                        'api/v1/account' => 5,
                        'api/v1/account/checkApiKey' => 1,
                        'api/v1/spot/order' => 1 * 1.67,
                        'api/v1/spot/openOrders' => 1 * 1.67,
                        'api/v1/futures/openOrders' => 1 * 1.67,
                        'api/v1/spot/tradeOrders' => 5 * 1.67,
                        'api/v1/futures/historyOrders' => 5 * 1.67,
                        'api/v1/account/trades' => 5 * 1.67,
                        'api/v1/account/balanceFlow' => 5,
                        'api/v1/account/depositOrders' => 5,
                        'api/v1/account/withdrawOrders' => 5,
                        'api/v1/account/deposit/address' => 1,
                        // contracts
                        'api/v1/subAccount' => 5,
                        'api/v1/futures/accountLeverage' => 1,
                        'api/v1/futures/order' => 1 * 1.67,
                        'api/v1/futures/positions' => 5 * 1.67,
                        'api/v1/futures/balance' => 5,
                        'api/v1/futures/userTrades' => 5 * 1.67,
                        'api/v1/futures/balanceFlow' => 5,
                        'api/v1/futures/commissionRate' => 5,
                        'api/v1/futures/todayPnl' => 5,
                    ),
                    'post' => array(
                        'api/v1/spot/orderTest' => 1 * 1.67,
                        'api/v1/spot/order' => 1 * 1.67,
                        'api/v1/futures/order' => 1 * 1.67,
                        'api/v1/spot/batchOrders' => 2 * 1.67,
                        'api/v1/subAccount/transfer' => 1,
                        'api/v1/account/withdraw' => 1,
                        // contracts
                        'api/v1/futures/marginType' => 1,
                        'api/v1/futures/leverage' => 1,
                        'api/v1/futures/batchOrders' => 2 * 1.67,
                        'api/v1/futures/position/trading-stop' => 3 * 1.67,
                        'api/v1/futures/positionMargin' => 1,
                        'api/v1/userDataStream' => 1,
                        'api/v1/listenKey' => 1,
                    ),
                    'delete' => array(
                        'api/v1/spot/order' => 1 * 1.67,
                        'api/v1/futures/order' => 1 * 1.67,
                        'api/v1/spot/openOrders' => 5 * 1.67,
                        'api/v1/futures/batchOrders' => 5 * 1.67,
                        'api/v1/spot/cancelOrderByIds' => 5 * 1.67,
                        'api/v1/futures/cancelOrderByIds' => 5 * 1.67,
                        'api/v1/listenKey' => 1,
                    ),
                    'put' => array(
                        'api/v1/listenKey' => 1,
                    ),
                ),
            ),
            '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',
                '1w' => '1w',
                '1M' => '1M',
            ),
            'precisionMode' => TICK_SIZE,
            'exceptions' => array(
                'exact' => array(
                    '-1000' => '\\ccxt\\OperationFailed', // An unknown error occurred while processing the request.
                    '-1001' => '\\ccxt\\OperationFailed', // Internal error; unable to process your request. Please try again.
                    '-1002' => '\\ccxt\\PermissionDenied', // You are not authorized to execute this request.
                    '-1003' => '\\ccxt\\RateLimitExceeded', // TOO_MANY_REQUESTS
                    '-1004' => '\\ccxt\\BadRequest', // array("code":-1004,"msg":"Missing required parameter \u0027xyz\u0027") | array("code":-1004,"msg":"Bad request")
                    '-1006' => '\\ccxt\\OperationFailed', // An unexpected response was received from the message bus. Execution status unknown
                    '-1007' => '\\ccxt\\OperationFailed', // Timeout waiting for response from backend server. Send status unknown; execution status unknown.
                    '-1014' => '\\ccxt\\OperationFailed', // Unsupported order combination.
                    '-1015' => '\\ccxt\\RateLimitExceeded', // Too many new orders
                    '-1016' => '\\ccxt\\OperationRejected', // This service is no longer available.
                    '-1020' => '\\ccxt\\OperationRejected', // This operation is not supported.
                    '-1021' => '\\ccxt\\OperationRejected', // Timestamp for this request is outside of the recvWindow.
                    '-1022' => '\\ccxt\\OperationRejected', // Signature for this request is not valid.
                    '-1100' => '\\ccxt\\BadRequest', // Illegal characters found in a parameter.
                    '-1101' => '\\ccxt\\BadRequest', // Too many parameters sent for this endpoint.
                    '-1102' => '\\ccxt\\BadRequest', // A mandatory parameter was not sent, was empty/null, or malformed
                    '-1103' => '\\ccxt\\BadRequest', // An unknown parameter was sent
                    '-1104' => '\\ccxt\\BadRequest', // Not all sent parameters were read
                    '-1105' => '\\ccxt\\BadRequest', // A parameter was empty
                    '-1106' => '\\ccxt\\BadRequest', // A parameter was sent when not required
                    '-1111' => '\\ccxt\\BadRequest', // Precision is over the maximum defined for this asset.
                    '-1112' => '\\ccxt\\OperationRejected', // No orders on book for symbol.
                    '-1114' => '\\ccxt\\BadRequest', // TimeInForce parameter sent when not required.
                    '-1115' => '\\ccxt\\BadRequest', // Invalid timeInForce
                    '-1116' => '\\ccxt\\BadRequest', // Invalid orderType
                    '-1117' => '\\ccxt\\BadRequest', // Invalid side
                    '-1118' => '\\ccxt\\InvalidOrder', // New client order ID was empty.
                    '-1119' => '\\ccxt\\InvalidOrder', // Original client order ID was empty
                    '-1120' => '\\ccxt\\BadRequest', // Invalid interval
                    '-1121' => '\\ccxt\\BadRequest', // Invalid symbol
                    '-1125' => '\\ccxt\\OperationRejected', // This listenKey does not exist.
                    '-1127' => '\\ccxt\\OperationRejected', // Lookup interval is too big
                    '-1128' => '\\ccxt\\BadRequest', // Combination of optional parameters invalid
                    '-1130' => '\\ccxt\\BadRequest', // Invalid data sent for a parameter
                    '-1132' => '\\ccxt\\OperationRejected', // Order price too high
                    '-1133' => '\\ccxt\\OperationRejected', // Order price lower than the minimum,please check general broker info
                    '-1134' => '\\ccxt\\OperationRejected', // Order price decimal too long,please check general broker info
                    '-1135' => '\\ccxt\\OperationRejected', // Order quantity too large
                    '-1136' => '\\ccxt\\OperationRejected', // Order quantity lower than the minimum
                    '-1137' => '\\ccxt\\OperationRejected', // Order quantity decimal too long
                    '-1138' => '\\ccxt\\OperationRejected', // Order price exceeds permissible range
                    '-1139' => '\\ccxt\\OperationRejected', // Order has been filled
                    '-1140' => '\\ccxt\\OperationRejected', // Transaction amount lower than the minimum
                    '-1141' => '\\ccxt\\InvalidOrder', // Duplicate clientOrderId
                    '-1142' => '\\ccxt\\InvalidOrder', // Order has been canceled
                    '-1143' => '\\ccxt\\InvalidOrder', // Cannot be found on order book
                    '-1144' => '\\ccxt\\OperationRejected', // Order has been locked
                    '-1145' => '\\ccxt\\OperationRejected', // This order type does not support cancellation
                    '-1146' => '\\ccxt\\OperationFailed', // Order creation timeout
                    '-1147' => '\\ccxt\\OperationFailed', // Order cancellation timeout
                    '-1193' => '\\ccxt\\OperationRejected', // Create order count limit
                    '-1194' => '\\ccxt\\OperationRejected', // Create market order forbidden
                    '-1195' => '\\ccxt\\OperationRejected', // Create limit order price too small
                    '-1196' => '\\ccxt\\OperationRejected', // Create limit order price too big
                    '-1197' => '\\ccxt\\OperationRejected', // Create limit order buy price too big
                    '-1198' => '\\ccxt\\OperationRejected', // Create limit order sell price too small
                    '-1199' => '\\ccxt\\OperationRejected', // Create order buy quantity too small
                    '-1200' => '\\ccxt\\OperationRejected', // Create order buy quantity too big
                    '-1201' => '\\ccxt\\OperationRejected', // Create limit order sell price too big
                    '-1202' => '\\ccxt\\OperationRejected', // Create order sell quantity too small
                    '-1203' => '\\ccxt\\OperationRejected', // Create order sell quantity too big
                    '-1206' => '\\ccxt\\OperationRejected', // Orders over the maximum transaction amount
                    '-2010' => '\\ccxt\\OperationFailed', // NEW_ORDER_REJECTED
                    '-2011' => '\\ccxt\\OperationFailed', // CANCEL_REJECTED
                    '-2013' => '\\ccxt\\InvalidOrder', // Order does not exist.
                    '-2014' => '\\ccxt\\PermissionDenied', // API-key format invalid.
                    '-2015' => '\\ccxt\\PermissionDenied', // Invalid API-key, IP, or permissions for action.
                    '-2016' => '\\ccxt\\BadRequest', // No trading window could be found for the symbol. Try ticker/24hrs instead.
                    // errors above 3xxx are from swap API
                    '-3050' => '\\ccxt\\ExchangeError', // CREATE_API_KEY_EXCEED_LIMIT
                    '-3101' => '\\ccxt\\OperationRejected', // open margin account error
                    '-3102' => '\\ccxt\\OperationRejected', // get margin safety error
                    '-3103' => '\\ccxt\\BadRequest', // risk config is not exit
                    '-3105' => '\\ccxt\\OperationRejected', // token can not borrow
                    '-3107' => '\\ccxt\\OperationRejected', // token can not withdraw
                    '-3108' => '\\ccxt\\OperationRejected', // get token avail withdraw error
                    '-3109' => '\\ccxt\\OperationRejected', // margin withdraw failed
                    '-3110' => '\\ccxt\\InsufficientFunds', // margin avail withdraw not enough failed
                    '-3116' => '\\ccxt\\OperationRejected', // repay fail
                    '-3117' => '\\ccxt\\OperationRejected', // get margin all position fail
                    '-3120' => '\\ccxt\\OperationRejected', // get repay order fail
                    '-3124' => '\\ccxt\\OperationRejected', // Position and order data error
                    '-3125' => '\\ccxt\\OperationRejected', // Position size cannot meet target leverage
                    '-3126' => '\\ccxt\\OperationRejected', // Adjust leverage fail
                    '-3127' => '\\ccxt\\OperationFailed', // Adjust leverage timeout
                    '-3128' => '\\ccxt\\OperationRejected', // The margin mode cannot be changed while you have an open order/position
                    '-3129' => '\\ccxt\\BadRequest', // cone futures change position type error
                    '-3130' => '\\ccxt\\OperationRejected', // order margin insufficient
                    '-3131' => '\\ccxt\\NotSupported', // Leverage reduction is not supported in Isolated Margin Mode with open positions.
                ),
                'broad' => array(
                    'Unknown order sent' => '\\ccxt\\OrderNotFound',
                    'Duplicate order sent' => '\\ccxt\\InvalidOrder',
                    'Market is closed' => '\\ccxt\\OperationRejected',
                    'Account has insufficient balance for requested action' => '\\ccxt\\InsufficientFunds',
                    'Market orders are not supported for this symbol' => '\\ccxt\\OperationRejected',
                    'Iceberg orders are not supported for this symbol' => '\\ccxt\\OperationRejected',
                    'Stop loss orders are not supported for this symbol' => '\\ccxt\\OperationRejected',
                    'Stop loss limit orders are not supported for this symbol' => '\\ccxt\\OperationRejected',
                    'Take profit orders are not supported for this symbol' => '\\ccxt\\OperationRejected',
                    'Take profit limit orders are not supported for this symbol' => '\\ccxt\\OperationRejected',
                    'QTY is zero or less' => '\\ccxt\\BadRequest',
                    'IcebergQty exceeds QTY' => '\\ccxt\\OperationRejected',
                    'This action disabled is on this account' => '\\ccxt\\PermissionDenied',
                    'Unsupported order combination' => '\\ccxt\\BadRequest',
                    'Order would trigger immediately' => '\\ccxt\\OperationRejected',
                    'Cancel order is invalid. Check origClOrdId and orderId' => '\\ccxt\\OperationRejected',
                    'Order would immediately match and take' => '\\ccxt\\OperationRejected',
                ),
            ),
            'commonCurrencies' => array(),
            'options' => array(
                'defaultType' => 'spot',
                'accountsByType' => array(
                    'spot' => 'MAIN',
                    'swap' => 'FUTURES',
                ),
                'networks' => array(
                    'BTC' => 'BTC',
                    'ERC20' => 'ETH',
                    'ETH' => 'ETH',
                    'BEP20' => 'BSC',
                    'TRC20' => 'TRX',
                    'SOL' => 'SOL',
                    'MATIC' => 'MATIC',
                    'ARBONE' => 'ARBITRUM',
                    'BASE' => 'BASE',
                    'TON' => 'TON',
                    'AVAXC' => 'AVAXC',
                    'DOGE' => 'DOGE',
                    'XRP' => 'XRP',
                    'DOT' => 'DOT',
                    'ADA' => 'ADA',
                    'LTC' => 'LTC',
                    'APT' => 'APT',
                    'ATOM' => 'ATOM',
                    'ALGO' => 'ALGO',
                    'NEAR' => 'NEAR',
                    'XLM' => 'XLM',
                    'SUI' => 'SUI',
                    'ETC' => 'ETC',
                    'EOS' => 'EOS',
                    'WAVES' => 'WAVES',
                    'ICP' => 'ICP',
                    'ONE' => 'ONE',
                    // 'CHZ2' => 'CHZ2',
                ),
                'networksById' => array(
                    'ETH' => 'ERC20',
                    'ERC20' => 'ERC20',
                ),
            ),
            'features' => array(
                'spot' => array(
                    'sandbox' => false,
                    'createOrder' => array(
                        'marginMode' => false,
                        'triggerPrice' => true,
                        'triggerPriceType' => null,
                        'triggerDirection' => false,
                        'stopLossPrice' => false,
                        'takeProfitPrice' => false,
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => true,
                            'PO' => true,
                            'GTD' => false,
                        ),
                        'hedged' => false,
                        'trailing' => false,
                        'leverage' => false,
                        'marketBuyRequiresPrice' => false,
                        'marketBuyByCost' => false,
                        'selfTradePrevention' => false,
                        'iceberg' => false,
                    ),
                    'createOrders' => null,
                    'fetchOHLCV' => array(
                        'limit' => 1000,
                    ),
                    'fetchMyTrades' => array(
                        'marginMode' => false,
                        'limit' => 1000,
                        'daysBack' => 100000,
                        'untilDays' => 100000,
                        'symbolRequired' => true,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => 1000,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => array(
                        'marginMode' => false,
                        'limit' => 500,
                        'daysBack' => 100000,
                        'untilDays' => 100000,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchClosedOrders' => null,
                ),
                'forDerivatives' => array(
                    'createOrders' => null,
                ),
                'swap' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
                'future' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
            ),
        ));
    }

    public function fetch_status($params = array ()) {
        /**
         * the latest known information on the availability of the exchange API
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#test-connectivity
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=exchange-status-structure status structure~
         */
        $response = $this->commonGetApiV1Ping ($params);
        return array(
            'status' => 'ok',
            'updated' => null,
            'eta' => null,
            'url' => null,
            'info' => $response,
        );
    }

    public function fetch_time($params = array ()): ?int {
        /**
         * fetches the current integer timestamp in milliseconds from the exchange server
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#check-server-time
         *
         * @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->commonGetApiV1Time ($params);
        //
        //     {
        //         "serverTime" => 1699827319559
        //     }
        //
        return $this->safe_integer($response, 'serverTime');
    }

    public function fetch_currencies($params = array ()): ?array {
        /**
         * fetches all available currencies on an exchange
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an associative dictionary of currencies
         */
        $response = $this->commonGetApiV1ExchangeInfo ($params);
        $this->options['exchangeInfo'] = $response; // we store it in options for later use in fetchMarkets
        //
        //    {
        //        "timezone" => "UTC",
        //        "serverTime" => "1755583099926",
        //        "brokerFilters" => array(),
        //        "symbols" => array(
        //            {
        //                "filters" => array(
        //                    array(
        //                        "minPrice" => "0.01",
        //                        "maxPrice" => "10000000.00000000",
        //                        "tickSize" => "0.01",
        //                        "filterType" => "PRICE_FILTER"
        //                    ),
        //                    array(
        //                        "minQty" => "0.0001",
        //                        "maxQty" => "4000",
        //                        "stepSize" => "0.0001",
        //                        "filterType" => "LOT_SIZE"
        //                    ),
        //                    array(
        //                        "minNotional" => "5",
        //                        "filterType" => "MIN_NOTIONAL"
        //                    ),
        //                    array(
        //                        "minAmount" => "5",
        //                        "maxAmount" => "6600000",
        //                        "minBuyPrice" => "0.01",
        //                        "filterType" => "TRADE_AMOUNT"
        //                    ),
        //                    array(
        //                        "maxSellPrice" => "99999999",
        //                        "buyPriceUpRate" => "0.1",
        //                        "sellPriceDownRate" => "0.1",
        //                        "filterType" => "LIMIT_TRADING"
        //                    ),
        //                    array(
        //                        "buyPriceUpRate" => "0.1",
        //                        "sellPriceDownRate" => "0.1",
        //                        "filterType" => "MARKET_TRADING"
        //                    ),
        //                    {
        //                        "noAllowMarketStartTime" => "0",
        //                        "noAllowMarketEndTime" => "0",
        //                        "limitOrderStartTime" => "0",
        //                        "limitOrderEndTime" => "0",
        //                        "limitMinPrice" => "0",
        //                        "limitMaxPrice" => "0",
        //                        "filterType" => "OPEN_QUOTE"
        //                    }
        //                ),
        //                "exchangeId" => "301",
        //                "symbol" => "ETHUSDT",
        //                "symbolName" => "ETHUSDT",
        //                "status" => "TRADING",
        //                "baseAsset" => "ETH",
        //                "baseAssetName" => "ETH",
        //                "baseAssetPrecision" => "0.0001",
        //                "quoteAsset" => "USDT",
        //                "quoteAssetName" => "USDT",
        //                "quotePrecision" => "0.01",
        //                "icebergAllowed" => false,
        //                "isAggregate" => false,
        //                "allowMargin" => true,
        //             }
        //        ),
        //        "options" => array(),
        //        "contracts" => array(
        //            array(
        //                 "filters" => array( ... ),
        //                 "exchangeId" => "301",
        //                 "symbol" => "BTC-SWAP-USDT",
        //                 "symbolName" => "BTC-SWAP-USDTUSDT",
        //                 "status" => "TRADING",
        //                 "baseAsset" => "BTC-SWAP-USDT",
        //                 "baseAssetPrecision" => "0.001",
        //                 "quoteAsset" => "USDT",
        //                 "quoteAssetPrecision" => "0.1",
        //                 "icebergAllowed" => false,
        //                 "inverse" => false,
        //                 "index" => "BTC",
        //                 "indexToken" => "BTCUSDT",
        //                 "marginToken" => "USDT",
        //                 "marginPrecision" => "0.0001",
        //                 "contractMultiplier" => "0.001",
        //                 "underlying" => "BTC",
        //                 "riskLimits" => array(
        //                     array(
        //                         "riskLimitId" => "200020911",
        //                         "quantity" => "42000.0",
        //                         "initialMargin" => "0.02",
        //                         "maintMargin" => "0.01",
        //                         "isWhite" => false
        //                     ),
        //                     array(
        //                         "riskLimitId" => "200020912",
        //                         "quantity" => "84000.0",
        //                         "initialMargin" => "0.04",
        //                         "maintMargin" => "0.02",
        //                         "isWhite" => false
        //                     ),
        //                     ...
        //                 )
        //            ),
        //        ),
        //        "coins" => array(
        //            {
        //                "orgId" => "9001",
        //                "coinId" => "TCOM",
        //                "coinName" => "TCOM",
        //                "coinFullName" => "TCOM",
        //                "allowWithdraw" => true,
        //                "allowDeposit" => true,
        //                "chainTypes" => [
        //                    array(
        //                        "chainType" => "BSC",
        //                        "withdrawFee" => "49.55478",
        //                        "minWithdrawQuantity" => "77",
        //                        "maxWithdrawQuantity" => "0",
        //                        "minDepositQuantity" => "48",
        //                        "allowDeposit" => true,
        //                        "allowWithdraw" => false
        //                    }
        //                ),
        //                "isVirtual" => false
        //            ),
        //          ...
        //
        $coins = $this->safe_list($response, 'coins', array());
        $result = array();
        for ($i = 0; $i < count($coins); $i++) {
            $coin = $coins[$i];
            $parsed = $this->parse_currency($coin);
            $code = $parsed['code'];
            $result[$code] = $parsed;
        }
        return $result;
    }

    public function parse_currency(array $rawCurrency): array {
        $id = $this->safe_string($rawCurrency, 'coinId');
        $code = $this->safe_currency_code($id);
        $networks = array();
        $rawNetworks = $this->safe_list($rawCurrency, 'chainTypes');
        for ($j = 0; $j < count($rawNetworks); $j++) {
            $rawNetwork = $rawNetworks[$j];
            $networkId = $this->safe_string($rawNetwork, 'chainType');
            $networkCode = $this->network_id_to_code($networkId);
            $networks[$networkCode] = array(
                'id' => $networkId,
                'network' => $networkCode,
                'margin' => null,
                'deposit' => $this->safe_bool($rawNetwork, 'allowDeposit'),
                'withdraw' => $this->safe_bool($rawNetwork, 'allowWithdraw'),
                'active' => null,
                'fee' => $this->safe_number($rawNetwork, 'withdrawFee'),
                'precision' => null,
                'limits' => array(
                    'deposit' => array(
                        'min' => $this->safe_number($rawNetwork, 'minDepositQuantity'),
                        'max' => null,
                    ),
                    'withdraw' => array(
                        'min' => $this->safe_number($rawNetwork, 'minWithdrawQuantity'),
                        'max' => $this->safe_number($rawNetwork, 'maxWithdrawQuantity'),
                    ),
                ),
                'info' => $rawNetwork,
            );
        }
        return $this->safe_currency_structure(array(
            'id' => $id,
            'code' => $code,
            'name' => $this->safe_string($rawCurrency, 'coinFullName'),
            'type' => null,
            'active' => null,
            'deposit' => $this->safe_bool($rawCurrency, 'allowDeposit'),
            'withdraw' => $this->safe_bool($rawCurrency, 'allowWithdraw'),
            'fee' => null,
            'precision' => null,
            'limits' => array(
                'amount' => array(
                    'min' => null,
                    'max' => null,
                ),
                'withdraw' => array(
                    'min' => null,
                    'max' => null,
                ),
            ),
            'networks' => $networks,
            'info' => $rawCurrency,
        ));
    }

    public function fetch_markets($params = array ()): array {
        /**
         * retrieves data on $all markets for toobit
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#exchange-information
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#exchange-information
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} an array of objects representing $market data
         */
        $response = $this->safe_dict($this->options, 'exchangeInfo');
        if ($response !== null) {
            $this->options['exchangeInfo'] = null; // reset it to avoid using old cached data
        } else {
            $response = $this->commonGetApiV1ExchangeInfo ($params);
        }
        //
        //    {
        //        "timezone" => "UTC",
        //        "serverTime" => "1755583099926",
        //        "brokerFilters" => array(),
        //        "symbols" => array(
        //            {
        //                "filters" => array(
        //                    array(
        //                        "minPrice" => "0.01",
        //                        "maxPrice" => "10000000.00000000",
        //                        "tickSize" => "0.01",
        //                        "filterType" => "PRICE_FILTER"
        //                    ),
        //                    array(
        //                        "minQty" => "0.0001",
        //                        "maxQty" => "4000",
        //                        "stepSize" => "0.0001",
        //                        "filterType" => "LOT_SIZE"
        //                    ),
        //                    array(
        //                        "minNotional" => "5",
        //                        "filterType" => "MIN_NOTIONAL"
        //                    ),
        //                    array(
        //                        "minAmount" => "5",
        //                        "maxAmount" => "6600000",
        //                        "minBuyPrice" => "0.01",
        //                        "filterType" => "TRADE_AMOUNT"
        //                    ),
        //                    array(
        //                        "maxSellPrice" => "99999999",
        //                        "buyPriceUpRate" => "0.1",
        //                        "sellPriceDownRate" => "0.1",
        //                        "filterType" => "LIMIT_TRADING"
        //                    ),
        //                    array(
        //                        "buyPriceUpRate" => "0.1",
        //                        "sellPriceDownRate" => "0.1",
        //                        "filterType" => "MARKET_TRADING"
        //                    ),
        //                    {
        //                        "noAllowMarketStartTime" => "0",
        //                        "noAllowMarketEndTime" => "0",
        //                        "limitOrderStartTime" => "0",
        //                        "limitOrderEndTime" => "0",
        //                        "limitMinPrice" => "0",
        //                        "limitMaxPrice" => "0",
        //                        "filterType" => "OPEN_QUOTE"
        //                    }
        //                ),
        //                "exchangeId" => "301",
        //                "symbol" => "ETHUSDT",
        //                "symbolName" => "ETHUSDT",
        //                "status" => "TRADING",
        //                "baseAsset" => "ETH",
        //                "baseAssetName" => "ETH",
        //                "baseAssetPrecision" => "0.0001",
        //                "quoteAsset" => "USDT",
        //                "quoteAssetName" => "USDT",
        //                "quotePrecision" => "0.01",
        //                "icebergAllowed" => false,
        //                "isAggregate" => false,
        //                "allowMargin" => true,
        //             }
        //        ),
        //        "options" => array(),
        //        "contracts" => array(
        //            array(
        //                 "filters" => array( ... ),
        //                 "exchangeId" => "301",
        //                 "symbol" => "BTC-SWAP-USDT",
        //                 "symbolName" => "BTC-SWAP-USDTUSDT",
        //                 "status" => "TRADING",
        //                 "baseAsset" => "BTC-SWAP-USDT",
        //                 "baseAssetPrecision" => "0.001",
        //                 "quoteAsset" => "USDT",
        //                 "quoteAssetPrecision" => "0.1",
        //                 "icebergAllowed" => false,
        //                 "inverse" => false,
        //                 "index" => "BTC",
        //                 "indexToken" => "BTCUSDT",
        //                 "marginToken" => "USDT",
        //                 "marginPrecision" => "0.0001",
        //                 "contractMultiplier" => "0.001",
        //                 "underlying" => "BTC",
        //                 "riskLimits" => array(
        //                     array(
        //                         "riskLimitId" => "200020911",
        //                         "quantity" => "42000.0",
        //                         "initialMargin" => "0.02",
        //                         "maintMargin" => "0.01",
        //                         "isWhite" => false
        //                     ),
        //                     array(
        //                         "riskLimitId" => "200020912",
        //                         "quantity" => "84000.0",
        //                         "initialMargin" => "0.04",
        //                         "maintMargin" => "0.02",
        //                         "isWhite" => false
        //                     ),
        //                     ...
        //                 )
        //            ),
        //        ),
        //        "coins" => array(
        //            {
        //                "orgId" => "9001",
        //                "coinId" => "TCOM",
        //                "coinName" => "TCOM",
        //                "coinFullName" => "TCOM",
        //                "allowWithdraw" => true,
        //                "allowDeposit" => true,
        //                "chainTypes" => [
        //                    array(
        //                        "chainType" => "BSC",
        //                        "withdrawFee" => "49.55478",
        //                        "minWithdrawQuantity" => "77",
        //                        "maxWithdrawQuantity" => "0",
        //                        "minDepositQuantity" => "48",
        //                        "allowDeposit" => true,
        //                        "allowWithdraw" => false
        //                    }
        //                ),
        //                "isVirtual" => false
        //            ),
        //          ...
        //
        $symbols = $this->safe_list($response, 'symbols', array());
        $contracts = $this->safe_list($response, 'contracts', array());
        $all = $this->array_concat($symbols, $contracts);
        $result = array();
        for ($i = 0; $i < count($all); $i++) {
            $market = $all[$i];
            $parsed = $this->parse_market($market);
            $result[] = $parsed;
        }
        return $result;
    }

    public function parse_market(array $market): array {
        $id = $this->safe_string($market, 'symbol');
        $baseId = $this->safe_string($market, 'baseAsset');
        $quoteId = $this->safe_string($market, 'quoteAsset');
        $baseParts = explode('-', $baseId);
        $baseIdClean = $baseParts[0];
        $base = $this->safe_currency_code($baseIdClean);
        $quote = $this->safe_currency_code($quoteId);
        $settleId = $this->safe_string($market, 'marginToken');
        $settle = $this->safe_currency_code($settleId);
        $status = $this->safe_string($market, 'status');
        $active = ($status === 'TRADING');
        $filters = $this->safe_list($market, 'filters', array());
        $filtersByType = $this->index_by($filters, 'filterType');
        $priceFilter = $this->safe_dict($filtersByType, 'PRICE_FILTER', array());
        $lotSizeFilter = $this->safe_dict($filtersByType, 'LOT_SIZE', array());
        $minNotionalFilter = $this->safe_dict($filtersByType, 'MIN_NOTIONAL', array());
        $symbol = $base . '/' . $quote;
        $isContract = (is_array($market) && array_key_exists('contractMultiplier', $market));
        $inverse = $this->safe_bool_2($market, 'isInverse', 'inverse');
        if ($isContract) {
            $symbol .= ':' . $settle;
        }
        return $this->safe_market_structure(array(
            'id' => $id,
            'symbol' => $symbol,
            'base' => $base,
            'quote' => $quote,
            'settle' => $settle,
            'baseId' => $baseId,
            'quoteId' => $quoteId,
            'settleId' => $settleId,
            'type' => $isContract ? 'swap' : 'spot',
            'spot' => !$isContract,
            'margin' => false,
            'swap' => $isContract,
            'future' => false,
            'option' => false,
            'active' => $active,
            'contract' => $isContract,
            'linear' => $isContract ? !$inverse : null,
            'inverse' => $isContract ? $inverse : null,
            'contractSize' => $this->safe_number($market, 'contractMultiplier'),
            'expiry' => null,
            'expiryDatetime' => null,
            'strike' => null,
            'optionType' => null,
            'precision' => array(
                'amount' => $this->safe_number($lotSizeFilter, 'stepSize'),
                'price' => $this->safe_number($priceFilter, 'tickSize'),
            ),
            'limits' => array(
                'leverage' => array(
                    'min' => null,
                    'max' => null,
                ),
                'amount' => array(
                    'min' => $this->safe_number($lotSizeFilter, 'minQty'),
                    'max' => $this->safe_number($lotSizeFilter, 'maxQty'),
                ),
                'price' => array(
                    'min' => $this->safe_number($priceFilter, 'minPrice'),
                    'max' => $this->safe_number($priceFilter, 'maxPrice'),
                ),
                'cost' => array(
                    'min' => $this->safe_number($minNotionalFilter, 'minNotional'),
                    'max' => null,
                ),
            ),
            'created' => null,
            'info' => $market,
        ));
    }

    public function fetch_order_book(string $symbol, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other data
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#order-book
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#order-book
         *
         * @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(
            'symbol' => $market['id'],
        );
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->commonGetQuoteV1Depth ($this->extend($request, $params));
        //
        //    {
        //        "t" => "1755593995237",
        //        "b" => array(
        //            array(
        //                "115186.47",
        //                "4.184864"
        //            ),
        //            array(
        //                "115186.46",
        //                "0.002756"
        //            ),
        //            ...
        //        ),
        //        "a" => array(
        //            array(
        //                "115186.48",
        //                "6.137369"
        //            ),
        //            array(
        //                "115186.49",
        //                "0.002914"
        //            ),
        //            ...
        //        )
        //    }
        //
        $timestamp = $this->safe_integer($response, 't');
        return $this->parse_order_book($response, $market['symbol'], $timestamp, 'b', 'a');
    }

    public function fetch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * get a list of the most recent trades for a particular $symbol
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#recent-trades-list
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#recent-trades-list
         *
         * @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 number 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' => $market['id'],
        );
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->commonGetQuoteV1Trades ($this->extend($request, $params));
        //
        //    array(
        //        array(
        //            "t" => "1755594277287",
        //            "p" => "115276.99",
        //            "q" => "0.001508",
        //            "ibm" => true
        //        ),
        //    )
        //
        return $this->parse_trades($response, $market, $since, $limit);
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // fetchTrades
        //
        //        array(
        //            "t" => "1755594277287",
        //            "p" => "115276.99",
        //            "q" => "0.001508",
        //            "ibm" => true
        //        ),
        //        // watchTrades have also an additional fields:
        //             "v" => "4864732022868004630",   // $trade id
        //             "m" => true,                    // is the buyer taker
        //
        // fetchMyTrades
        //
        //        array(
        //            "id" => "2024934575206059008",
        //            "symbol" => "ETHUSDT",
        //            "orderId" => "2024934575097029888",
        //            "ticketId" => "4864450547563401875",
        //            "price" => "4641.21",
        //            "qty" => "0.001",
        //            "time" => "1756127012094",
        //            "isMaker" => false,
        //            "commission" => "0.00464121",
        //            "commissionAsset" => "USDT",
        //            "makerRebate" => "0",
        //            "symbolName" => "ETHUSDT",                 // only in SPOT
        //            "isBuyer" => false,                        // only in SPOT
        //            "feeAmount" => "0.00464121",               // only in SPOT
        //            "feeCoinId" => "USDT",                     // only in SPOT
        //            "fee" => array(                                 // only in SPOT
        //                "feeCoinId" => "USDT",
        //                "feeCoinName" => "USDT",
        //                "fee" => "0.00464121"
        //            ),
        //            "type" => "LIMIT",                         // only in CONTRACT
        //            "side" => "BUY_OPEN",                      // only in CONTRACT
        //            "realizedPnl" => "0",                      // only in CONTRACT
        //        ),
        //
        $timestamp = $this->safe_integer_2($trade, 't', 'time');
        $priceString = $this->safe_string_2($trade, 'p', 'price');
        $amountString = $this->safe_string_2($trade, 'q', 'qty');
        $isBuyer = $this->safe_bool($trade, 'isBuyer');
        $side = null;
        $isBuyerMaker = $this->safe_bool($trade, 'ibm');
        if ($isBuyerMaker === null) {
            $isBuyerTaker = $this->safe_bool($trade, 'm');
            if ($isBuyerTaker !== null) {
                $isBuyerMaker = !$isBuyerTaker;
            }
        }
        if ($isBuyerMaker !== null) {
            if ($isBuyerMaker) {
                $side = 'sell';
            } else {
                $side = 'buy';
            }
        } else {
            if ($isBuyer) {
                $side = 'buy';
            } else {
                $side = 'sell';
            }
        }
        $feeCurrencyId = $this->safe_string($trade, 'feeCoinId');
        $feeAmount = $this->safe_string($trade, 'feeAmount');
        $fee = null;
        if ($feeAmount !== null) {
            $fee = array(
                'currency' => $this->safe_currency_code($feeCurrencyId),
                'cost' => $feeAmount,
            );
        }
        $isMaker = $this->safe_bool($trade, 'isMaker');
        $takerOrMaker = null;
        if ($isMaker !== null) {
            $takerOrMaker = $isMaker ? 'maker' : 'taker';
        }
        $market = $this->safe_market(null, $market);
        $symbol = $market['symbol'];
        return $this->safe_trade(array(
            'info' => $trade,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $symbol,
            'id' => $this->safe_string_2($trade, 'id', 'v'),
            'order' => $this->safe_string($trade, 'orderId'),
            'type' => null,
            'side' => $side,
            'amount' => $amountString,
            'price' => $priceString,
            'cost' => null,
            'takerOrMaker' => $takerOrMaker,
            'fee' => $fee,
        ), $market);
    }

    public function fetch_ohlcv(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches historical candlestick data containing the open, high, low, and close price, and the volume of a $market
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#kline-candlestick-data
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#kline-candlestick-data
         *
         * @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
         * @return {int[][]} A list of candles ordered, open, high, low, close, volume
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            'interval' => $this->safe_string($this->timeframes, $timeframe, $timeframe),
        );
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        $until = $this->safe_integer($params, 'until');
        if ($until !== null) {
            $params = $this->omit($params, 'until');
            $request['endTime'] = $until;
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = null;
        $endpoint = null;
        list($endpoint, $params) = $this->handle_option_and_params($params, 'fetchOHLCV', 'price');
        if ($endpoint === 'index') {
            $response = $this->commonGetQuoteV1IndexKlines ($this->extend($request, $params));
            //
            //     {
            //         "code" => 200,
            //         "data" => array(
            //             array(
            //                 "t" => 1669155300000,//time
            //                 "s" => "ETHUSDT",// $symbol
            //                 "sn" => "ETHUSDT",//symbol name
            //                 "c" => "1127.1",//Close price
            //                 "h" => "1130.81",//High price
            //                 "l" => "1126.17",//Low price
            //                 "o" => "1130.8",//Open price
            //                 "v" => "0"//Volume
            //             ),
            //             {
            //                 "t" => 1669156200000,
            //                 "s" => "ETHUSDT",
            //                 "sn" => "ETHUSDT",
            //                 "c" => "1129.44",
            //                 "h" => "1129.54",
            //                 "l" => "1127.1",
            //                 "o" => "1127.1",
            //                 "v" => "0"
            //             }
            //         )
            //     }
            //
        } elseif ($endpoint === 'mark') {
            $response = $this->commonGetQuoteV1MarkPriceKlines ($this->extend($request, $params));
            //
            //     {
            //         "code" => 200,
            //         "data" => array(
            //             {
            //                 "symbol" => "BTCUSDT",// Symbol
            //                 "time" => 1670157900000,// time
            //                 "low" => "16991.14096",//Low price
            //                 "open" => "16991.78288",//Open price
            //                 "high" => "16996.30641",// High prce
            //                 "close" => "16996.30641",// Close price
            //                 "volume" => "0",// Volume
            //                 "curId" => 1670157900000
            //             }
            //         )
            //     }
            //
        } else {
            $response = $this->commonGetQuoteV1Klines ($this->extend($request, $params));
            //
            //    array(
            //        [
            //            1755540660000,
            //            "116399.99",
            //            "116399.99",
            //            "116360.09",
            //            "116360.1",
            //            "2.236869",
            //            0,
            //            "260303.79722607",
            //            22,
            //            "2.221061",
            //            "258464.10338267"
            //        ),
            //        ...
            //
        }
        return $this->parse_ohlcvs($response, $market, $timeframe, $since, $limit);
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        return array(
            $this->safe_integer_n($ohlcv, array( 0, 'time', 't' )),
            $this->safe_number_n($ohlcv, array( 1, 'open', 'o' )),
            $this->safe_number_n($ohlcv, array( 2, 'high', 'h' )),
            $this->safe_number_n($ohlcv, array( 3, 'low', 'l' )),
            $this->safe_number_n($ohlcv, array( 4, 'close', 'c' )),
            $this->safe_number_n($ohlcv, array( 5, 'volume', 'v' )),
        );
    }

    public function fetch_tickers(?array $symbols = null, $params = array ()): array {
        /**
         * fetches price tickers for multiple markets, statistical information calculated over the past 24 hours for each $market
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#24hr-ticker-price-change-statistics
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#24hr-ticker-price-change-statistics
         *
         * @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();
        $symbols = $this->market_symbols($symbols);
        $type = null;
        $market = null;
        $request = array();
        if ($symbols !== null) {
            $symbol = $this->safe_string($symbols, 0);
            $market = $this->market($symbol);
            $length = count($symbols);
            if ($length === 1) {
                $request['symbol'] = $market['id'];
            }
        }
        list($type, $params) = $this->handle_market_type_and_params('fetchTickers', $market, $params);
        $response = null;
        if ($type === 'spot') {
            $response = $this->commonGetQuoteV1Ticker24hr ($this->extend($request, $params));
        } else {
            $response = $this->commonGetQuoteV1ContractTicker24hr ($this->extend($request, $params));
        }
        //
        //    [
        //        array(
        //            "t" => "1755601440162",
        //            "s" => "GRDRUSDT",
        //            "o" => "0.38",
        //            "h" => "0.38",
        //            "l" => "0.38",
        //            "c" => "0.38",
        //            "v" => "0",
        //            "qv" => "0",
        //            "pc" => "0",
        //            "pcp" => "0"
        //        ),
        //        ...
        //
        return $this->parse_tickers($response, $symbols, $params);
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        $marketId = $this->safe_string($ticker, 's');
        $market = $this->safe_market($marketId, $market);
        $timestamp = $this->safe_integer($ticker, 't');
        $last = $this->safe_string($ticker, 'c');
        return $this->safe_ticker(array(
            'symbol' => $market['symbol'],
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $this->safe_string($ticker, 'h'),
            'low' => $this->safe_string($ticker, 'l'),
            'bid' => null,
            'bidVolume' => null,
            'ask' => null,
            'askVolume' => null,
            'vwap' => null,
            'open' => $this->safe_string($ticker, 'o'),
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => $this->safe_string($ticker, 'pc'),
            'percentage' => $this->safe_string($ticker, 'pcp'),
            'average' => null,
            'baseVolume' => $this->safe_string($ticker, 'v'),
            'quoteVolume' => $this->safe_string($ticker, 'qv'),
            'info' => $ticker,
        ), $market);
    }

    public function fetch_last_prices(?array $symbols = null, $params = array ()) {
        /**
         * fetches the last price for multiple markets
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#symbol-price-ticker
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#symbol-price-ticker
         *
         * @param {string[]|null} $symbols unified $symbols of the markets to fetch the last prices
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a dictionary of lastprices structures
         */
        $this->load_markets();
        $symbols = $this->market_symbols($symbols);
        $request = array();
        if ($symbols !== null) {
            $length = count($symbols);
            if ($length === 1) {
                $market = $this->market($symbols[0]);
                $request['symbol'] = $market['id'];
            }
        }
        $response = $this->commonGetQuoteV1TickerPrice ($this->extend($request, $params));
        //
        //    [
        //        array(
        //            "s" => "BNTUSDT",
        //            "si" => "BNTUSDT",
        //            "p" => "0.823"
        //        ),
        //
        return $this->parse_last_prices($response, $symbols);
    }

    public function parse_last_price($entry, ?array $market = null) {
        $marketId = $this->safe_string($entry, 's');
        $market = $this->safe_market($marketId, $market);
        return array(
            'symbol' => $market['symbol'],
            'timestamp' => null,
            'datetime' => null,
            'price' => $this->safe_number_omit_zero($entry, 'price'),
            'side' => null,
            'info' => $entry,
        );
    }

    public function fetch_bids_asks(?array $symbols = null, $params = array ()) {
        /**
         * fetches the bid and ask price and volume for multiple markets
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#symbol-order-book-ticker
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#symbol-order-book-ticker
         *
         * @param {string[]} [$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();
        $symbols = $this->market_symbols($symbols);
        $request = array();
        if ($symbols !== null) {
            $length = count($symbols);
            if ($length === 1) {
                $market = $this->market($symbols[0]);
                $request['symbol'] = $market['id'];
            }
        }
        $response = $this->commonGetQuoteV1TickerBookTicker ($this->extend($request, $params));
        //
        //    [
        //        array(
        //            "s" => "GRDRUSDT",
        //            "b" => "0",
        //            "bq" => "0",
        //            "a" => "0",
        //            "aq" => "0",
        //            "t" => "1755936610506"
        //        ), ...
        //
        return $this->parse_bids_asks_custom($response, $symbols);
    }

    public function parse_bids_asks_custom($tickers, ?array $symbols = null, $params = array ()): array {
        $results = array();
        for ($i = 0; $i < count($tickers); $i++) {
            $parsedTicker = $this->parse_bid_ask_custom($tickers[$i]);
            $ticker = $this->extend($parsedTicker, $params);
            $results[] = $ticker;
        }
        $symbols = $this->market_symbols($symbols);
        return $this->filter_by_array($results, 'symbol', $symbols);
    }

    public function parse_bid_ask_custom($ticker) {
        return array(
            'timestamp' => $this->safe_string($ticker, 't'),
            'symbol' => $this->safe_string($ticker, 's'),
            'bid' => $this->safe_number($ticker, 'b'),
            'bidVolume' => $this->safe_number($ticker, 'bq'),
            'ask' => $this->safe_number($ticker, 'a'),
            'askVolume' => $this->safe_number($ticker, 'aq'),
            'info' => $ticker,
        );
    }

    public function fetch_funding_rates(?array $symbols = null, $params = array ()): array {
        /**
         * fetch the funding rate for multiple markets
         *
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#funding-rate
         *
         * @param {string[]|null} $symbols list of unified $market $symbols
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=funding-rates-structure funding rates structures~, indexe by $market $symbols
         */
        $this->load_markets();
        $symbols = $this->market_symbols($symbols);
        $request = array();
        if ($symbols !== null) {
            $length = count($symbols);
            if ($length === 1) {
                $market = $this->market($symbols[0]);
                $request['symbol'] = $market['id'];
            }
        }
        $response = $this->commonGetApiV1FuturesFundingRate ($this->extend($request, $params));
        //
        //    [
        //        array(
        //            "symbol" => "BTC-SWAP-USDT",
        //            "rate" => "0.0001071148112848",
        //            "nextFundingTime" => "1755964800000"
        //        ),...
        //
        return $this->parse_funding_rates($response, $symbols);
    }

    public function parse_funding_rate($contract, ?array $market = null): array {
        $marketId = $this->safe_string($contract, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $nextFundingRate = $this->safe_number($contract, 'rate');
        $nextFundingRateTimestamp = $this->safe_integer($contract, 'nextFundingTime');
        return array(
            'info' => $contract,
            'symbol' => $symbol,
            'markPrice' => null,
            'indexPrice' => null,
            'interestRate' => null,
            'estimatedSettlePrice' => null,
            'timestamp' => null,
            'datetime' => null,
            'previousFundingRate' => null,
            'nextFundingRate' => null,
            'previousFundingTimestamp' => null,
            'nextFundingTimestamp' => null,
            'previousFundingDatetime' => null,
            'nextFundingDatetime' => null,
            'fundingRate' => $nextFundingRate,
            'fundingTimestamp' => $nextFundingRateTimestamp,
            'fundingDatetime' => $this->iso8601($nextFundingRateTimestamp),
            'interval' => null,
        );
    }

    public function fetch_funding_rate_history(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetches historical funding rate prices
         *
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#get-funding-rate-history
         *
         * @param {string} $symbol unified $symbol of the $market to fetch the funding rate history for
         * @param {int} [$since] timestamp in ms of the earliest funding rate to fetch
         * @param {int} [$limit] the maximum amount of ~@link https://docs.ccxt.com/#/?id=funding-rate-history-structure funding rate structures~ to fetch
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] timestamp in ms of the latest funding rate to fetch
         * @param {boolean} [$params->paginate] default false, when true will automatically $paginate by calling this endpoint multiple times. See in the docs all the [availble parameters](https://github.com/ccxt/ccxt/wiki/Manual#pagination-$params)
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=funding-rate-history-structure funding rate structures~
         */
        $this->load_markets();
        $paginate = false;
        list($paginate, $params) = $this->handle_option_and_params($params, 'fetchFundingRateHistory', 'paginate');
        if ($paginate) {
            return $this->fetch_paginated_call_deterministic('fetchFundingRateHistory', $symbol, $since, $limit, '8h', $params);
        }
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = $this->commonGetApiV1FuturesHistoryFundingRate ($this->extend($request, $params));
        //
        //    [
        //        array(
        //            "id" => "869931",
        //            "symbol" => "BTC-SWAP-USDT",
        //            "settleTime" => "1755936000000",
        //            "settleRate" => "0.0001"
        //        ), ...
        //
        return $this->parse_funding_rate_histories($response, $market, $since, $limit);
    }

    public function parse_funding_rate_history($contract, ?array $market = null) {
        $timestamp = $this->safe_integer($contract, 'settleTime');
        $marketId = $this->safe_string($contract, 'symbol');
        return array(
            'info' => $contract,
            'symbol' => $this->safe_symbol($marketId, $market),
            'fundingRate' => $this->safe_number($contract, 'settleRate'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
        );
    }

    public function fetch_balance($params = array ()): array {
        /**
         * query for balance and get the amount of funds available for trading or funds locked in orders
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#account-information-user_data
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#futures-account-balance-user_data
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpointinvalid
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=balance-structure balance structure~
         */
        $this->load_markets();
        $response = null;
        $marketType = null;
        list($marketType, $params) = $this->handle_market_type_and_params('fetchBalance', null, $params);
        if ($this->in_array($marketType, array( 'swap', 'future' ))) {
            $response = $this->privateGetApiV1FuturesBalance ();
            //
            //     array(
            //         {
            //             "asset" => "USDT", // asset
            //             "balance" => "999999999999.982", // total
            //             "availableBalance" => "1899999999978.4995", // available balance Include unrealized pnl
            //             "positionMargin" => "11.9825", //position Margin
            //             "orderMargin" => "9.5", //order Margin
            //             "crossUnRealizedPnl" => "10.01" //The unrealized profit and loss of cross position
            //         }
            //     )
            //
        } else {
            $response = $this->privateGetApiV1Account ();
            //
            //    {
            //        "userId" => "912902020",
            //        "balances" => array(
            //            {
            //                "asset" => "ETH",
            //                "assetId" => "ETH",
            //                "assetName" => "ETH",
            //                "total" => "0.025",
            //                "free" => "0.025",
            //                "locked" => "0"
            //            }
            //        )
            //    }
            //
        }
        return $this->parse_balance($response);
    }

    public function parse_balance($response): array {
        $result = array(
            'info' => $response,
            'timestamp' => null,
            'datetime' => null,
        );
        $balances = $this->safe_list($response, 'balances', $response);
        for ($i = 0; $i < count($balances); $i++) {
            $balance = $balances[$i];
            $code = $this->safe_currency_code($this->safe_string($balance, 'asset'));
            $account = $this->account();
            $account['free'] = $this->safe_string_2($balance, 'free', 'availableBalance');
            $account['total'] = $this->safe_string_2($balance, 'total', 'balance');
            $account['used'] = $this->safe_string($balance, 'locked');
            $result[$code] = $account;
        }
        return $this->safe_balance($result);
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        /**
         * create a trade order
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#new-order-trade
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#new-order-trade
         *
         * @param {string} $symbol unified $symbol of the $market to create an order in
         * @param {string} $type 'market', '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
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array();
        $response = null;
        if ($market['spot']) {
            list($request, $params) = $this->create_order_request($symbol, $type, $side, $amount, $price, $params);
            $response = $this->privatePostApiV1SpotOrder ($this->extend($request, $params));
        } else {
            list($request, $params) = $this->create_contract_order_request($symbol, $type, $side, $amount, $price, $params);
            $response = $this->privatePostApiV1FuturesOrder ($this->extend($request, $params));
        }
        //
        //     {
        //         "symbol" => "ETHUSDT",
        //         "price" => "0",
        //         "origQty" => "0.001",
        //         "orderId" => "2024837825254460160",
        //         "clientOrderId" => "1756115478113679",
        //         "executedQty" => "0",
        //         "status" => "PENDING_NEW",
        //         "timeInForce" => "GTC",
        //         "type" => "MARKET",
        //         "side" => "SELL"
        //         "accountId" => "1783404067076253952",    // only in spot
        //         "symbolName" => "ETHUSDT",               // only in spot
        //         "transactTime" => "1756115478604",       // only in spot
        //         "time" => "1668418485058",               // only in contract
        //         "updateTime" => "1668418485058",         // only in contract
        //         "leverage" => "2",                       // only in contract
        //         "avgPrice" => "0",                       // only in contract
        //         "marginLocked" => "9.5",                 // only in contract
        //         "priceType" => "INPUT"                   // only in contract
        //     }
        //
        return $this->parse_order($response, $market);
    }

    public function create_order_request(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        $market = $this->market($symbol);
        $id = $market['id'];
        $request = array(
            'symbol' => $id,
            'side' => strtoupper($side),
        );
        if ($price !== null) {
            $request['price'] = $this->price_to_precision($symbol, $price);
        }
        $cost = null;
        list($cost, $params) = $this->handle_param_string($params, 'cost');
        if ($type === 'market') {
            if ($cost === null && $side === 'buy') {
                throw new ArgumentsRequired($this->id . ' createOrder() requires $params["cost"] for $market buy order');
            } else {
                $request['quantity'] = $this->cost_to_precision($symbol, $cost);
            }
        } else {
            $request['quantity'] = $this->amount_to_precision($symbol, $amount);
        }
        $isPostOnly = null;
        list($isPostOnly, $params) = $this->handle_post_only($type === 'market', false, $params);
        if ($isPostOnly) {
            $request['type'] = 'LIMIT_MAKER';
        } else {
            $request['type'] = strtoupper($type);
        }
        return array( $request, $params );
    }

    public function create_contract_order_request(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            'quantity' => $this->amount_to_precision($symbol, $amount),
        );
        $reduceOnly = null;
        list($reduceOnly, $params) = $this->handle_param_bool($params, 'reduceOnly');
        if ($side === 'buy') {
            $side = $reduceOnly ? 'SELL_CLOSE' : 'BUY_OPEN';
        } elseif ($side === 'sell') {
            $side = $reduceOnly ? 'BUY_CLOSE' : 'SELL_OPEN';
        }
        $request['side'] = $side;
        if ($price !== null) {
            $request['price'] = $this->price_to_precision($symbol, $price);
        }
        if ($this->in_array($type, array( 'limit', 'LIMIT' ))) {
            $request['type'] = strtoupper($type);
            $request['price'] = $this->price_to_precision($symbol, $price);
        } elseif ($type === 'market') {
            $request['type'] = 'LIMIT'; // weird, but exchange works this way
            $request['priceType'] = 'MARKET';
        }
        $isPostOnly = null;
        list($isPostOnly, $params) = $this->handle_post_only($type === 'market', false, $params);
        if ($isPostOnly) {
            $request['timeInForce'] = 'LIMIT_MAKER';
        }
        $values = $this->handle_trigger_prices_and_params($symbol, $params);
        $triggerPrice = $values[0];
        $params = $values[3];
        if ($triggerPrice !== null) {
            $request['stopPrice'] = $triggerPrice;
        }
        $stopLoss = $this->safe_dict($params, 'stopLoss');
        $takeProfit = $this->safe_dict($params, 'takeProfit');
        $triggerPriceTypes = array(
            'mark' => 'MARK_PRICE',
            'last' => 'CONTRACT_PRICE',
        );
        if ($stopLoss !== null) {
            $request['stopLoss'] = $this->safe_value($stopLoss, 'triggerPrice');
            $limitPrice = $this->safe_value($stopLoss, 'price');
            if ($limitPrice !== null) {
                $request['slOrderType'] = 'LIMIT';
                $request['slLimitPrice'] = $this->price_to_precision($symbol, $limitPrice);
            }
            $triggerPriceType = $this->safe_string($stopLoss, 'triggerPriceType');
            if ($triggerPriceType !== null) {
                $request['slTriggerBy'] = $this->safe_string($triggerPriceTypes, $triggerPriceType, $triggerPriceType);
            }
            $params = $this->omit($params, 'stopLoss');
        }
        if ($takeProfit !== null) {
            $request['takeProfit'] = $this->safe_value($takeProfit, 'triggerPrice');
            $limitPrice = $this->safe_value($takeProfit, 'price');
            if ($limitPrice !== null) {
                $request['tpOrderType'] = 'LIMIT';
                $request['tpLimitPrice'] = $this->price_to_precision($symbol, $limitPrice);
            }
            $triggerPriceType = $this->safe_string($takeProfit, 'triggerPriceType');
            if ($triggerPriceType !== null) {
                $request['tpTriggerBy'] = $this->safe_string($triggerPriceTypes, $triggerPriceType, $triggerPriceType);
            }
            $params = $this->omit($params, 'takeProfit');
        }
        if (!(is_array($params) && array_key_exists('newClientOrderId', $params))) {
            $request['newClientOrderId'] = $this->uuid();
        }
        return array( $request, $params );
    }

    public function parse_order(array $order, ?array $market = null): array {
        //
        // createOrder, cancelOrder
        //
        //     {
        //         "symbol" => "ETHUSDT",
        //         "price" => "0",
        //         "origQty" => "0.001",
        //         "orderId" => "2024837825254460160",
        //         "clientOrderId" => "1756115478113679",
        //         "executedQty" => "0",
        //         "status" => "PENDING_NEW",
        //         "timeInForce" => "GTC",
        //         "type" => "MARKET",
        //         "side" => "SELL"
        //         "accountId" => "1783404067076253952",    // only in spot
        //         "symbolName" => "ETHUSDT",               // only in spot
        //         "transactTime" => "1756115478604",       // only in spot
        //         "time" => "1668418485058",               // only in contract
        //         "updateTime" => "1668418485058",         // only in contract
        //         "leverage" => "2",                       // only in contract
        //         "avgPrice" => "0",                       // only in contract
        //         "marginLocked" => "9.5",                 // only in contract
        //         "priceType" => "INPUT"                   // only in contract
        //     }
        //
        //
        // fetchOrder, fetchOrders, fetchOpenOrders
        //
        //    {
        //        "time" => "1756140208069",
        //        "updateTime" => "1756140208078",
        //        "orderId" => "2025045271033977089",
        //        "clientOrderId" => "17561402075722006",
        //        "symbol" => "ETHUSDT",
        //        "price" => "3000",
        //        "origQty" => "0.002",
        //        "executedQty" => "0",
        //        "avgPrice" => "0",
        //        "type" => "LIMIT",
        //        "side" => "BUY",
        //        "timeInForce" => "GTC",
        //        "status" => "NEW",
        //        "accountId" => "1783404067076253952",  // only in SPOT
        //        "exchangeId" => "301",                 // only in SPOT
        //        "symbolName" => "ETHUSDT",             // only in SPOT
        //        "cummulativeQuoteQty" => "0",          // only in SPOT
        //        "cumulativeQuoteQty" => "0",           // only in SPOT
        //        "stopPrice" => "0.0",                  // only in SPOT
        //        "icebergQty" => "0.0",                 // only in SPOT
        //        "isWorking" => true                    // only in SPOT
        //        "leverage" => "2",                     // only in CONTRACT
        //        "marginLocked" => "9.5",               // only in CONTRACT
        //        "priceType" => "INPUT"                 // only in CONTRACT
        //        "triggerType" => "0",                  // only in CONTRACT fetchClosedOrders
        //        "fallType" => "0",                     // only in CONTRACT fetchClosedOrders
        //        "activeStatus" => "0"                  // only in CONTRACT fetchClosedOrders
        //    }
        //
        $timestamp = $this->safe_integer_2($order, 'transactTime', 'time');
        $marketId = $this->safe_string($order, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $rawType = $this->safe_string($order, 'type');
        $rawSideLower = $this->safe_string_lower($order, 'side');
        $triggerPrice = $this->omit_zero($this->safe_string($order, 'stopPrice'));
        if ($triggerPrice === '0.0') {
            $triggerPrice = null;
        }
        return $this->safe_order(array(
            'info' => $order,
            'id' => $this->safe_string($order, 'orderId'),
            'clientOrderId' => $this->safe_string($order, 'clientOrderId'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => null,
            'lastUpdateTimestamp' => $this->safe_integer($order, 'updateTime'),
            'status' => $this->parse_order_status($this->safe_string($order, 'status')),
            'symbol' => $market['symbol'],
            'type' => $this->parse_order_type($rawType),
            'timeInForce' => $this->safe_string($order, 'timeInForce'),
            'postOnly' => ($rawType === 'LIMIT_MAKER'),
            'side' => $rawSideLower,
            'price' => $this->omit_zero($this->safe_string($order, 'price')),
            'triggerPrice' => $triggerPrice,
            'cost' => $this->omit_zero($this->safe_string($order, 'cumulativeQuoteQty')),
            'average' => $this->safe_string($order, 'avgPrice'),
            'amount' => $this->safe_string($order, 'origQty'),
            'filled' => $this->safe_string($order, 'executedQty'),
            'remaining' => null,
            'trades' => null,
            'fee' => null,
            'marginMode' => null,
            'reduceOnly' => null,
            'leverage' => null,
            'hedged' => null,
        ), $market);
    }

    public function parse_order_status(?string $status) {
        $statuses = array(
            'PENDING_NEW' => 'open',
            'NEW' => 'open',
            'PARTIALLY_FILLED' => 'open',
            'FILLED' => 'closed',
            'PENDING_CANCEL' => 'canceled',
            'CANCELED' => 'canceled',
            'REJECTED' => 'canceled',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_order_type($status) {
        $statuses = array(
            'MARKET' => 'market',
            'LIMIT' => 'limit',
            'LIMIT_MAKER' => 'limit',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * cancels an open order
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#cancel-order-trade
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#cancel-order-trade
         *
         * @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();
        if ($this->safe_string($params, 'clientOrderId') === null) {
            $request['orderId'] = $id;
        }
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        $marketType = null;
        list($marketType, $params) = $this->handle_market_type_and_params('cancelOrder', $market, $params, 'none');
        if ($marketType === 'none') {
            throw new ArgumentsRequired($this->id . ' cancelOrder() requires a $symbol argument or the "defaultType" parameter to be set to "spot" or "swap"');
        }
        $response = null;
        if ($marketType === 'spot') {
            $response = $this->privateDeleteApiV1SpotOrder ($this->extend($request, $params));
        } else {
            $response = $this->privateDeleteApiV1FuturesOrder ($this->extend($request, $params));
        }
        // $response same `createOrder`
        $status = $this->parse_order_status($this->safe_string($response, 'status'));
        if ($status !== 'open') {
            throw new OrderNotFound($this->id . ' order ' . $id . ' can not be canceled, ' . $this->json($response));
        }
        return $this->parse_order($response, $market);
    }

    public function cancel_all_orders(?string $symbol = null, $params = array ()) {
        /**
         * cancel all open orders in a $market
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#cancel-all-open-orders-trade
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#cancel-orders-trade
         *
         * @param {string} $symbol unified $symbol
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->load_markets();
        $request = array();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        $marketType = null;
        list($marketType, $params) = $this->handle_market_type_and_params('cancelAllOrders', $market, $params, 'none');
        if ($marketType === 'none') {
            throw new ArgumentsRequired($this->id . ' cancelAllOrders() requires a $symbol argument or the "defaultType" parameter to be set to "spot" or "swap"');
        }
        $response = null;
        if ($marketType === 'spot') {
            $response = $this->privateDeleteApiV1SpotOpenOrders ($this->extend($request, $params));
            //
            // array("success":true)  // always same $response
            //
        } else {
            $response = $this->privateDeleteApiV1FuturesBatchOrders ($this->extend($request, $params));
            //
            // array( "code" => 200, "message":"success", "timestamp":1541161088303 )
            //
        }
        return array(
            $this->safe_order(array(
                'info' => $response,
            )),
        );
    }

    public function cancel_orders(array $ids, ?string $symbol = null, $params = array ()) {
        /**
         * cancel multiple orders
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#cancel-multiple-orders-trade
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#cancel-multiple-orders-trade
         *
         * @param {string[]} $ids order $ids
         * @param {string} [$symbol] unified $market $symbol
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} an list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
         */
        $this->load_markets();
        $idsString = implode(',', $ids);
        $request = array(
            'ids' => $idsString,
        );
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
        }
        $marketType = null;
        list($marketType, $params) = $this->handle_market_type_and_params('cancelOrders', $market, $params, 'none');
        if ($marketType === 'none') {
            throw new ArgumentsRequired($this->id . ' cancelOrders() requires a $symbol argument or the "defaultType" parameter to be set to "spot" or "swap"');
        }
        $response = null;
        if ($marketType === 'spot') {
            $response = $this->privateDeleteApiV1SpotCancelOrderByIds ($this->extend($request, $params));
            //
            // array("success":true)  // always same $response
            //
        } else {
            $response = $this->privateDeleteApiV1FuturesCancelOrderByIds ($this->extend($request, $params));
            //
            // {
            //     "code":200,
            //     "result":array(
            //         array(
            //             "orderId":"1327047813809448704",
            //             "code":-2013
            //         ),
            //         {
            //             "orderId":"1327047814212101888",
            //             "code":-2013
            //         }
            //     )
            // }
            //
            // or empty array if no orders were canceled
        }
        $result = $this->safe_list($response, 'result', array());
        return $this->parse_orders($result, $market);
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()) {
        /**
         * fetches information on an order made by the user
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#query-order-user_data
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#query-order-user_data
         *
         * @param {string} $id the 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~
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' fetchOrder() requires a $symbol argument');
        }
        $this->load_markets();
        $request = array(
            'orderId' => $id,
        );
        $market = $this->market($symbol);
        $response = null;
        if ($market['spot']) {
            $response = $this->privateGetApiV1SpotOrder ($this->extend($request, $params));
        } else {
            $response = $this->privateGetApiV1FuturesOrder ($this->extend($request, $params));
        }
        //
        //    {
        //        "time" => "1756140208069",
        //        "updateTime" => "1756140208078",
        //        "orderId" => "2025045271033977089",
        //        "clientOrderId" => "17561402075722006",
        //        "symbol" => "ETHUSDT",
        //        "price" => "3000",
        //        "origQty" => "0.002",
        //        "executedQty" => "0",
        //        "avgPrice" => "0",
        //        "type" => "LIMIT",
        //        "side" => "BUY",
        //        "timeInForce" => "GTC",
        //        "status" => "NEW",
        //        "accountId" => "1783404067076253952",  // only in SPOT
        //        "exchangeId" => "301",                 // only in SPOT
        //        "symbolName" => "ETHUSDT",             // only in SPOT
        //        "cummulativeQuoteQty" => "0",          // only in SPOT
        //        "cumulativeQuoteQty" => "0",           // only in SPOT
        //        "stopPrice" => "0.0",                  // only in SPOT
        //        "icebergQty" => "0.0",                 // only in SPOT
        //        "isWorking" => true                    // only in SPOT
        //        "leverage" => "2",                     // only in CONTRACT
        //        "marginLocked" => "9.5",               // only in CONTRACT
        //        "priceType" => "INPUT"                 // only in CONTRACT
        //    }
        //
        return $this->parse_order($response, $market);
    }

    public function fetch_open_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on multiple orders made by the user
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#current-open-orders-user_data
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#query-current-open-order-user_data
         *
         * @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~
         */
        $this->load_markets();
        $request = array();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $marketType = null;
        list($marketType, $params) = $this->handle_market_type_and_params('fetchOrders', $market, $params);
        $response = null;
        if ($marketType === 'spot') {
            $response = $this->privateGetApiV1SpotOpenOrders ($this->extend($request, $params));
            //
            //    array(
            //        array(
            //            "accountId" => "1783404067076253952",
            //            "exchangeId" => "301",
            //            "symbol" => "ETHUSDT",
            //            "symbolName" => "ETHUSDT",
            //            "clientOrderId" => "17561415157172008",
            //            "orderId" => "2025056244339984384",
            //            "price" => "3000",
            //            "origQty" => "0.002",
            //            "executedQty" => "0",
            //            "cummulativeQuoteQty" => "0",
            //            "cumulativeQuoteQty" => "0",
            //            "avgPrice" => "0",
            //            "status" => "NEW",
            //            "timeInForce" => "GTC",
            //            "type" => "LIMIT",
            //            "side" => "BUY",
            //            "stopPrice" => "0.0",
            //            "icebergQty" => "0.0",
            //            "time" => "1756141516189",
            //            "updateTime" => "1756141516198",
            //            "isWorking" => true
            //        ), ...
            //    )
            //
        } else {
            $response = $this->privateGetApiV1FuturesOpenOrders ($this->extend($request, $params));
        }
        return $this->parse_orders($response, $market, $since, $limit);
    }

    public function fetch_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on multiple orders made by the user
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#all-orders-user_data
         *
         * @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~
         */
        $this->load_markets();
        $request = array();
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        list($request, $params) = $this->handle_until_option('endTime', $request, $params);
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        $marketType = null;
        list($marketType, $params) = $this->handle_market_type_and_params('fetchOrders', $market, $params);
        $response = null;
        if ($marketType === 'spot') {
            $response = $this->privateGetApiV1SpotTradeOrders ($request);
            //
            //    array(
            //        array(
            //            "accountId" => "1783404067076253952",
            //            "exchangeId" => "301",
            //            "symbol" => "ETHUSDT",
            //            "symbolName" => "ETHUSDT",
            //            "clientOrderId" => "17561415157172008",
            //            "orderId" => "2025056244339984384",
            //            "price" => "3000",
            //            "origQty" => "0.002",
            //            "executedQty" => "0",
            //            "cummulativeQuoteQty" => "0",
            //            "cumulativeQuoteQty" => "0",
            //            "avgPrice" => "0",
            //            "status" => "NEW",
            //            "timeInForce" => "GTC",
            //            "type" => "LIMIT",
            //            "side" => "BUY",
            //            "stopPrice" => "0.0",
            //            "icebergQty" => "0.0",
            //            "time" => "1756141516189",
            //            "updateTime" => "1756141516198",
            //            "isWorking" => true
            //        ), ...
            //    )
            //
        } else {
            throw new NotSupported($this->id . ' fetchOrders() is not supported for ' . $marketType . ' markets');
        }
        return $this->parse_orders($response, $market, $since, $limit);
    }

    public function fetch_closed_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetches information on multiple closed orders made by the user
         *
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#query-history-orders-user_data
         *
         * @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~
         */
        // returns the most recent closed or canceled orders up to circa two weeks ago
        $this->load_markets();
        $request = array();
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        list($request, $params) = $this->handle_until_option('endTime', $request, $params);
        $marketType = null;
        list($marketType, $params) = $this->handle_market_type_and_params('fetchClosedOrders', $market, $params);
        $response = null;
        if ($marketType === 'spot') {
            throw new NotSupported($this->id . ' fetchOrders() is not supported for ' . $marketType . ' markets');
        } else {
            $response = $this->privateGetApiV1FuturesHistoryOrders ($request);
            //
            //    array(
            //        {
            //            "time" => "1756756879360",
            //            "updateTime" => "1756757165956",
            //            "orderId" => "2030218284767504128",
            //            "clientOrderId" => "1756756876002",
            //            "symbol" => "SOL-SWAP-USDT",
            //            "price" => "144",
            //            "leverage" => "50",
            //            "origQty" => "1",
            //            "executedQty" => "0",
            //            "executeQty" => "0",
            //            "avgPrice" => "0",
            //            "marginLocked" => "0",
            //            "type" => "LIMIT",
            //            "side" => "BUY_OPEN",
            //            "timeInForce" => "GTC",
            //            "status" => "CANCELED",
            //            "priceType" => "INPUT",
            //            "triggerType" => "0",
            //            "fallType" => "0",
            //            "activeStatus" => "0"
            //        }
            //    )
            //
        }
        $ordersList = array();
        for ($i = 0; $i < count($response); $i++) {
            $ordersList[] = array( 'result' => $response[$i] );
        }
        return $this->parse_orders($ordersList, $market, $since, $limit);
    }

    public function fetch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        /**
         * fetch all trades made by the user
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#account-trade-list-user_data
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#account-trade-list-user_data
         *
         * @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 trade 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 trades for
         * @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();
        $request = array();
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $market = $this->market($symbol);
        $request['symbol'] = $market['id'];
        $marketType = null;
        list($marketType, $params) = $this->handle_market_type_and_params('fetchMyTrades', $market, $params);
        list($request, $params) = $this->handle_until_option('endTime', $request, $params);
        $response = null;
        if ($marketType === 'spot') {
            $response = $this->privateGetApiV1AccountTrades ($this->extend($request, $params));
            //
            //    array(
            //        array(
            //            "id" => "2024934575206059008",
            //            "symbol" => "ETHUSDT",
            //            "symbolName" => "ETHUSDT",
            //            "orderId" => "2024934575097029888",
            //            "price" => "4641.21",
            //            "qty" => "0.001",
            //            "commission" => "0.00464121",
            //            "commissionAsset" => "USDT",
            //            "time" => "1756127012094",
            //            "isBuyer" => false,
            //            "isMaker" => false,
            //            "fee" => array(
            //                "feeCoinId" => "USDT",
            //                "feeCoinName" => "USDT",
            //                "fee" => "0.00464121"
            //            ),
            //            "feeCoinId" => "USDT",
            //            "feeAmount" => "0.00464121",
            //            "makerRebate" => "0",
            //            "ticketId" => "4864450547563401875"
            //        ), ...
            //
        } else {
            $response = $this->privateGetApiV1FuturesUserTrades ($request);
            //
            //    [
            //        {
            //            "time" => "1756758426899",
            //            "id" => "2030231266499116032",
            //            "orderId" => "2030231266373265152",
            //            "symbol" => "DOGE-SWAP-USDT",
            //            "price" => "0.21191",
            //            "qty" => "63",
            //            "commissionAsset" => "USDT",
            //            "commission" => "0.00801019",
            //            "makerRebate" => "0",
            //            "type" => "LIMIT",
            //            "side" => "BUY_OPEN",
            //            "realizedPnl" => "0",
            //            "ticketId" => "4900760819871364854",
            //            "isMaker" => false
            //        }
            //    )
            //
        }
        return $this->parse_trades($response, $market, $since, $limit);
    }

    public function transfer(string $code, float $amount, string $fromAccount, string $toAccount, $params = array ()): array {
        /**
         * transfer $currency internally between wallets on the same account
         *
         * @see https://open.big.one/docs/spot_transfer.html#transfer-of-user
         *
         * @param {string} $code unified $currency $code
         * @param {float} $amount amount to transfer
         * @param {string} $fromAccount 'spot', 'swap'
         * @param {string} $toAccount 'spot', 'swap'
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=transfer-structure transfer structure~
         */
        $this->load_markets();
        $currency = $this->currency($code);
        $accountsByType = $this->safe_dict($this->options, 'accountsByType', array());
        $fromId = $this->safe_string($accountsByType, $fromAccount, $fromAccount);
        $toId = $this->safe_string($accountsByType, $toAccount, $toAccount);
        $request = array(
            'asset' => $currency['id'],
            'quantity' => $this->currency_to_precision($code, $amount),
            'fromAccountType' => $fromId,
            'toAccountType' => $toId,
        );
        $response = $this->privatePostApiV1SubAccountTransfer ($this->extend($request, $params));
        //
        //    {
        //     "code" => 200, // 200 = success
        //     "msg" => "success" // $response message
        //    }
        //
        return $this->parse_transfer($response, $currency);
    }

    public function parse_transfer(array $transfer, ?array $currency = null): array {
        //
        //    {
        //     "code" => 200, // 200 = success
        //     "msg" => "success" // response message
        //    }
        //
        return array(
            'info' => $transfer,
            'id' => null,
            'timestamp' => null,
            'datetime' => null,
            'currency' => null,
            'amount' => null,
            'fromAccount' => null,
            'toAccount' => null,
            'status' => null,
        );
    }

    public function fetch_ledger(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch the history of changes, actions done by the user or operations that altered the balance of the user
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#get-account-transaction-history-list-user_data
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#get-future-account-transaction-history-list-user_data
         *
         * @param {string} [$code] unified $currency $code, default is null
         * @param {int} [$since] timestamp in ms of the earliest ledger entry, default is null
         * @param {int} [$limit] max number of ledger entries to return, default is null
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->until] end time in ms
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=ledger ledger structure~
         */
        $this->load_markets();
        $currency = null;
        $request = array();
        if ($code !== null) {
            $currency = $this->currency($code);
            $request['coin'] = $currency['id'];
        }
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        list($request, $params) = $this->handle_until_option('endTime', $request, $params);
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $marketType = null;
        list($marketType, $params) = $this->handle_market_type_and_params('cancelAllOrders', null, $params);
        $response = null;
        if ($marketType === 'spot') {
            $response = $this->privateGetApiV1AccountBalanceFlow ($this->extend($request, $params));
        } else {
            $response = $this->privateGetApiV1FuturesBalanceFlow ($this->extend($request, $params));
        }
        //
        // both answers are same format
        //
        // [
        //     array(
        //         "id" => "539870570957903104",
        //         "accountId" => "122216245228131",
        //         "coin" => "BTC",
        //         "coinId" => "BTC",
        //         "coinName" => "BTC",
        //         "flowTypeValue" => 51,
        //         "flowType" => "USER_ACCOUNT_TRANSFER",
        //         "flowName" => "Transfer",
        //         "change" => "-12.5",
        //         "total" => "379.624059937852365",
        //         "created" => "1579093587214"
        //     ),
        //
        return $this->parse_ledger($response, $currency, $since, $limit);
    }

    public function parse_ledger_entry(array $item, ?array $currency = null): array {
        $currencyId = $this->safe_string($item, 'coinId');
        $currency = $this->safe_currency($currencyId, $currency);
        $timestamp = $this->safe_integer($item, 'created');
        $after = $this->safe_number($item, 'total');
        $amountRaw = $this->safe_string($item, 'change');
        $amount = $this->parse_number(Precise::string_abs($amountRaw));
        $direction = 'in';
        if (str_starts_with($amountRaw, '-')) {
            $direction = 'out';
        }
        return $this->safe_ledger_entry(array(
            'info' => $item,
            'id' => $this->safe_string($item, 'id'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'direction' => $direction,
            'account' => null,
            'referenceId' => null,
            'referenceAccount' => null,
            'type' => $this->parse_ledger_type($this->safe_string($item, 'flowType')),
            'currency' => $currency['code'],
            'amount' => $amount,
            'before' => null,
            'after' => $after,
            'status' => null,
            'fee' => null,
        ), $currency);
    }

    public function parse_ledger_type($type) {
        $types = array(
            'USER_ACCOUNT_TRANSFER' => 'transfer',
            'AIRDROP' => 'rebate',
        );
        return $this->safe_string($types, $type, $type);
    }

    public function fetch_trading_fees($params = array ()): array {
        /**
         * fetch the trading fees for multiple markets
         *
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#user-trade-$fee-rate-user_data
         *
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a dictionary of ~@link https://docs.ccxt.com/#/?id=$fee-structure $fee structures~ indexed by $market symbols
         */
        $this->load_markets();
        $response = null;
        $marketType = null;
        $market = null;
        list($marketType, $params) = $this->handle_market_type_and_params('fetchTradingFees', null, $params);
        if ($marketType === 'spot') {
            throw new NotSupported($this->id . ' fetchTradingFees() => does not support ' . $marketType . ' markets');
        } elseif ($this->in_array($marketType, array( 'swap', 'future' ))) {
            $symbol = null;
            list($symbol, $params) = $this->handle_param_string($params, 'symbol');
            if ($symbol === null) {
                throw new BadRequest($this->id . ' fetchTradingFees requires a $params["symbol"]');
            }
            $market = $this->market($symbol);
            $request = array(
                'symbol' => $market['id'],
            );
            $response = $this->privateGetApiV1FuturesCommissionRate ($this->extend($request, $params));
        }
        //
        // {
        //     "openMakerFee" => "0.000006", // The trade $fee rate for opening pending orders
        //     "openTakerFee" => "0.0001", // The trade $fee rate for open position taker
        //     "closeMakerFee" => "0.0002", // The trade $fee rate for closing pending orders
        //     "closeTakerFee" => "0.0004" // The trade $fee rate for closing a taker order
        // }
        //
        $result = array();
        $entry = $response;
        $marketId = $this->safe_string($entry, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $fee = $this->parse_trading_fee($entry, $market);
        $result[$market['symbol']] = $fee;
        return $result;
    }

    public function parse_trading_fee($data, ?array $market = null) {
        $marketId = $this->safe_string($data, 'symbol');
        return array(
            'info' => $data,
            'symbol' => $this->safe_symbol($marketId, $market),
            'maker' => $this->safe_number($data, 'closeMakerFee'),
            'taker' => $this->safe_number($data, 'closeTakerFee'),
            'percentage' => null,
            'tierBased' => null,
        );
    }

    public function fetch_deposits(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all deposits made to an account
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#deposit-history-user_data
         *
         * @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 deposit 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~
         */
        return $this->fetch_deposits_or_withdrawals_helper('deposits', $code, $since, $limit, $params);
    }

    public function fetch_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): array {
        /**
         * fetch all withdrawals made from an account
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#withdrawal-records-user_data
         *
         * @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 withdrawal 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~
         */
        return $this->fetch_deposits_or_withdrawals_helper('withdrawals', $code, $since, $limit, $params);
    }

    public function fetch_deposits_or_withdrawals_helper($type, $code, $since, $limit, $params) {
        $this->load_markets();
        $currency = null;
        $request = array();
        if ($code !== null) {
            $currency = $this->currency($code);
            $request['coin'] = $currency['id'];
        }
        if ($since !== null) {
            $request['startTime'] = $since;
        }
        list($request, $params) = $this->handle_until_option('endTime', $request, $params);
        if ($limit !== null) {
            $request['limit'] = $limit;
        }
        $response = null;
        if ($type === 'deposits') {
            $response = $this->privateGetApiV1AccountDepositOrders ($this->extend($request, $params));
            //
            // array(
            //     {
            //         "time" => 1499865549590,
            //         "id" => 100234,
            //         "coinName" => "EOS",
            //         "statusCode" => "DEPOSIT_CAN_WITHDRAW",
            //         "status" => "2", // 2=SUCCESS, 11=REJECT, 12=AUDIT
            //         "address" => "deposit2bb",
            //         "txId" => "98A3EA560C6B3336D348B6C83F0F95ECE4F1F5919E94BD006E5BF3BF264FACFC",
            //         "txIdUrl" => "",
            //         "requiredConfirmTimes" => "5",
            //         "confirmTimes" => "5",
            //         "quantity" => "1.01",
            //         "coin" => "EOS",
            //         "fromAddress" => "clarkkent",
            //         "fromAddressTag" => "19029901"
            //         "addressTag" => "19012584",
            //     }
            // )
            //
        } elseif ($type === 'withdrawals') {
            $response = $this->privateGetApiV1AccountWithdrawOrders ($this->extend($request, $params));
            //
            // array(
            //     {
            //         "time":"1536232111669",
            //         "id ":"90161227158286336",
            //         "accountId":"517256161325920",
            //         "coinName":"BHC",
            //         "statusCode":"PROCESSING_STATUS",
            //         "status":3,
            //         "address":"0x815bF1c3cc0f49b8FC66B21A7e48fCb476051209",
            //         "txId ":"",
            //         "txIdUrl ":"",
            //         "requiredConfirmTimes ":0, // Number of confirmation requests
            //         "confirmTimes ":0, // number of confirmations
            //         "quantity":"14", // Withdrawal amount
            //         "coinId ":"BHC",
            //         "addressExt":"address tag",
            //         "arriveQuantity":"14",
            //         "walletHandleTime":"1536232111669",
            //         "feeCoinId ":"BHC",
            //         "feeCoinName ":"BHC",
            //         "fee":"0.1",
            //         "kernelId":"", // Exclusive to BEAM and GRIN
            //         "isInternalTransfer" => false // Whether internal transfer
            //     }
            // )
            //
        }
        return $this->parse_transactions($response, $currency, $since, $limit, $params);
    }

    public function parse_transaction(array $transaction, ?array $currency = null): array {
        //
        // fetchDeposits & fetchWithdrawals
        //
        //     {
        //         "time" => 1499865549590,
        //         "id" => 100234,
        //         "coinName" => "EOS",
        //         "statusCode" => "DEPOSIT_CAN_WITHDRAW",
        //         "status" => "2", // 2=SUCCESS, 11=REJECT, 12=AUDIT
        //         "address" => "deposit2bb",
        //         "txId" => "98A3EA560C6B3336D348B6C83F0F95ECE4F1F5919E94BD006E5BF3BF264FACFC",
        //         "txIdUrl" => "",
        //         "requiredConfirmTimes" => "5",
        //         "confirmTimes" => "5",
        //         "quantity" => "1.01",
        //         "coin" => "EOS",                     // present in "fetchDeposits"
        //         "coinId ":"BHC",                   // present in "fetchWithdrawals"
        //         "addressTag" => "19012584",          // present in "fetchDeposits"
        //         "addressExt":"address tag",        // present in "fetchWithdrawals"
        //         "fromAddress" => "clarkkent",        // present in "fetchDeposits"
        //         "fromAddressTag" => "19029901"       // present in "fetchDeposits"
        //         "arriveQuantity":"14",             // present in "fetchWithdrawals"
        //         "walletHandleTime":"1536232111669",// present in "fetchWithdrawals"
        //         "feeCoinId ":"BHC",                // present in "fetchWithdrawals"
        //         "feeCoinName ":"BHC",              // present in "fetchWithdrawals"
        //         "fee":"0.1",                       // present in "fetchWithdrawals"
        //         "kernelId":"",                     // present in "fetchWithdrawals"
        //         "isInternalTransfer" => false        // present in "fetchWithdrawals"
        //     }
        //
        // withdraw
        //
        //     {
        //         "status" => 0,
        //         "success" => true,
        //         "needBrokerAudit" => false, // Do you need a brokerage review?
        //         "id" => "423885103582776064",
        //         "refuseReason":"" // failure rejection reason
        //     }
        //
        $timestamp = $this->safe_integer($transaction, 'time');
        $currencyId = $this->safe_string_2($transaction, 'coin', 'coinId');
        $code = $this->safe_currency_code($currencyId, $currency);
        $feeString = $this->safe_string($transaction, 'fee');
        $feeCoin = $this->safe_string($transaction, 'feeCoinName');
        $fee = null;
        if ($feeString !== null) {
            $fee = array(
                'cost' => $this->parse_number($feeString),
                'currency' => $this->safe_currency_code($feeCoin),
            );
        }
        $tagTo = $this->safe_string_2($transaction, 'addressTag', 'addressExt');
        $tagFrom = $this->safe_string($transaction, 'fromAddressTag');
        $addressTo = $this->safe_string($transaction, 'address');
        $addressFrom = $this->safe_string($transaction, 'fromAddress');
        $isWithdraw = (is_array($transaction) && array_key_exists('arriveQuantity', $transaction));
        $type = $isWithdraw ? 'withdrawal' : 'deposit';
        return array(
            'info' => $transaction,
            'id' => $this->safe_string($transaction, 'id'),
            'txid' => $this->safe_string($transaction, 'txId'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'network' => null,
            'address' => null,
            'addressTo' => $addressTo,
            'addressFrom' => $addressFrom,
            'tag' => null,
            'tagTo' => $tagTo,
            'tagFrom' => $tagFrom,
            'type' => $type,
            'amount' => $this->safe_number($transaction, 'quantity'),
            'currency' => $code,
            'status' => $this->parse_transaction_status($this->safe_string($transaction, 'status')),
            'updated' => null,
            'fee' => $fee,
            'comment' => null,
            'internal' => null,
        );
    }

    public function parse_transaction_status(?string $status) {
        $statuses = array(
            '2' => 'pending',
            '12' => 'pending',
            '11' => 'failed',
            '3' => 'ok',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function fetch_deposit_address(string $code, $params = array ()): array {
        /**
         * fetch the deposit address for a $currency associated with this account
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#deposit-address-user_data
         *
         * @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(
            'coin' => $currency['id'],
        );
        list($networkCode, $paramsOmitted) = $this->handle_network_code_and_params($this->extend($request, $params));
        if ($networkCode === null) {
            throw new ArgumentsRequired($this->id . ' fetchDepositAddress() : param["network"] is required');
        }
        $request['chainType'] = $this->network_code_to_id($networkCode);
        $response = $this->privateGetApiV1AccountDepositAddress ($this->extend($request, $paramsOmitted));
        //
        //     {
        //         "canDeposit":false,//Is it possible to recharge
        //         "address":"0x815bF1c3cc0f49b8FC66B21A7e48fCb476051209",
        //         "addressExt":"address tag",
        //         "minQuantity":"100",//minimum amount
        //         "requiredConfirmTimes ":1,//Arrival confirmation number
        //         "canWithdrawConfirmNum ":12,//Withdrawal confirmation number
        //         "coinType":"ERC20_TOKEN"
        //     }
        //
        return $this->parse_deposit_address($response, $currency);
    }

    public function parse_deposit_address($depositAddress, ?array $currency = null): array {
        $address = $this->safe_string($depositAddress, 'address');
        $this->check_address($address);
        return array(
            'info' => $depositAddress,
            'currency' => $this->safe_string($currency, 'code'),
            'network' => null,
            'address' => $address,
            'tag' => $this->safe_string($depositAddress, 'addressExt'),
        );
    }

    public function withdraw(string $code, float $amount, string $address, ?string $tag = null, $params = array ()): array {
        /**
         * make a withdrawal
         *
         * @see https://toobit-docs.github.io/apidocs/spot/v1/en/#withdraw-user_data
         *
         * @param {string} $code unified $currency $code
         * @param {float} $amount the $amount to withdraw
         * @param {string} $address the $address to withdraw to
         * @param {string} $tag a memo for the transaction
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structure~
         */
        $this->check_address($address);
        $networkCode = null;
        list($networkCode, $params) = $this->handle_network_code_and_params($params);
        if ($networkCode === null) {
            throw new ArgumentsRequired($this->id . ' withdraw() : param["network"] is required');
        }
        $this->load_markets();
        $currency = $this->currency($code);
        $request = array(
            'coin' => $currency['id'],
            'address' => $address,
            'quantity' => $this->currency_to_precision($currency['code'], $amount),
            'network' => $networkCode,
        );
        if ($tag !== null) {
            $request['addressExt'] = $tag;
        }
        $response = $this->privatePostApiV1AccountWithdraw ($this->extend($request, $params));
        //
        // {
        //     "status" => 0,
        //     "success" => true,
        //     "needBrokerAudit" => false, // Do you need a brokerage review?
        //     "id" => "423885103582776064", // Withdrawal successful order id
        //     "refuseReason":"" // failure rejection reason
        // }
        //
        return $this->parse_transaction($response, $currency);
    }

    public function set_margin_mode(string $marginMode, ?string $symbol = null, $params = array ()) {
        /**
         * set margin mode to 'cross' or 'isolated'
         *
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#change-margin-type-trade
         *
         * @param {string} $marginMode 'cross' or 'isolated'
         * @param {string} $symbol unified $market $symbol
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} $response from the exchange
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' setMarginMode() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        if ($market['type'] !== 'swap') {
            throw new BadSymbol($this->id . ' setMarginMode() supports swap contracts only');
        }
        $marginMode = strtoupper($marginMode);
        $request = array(
            'symbol' => $market['id'],
            'marginType' => $marginMode,
        );
        $response = $this->privatePostApiV1FuturesMarginType ($this->extend($request, $params));
        //
        // array("code":200,"symbolId":"BTC-SWAP-USDT","marginType":"ISOLATED")
        //
        return $response;
    }

    public function set_leverage(int $leverage, ?string $symbol = null, $params = array ()) {
        /**
         * set the level of $leverage for a $market
         *
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#change-initial-$leverage-trade
         *
         * @param {float} $leverage the rate of $leverage
         * @param {string} $symbol unified $market $symbol
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} $response from the exchange
         */
        if ($symbol === null) {
            throw new ArgumentsRequired($this->id . ' setLeverage() requires a $symbol argument');
        }
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            'leverage' => $leverage,
        );
        $response = $this->privatePostApiV1FuturesLeverage ($this->extend($request, $params));
        //
        // array("code":200,"symbolId":"BTC-SWAP-USDT","leverage":"19")
        //
        return $response;
    }

    public function fetch_leverage(string $symbol, $params = array ()): array {
        /**
         * fetch the set leverage for a $market
         *
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#get-the-leverage-multiple-and-position-mode-user_data
         *
         * @param {string} $symbol unified $market $symbol
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array} a ~@link https://docs.ccxt.com/#/?id=leverage-structure leverage structure~
         */
        $this->load_markets();
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
        );
        $response = $this->privateGetApiV1FuturesAccountLeverage ($this->extend($request, $params));
        //
        // array(
        //     {
        //         "symbol":"BTC-SWAP-USDT", //symbol
        //         "leverage":"20",  // leverage
        //         "marginType":"CROSS" // CROSS;ISOLATED
        //     }
        // )
        //
        $data = $this->safe_dict($response, 'data', array());
        return $this->parse_leverage($data, $market);
    }

    public function parse_leverage(array $leverage, ?array $market = null): array {
        $marketId = $this->safe_string($leverage, 'symbol');
        $leverageValue = $this->safe_integer($leverage, 'leverage');
        $marginType = $this->safe_string($leverage, 'marginType');
        $marginMode = ($marginType === 'crossed') ? 'cross' : 'isolated';
        return array(
            'info' => $leverage,
            'symbol' => $this->safe_symbol($marketId, $market),
            'marginMode' => $marginMode,
            'longLeverage' => $leverageValue,
            'shortLeverage' => $leverageValue,
        );
    }

    public function fetch_positions(?array $symbols = null, $params = array ()): array {
        /**
         * fetch all open positions
         *
         * @see https://toobit-docs.github.io/apidocs/usdt_swap/v1/en/#query-position-user_data
         *
         * @param {string[]|null} $symbols list of unified $market $symbols
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=position-structure position structures~
         */
        $this->load_markets();
        $request = array();
        $market = null;
        if ($symbols !== null) {
            $length = count($symbols);
            if ($length > 1) {
                throw new BadRequest($this->id . ' fetchPositions() only accepts an array with a single symbol or without $symbols argument');
            }
            $firstSymbol = $this->safe_string($symbols, 0);
            if ($firstSymbol !== null) {
                $market = $this->market($firstSymbol);
                $request['symbol'] = $market['id'];
            }
        }
        $response = $this->privateGetApiV1FuturesPositions ($this->extend($request, $params));
        //
        //    array(
        //        {
        //            "symbol" => "DOGE-SWAP-USDT",
        //            "side" => "LONG",
        //            "avgPrice" => "0.21191",
        //            "position" => "63",
        //            "available" => "63",
        //            "leverage" => "25",
        //            "lastPrice" => "0.20932",
        //            "positionValue" => "13.3503",
        //            "flp" => "0.05471",
        //            "margin" => "0.5262",
        //            "marginRate" => "",
        //            "unrealizedPnL" => "-0.1701",
        //            "profitRate" => "-0.3185",
        //            "realizedPnL" => "-0.008",
        //            "minMargin" => "0",
        //            "maxNotionalValue" => "10000000",
        //            "markPrice" => "0.20921"
        //        }
        //    )
        //
        return $this->parse_positions($response, $symbols);
    }

    public function parse_position(array $position, ?array $market = null) {
        $marketId = $this->safe_string($position, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $side = $this->safe_string_lower($position, 'side');
        $quantity = $this->safe_string($position, 'position');
        $leverage = $this->safe_integer($position, 'leverage');
        return $this->safe_position(array(
            'info' => $position,
            'id' => $this->safe_string($position, 'id'),
            'symbol' => $market['symbol'],
            'entryPrice' => $this->safe_string($position, 'avgPrice'),
            'markPrice' => $this->safe_string($position, 'markPrice'),
            'lastPrice' => $this->safe_string($position, 'lastPrice'),
            'notional' => $this->safe_string($position, 'positionValue'),
            'collateral' => null,
            'unrealizedPnl' => $this->safe_string($position, 'unrealizedPnL'),
            'side' => $side,
            'contracts' => $this->parse_number($quantity),
            'contractSize' => null,
            'timestamp' => null,
            'datetime' => null,
            'hedged' => null,
            'maintenanceMargin' => null,
            'maintenanceMarginPercentage' => null,
            'initialMargin' => $this->safe_string($position, 'margin'),
            'initialMarginPercentage' => null,
            'leverage' => $leverage,
            'liquidationPrice' => null,
            'marginRatio' => null,
            'marginMode' => null,
            'percentage' => null,
        ));
    }

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $url = $this->urls['api'][$api] . '/' . $this->implode_params($path, $params);
        $isPost = $method === 'POST';
        $isDelete = $method === 'DELETE';
        $extraQuery = array();
        $query = $this->omit($params, $this->extract_params($path));
        if ($api !== 'private') {
            // Public endpoints
            if (!$isPost) {
                if ($query) {
                    $url .= '?' . $this->urlencode($query);
                }
            }
        } else {
            $this->check_required_credentials();
            $timestamp = $this->milliseconds();
            // Add $timestamp to parameters for signed endpoints
            $extraQuery['recvWindow'] = $this->safe_string($this->options, 'recvWindow', '5000');
            $extraQuery['timestamp'] = (string) $timestamp;
            $queryExtended = $this->extend($query, $extraQuery);
            $queryString = '';
            if ($isPost || $isDelete) {
                // everything else except Batch-Orders
                if (gettype($params) !== 'array' || array_keys($params) !== array_keys(array_keys($params))) {
                    $body = $this->urlencode($queryExtended);
                } else {
                    $queryString = $this->urlencode($extraQuery);
                    $body = $this->json($query);
                }
            } else {
                $queryString = $this->urlencode($queryExtended);
            }
            $payload = $queryString;
            if ($body !== null) {
                $payload = $body . $payload;
            }
            $signature = $this->hmac($this->encode($payload), $this->encode($this->secret), 'sha256', 'hex');
            if ($queryString !== '') {
                $queryString .= '&$signature=' . $signature;
                $url .= '?' . $queryString;
            } else {
                $body .= '&$signature=' . $signature;
            }
            $headers = array(
                'X-BB-APIKEY' => $this->apiKey,
                'Content-Type' => 'application/x-www-form-urlencoded',
            );
        }
        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 ($response === null) {
            return null;
        }
        $errorCode = $this->safe_string($response, 'code');
        $message = $this->safe_string($response, 'msg');
        if ($errorCode && $errorCode !== '200' && $errorCode !== '0') {
            $feedback = $this->id . ' ' . $body;
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $errorCode, $feedback);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $message, $feedback);
            throw new ExchangeError($feedback);
        }
        return null;
    }
}
