<?php

namespace ccxt\async;

// 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\async\abstract\bitmart as Exchange;
use ccxt\ExchangeError;
use ccxt\ArgumentsRequired;
use ccxt\BadRequest;
use ccxt\BadSymbol;
use ccxt\InvalidOrder;
use ccxt\NotSupported;
use ccxt\Precise;
use \React\Async;
use \React\Promise\PromiseInterface;

class bitmart extends Exchange {

    public function describe(): mixed {
        return $this->deep_extend(parent::describe(), array(
            'id' => 'bitmart',
            'name' => 'BitMart',
            'countries' => array( 'US', 'CN', 'HK', 'KR' ),
            // 150 per 5 seconds = 30 per second
            // rateLimit = 1000ms / 30 ~= 33.334
            'rateLimit' => 33.34,
            'version' => 'v2',
            'certified' => true,
            'pro' => true,
            'has' => array(
                'CORS' => null,
                'spot' => true,
                'margin' => true,
                'swap' => true,
                'future' => false,
                'option' => false,
                'borrowCrossMargin' => false,
                'borrowIsolatedMargin' => true,
                'cancelAllOrders' => true,
                'cancelOrder' => true,
                'cancelOrders' => true,
                'createMarketBuyOrderWithCost' => true,
                'createMarketOrderWithCost' => false,
                'createMarketSellOrderWithCost' => false,
                'createOrder' => true,
                'createOrders' => true,
                'createPostOnlyOrder' => true,
                'createReduceOnlyOrder' => true,
                'createStopLimitOrder' => false,
                'createStopMarketOrder' => false,
                'createStopOrder' => false,
                'createTrailingPercentOrder' => true,
                'fetchBalance' => true,
                'fetchBorrowInterest' => true,
                'fetchBorrowRateHistories' => false,
                'fetchBorrowRateHistory' => false,
                'fetchCanceledOrders' => true,
                'fetchClosedOrders' => true,
                'fetchCrossBorrowRate' => false,
                'fetchCrossBorrowRates' => false,
                'fetchCurrencies' => true,
                'fetchDeposit' => true,
                'fetchDepositAddress' => true,
                'fetchDepositAddresses' => false,
                'fetchDepositAddressesByNetwork' => false,
                'fetchDeposits' => true,
                'fetchDepositWithdrawFee' => true,
                'fetchDepositWithdrawFees' => false,
                'fetchFundingHistory' => true,
                'fetchFundingRate' => true,
                'fetchFundingRateHistory' => false,
                'fetchFundingRates' => false,
                'fetchIsolatedBorrowRate' => true,
                'fetchIsolatedBorrowRates' => true,
                'fetchLedger' => true,
                'fetchLiquidations' => false,
                'fetchMarginMode' => false,
                'fetchMarkets' => true,
                'fetchMarkOHLCV' => true,
                'fetchMyLiquidations' => true,
                'fetchMyTrades' => true,
                'fetchOHLCV' => true,
                'fetchOpenInterest' => true,
                'fetchOpenInterestHistory' => false,
                'fetchOpenOrders' => true,
                'fetchOrder' => true,
                'fetchOrderBook' => true,
                'fetchOrders' => false,
                'fetchOrderTrades' => true,
                'fetchPosition' => true,
                'fetchPositionMode' => true,
                'fetchPositions' => true,
                'fetchStatus' => true,
                'fetchTicker' => true,
                'fetchTickers' => true,
                'fetchTime' => true,
                'fetchTrades' => true,
                'fetchTradingFee' => true,
                'fetchTradingFees' => false,
                'fetchTransactionFee' => true,
                'fetchTransactionFees' => false,
                'fetchTransfer' => false,
                'fetchTransfers' => true,
                'fetchWithdrawAddresses' => true,
                'fetchWithdrawAddressesByNetwork' => false,
                'fetchWithdrawal' => true,
                'fetchWithdrawals' => true,
                'reduceMargin' => false,
                'repayCrossMargin' => false,
                'repayIsolatedMargin' => true,
                'setLeverage' => true,
                'setMarginMode' => false,
                'setPositionMode' => true,
                'transfer' => true,
                'withdraw' => true,
            ),
            'hostname' => 'bitmart.com', // bitmart.info, bitmart.news for Hong Kong users
            'urls' => array(
                'logo' => 'https://github.com/user-attachments/assets/0623e9c4-f50e-48c9-82bd-65c3908c3a14',
                'api' => array(
                    'spot' => 'https://api-cloud.{hostname}',
                    'swap' => 'https://api-cloud-v2.{hostname}', // bitmart.info for Hong Kong users
                ),
                'www' => 'https://www.bitmart.com/',
                'doc' => 'https://developer-pro.bitmart.com/',
                'referral' => array(
                    'url' => 'http://www.bitmart.com/?r=rQCFLh',
                    'discount' => 0.3,
                ),
                'fees' => 'https://www.bitmart.com/fee/en',
            ),
            'requiredCredentials' => array(
                'apiKey' => true,
                'secret' => true,
                'uid' => true,
            ),
            'api' => array(
                'public' => array(
                    'get' => array(
                        'system/time' => 3, // 10 times/sec => 30/10 = 3
                        'system/service' => 3,
                        // spot markets
                        'spot/v1/currencies' => 7.5,
                        'spot/v1/symbols' => 7.5,
                        'spot/v1/symbols/details' => 5,
                        'spot/quotation/v3/tickers' => 6, // 10 times/2 sec = 5/s => 30/5 = 6
                        'spot/quotation/v3/ticker' => 4, // 15 times/2 sec = 7.5/s => 30/7.5 = 4
                        'spot/quotation/v3/lite-klines' => 5, // should be 4 but errors
                        'spot/quotation/v3/klines' => 7, // should be 6 but errors
                        'spot/quotation/v3/books' => 4, // 15 times/2 sec = 7.5/s => 30/7.5 = 4
                        'spot/quotation/v3/trades' => 4, // 15 times/2 sec = 7.5/s => 30/7.5 = 4
                        'spot/v1/ticker' => 5,
                        'spot/v2/ticker' => 30,
                        'spot/v1/ticker_detail' => 5, // 12 times/2 sec = 6/s => 30/6 = 5
                        'spot/v1/steps' => 30,
                        'spot/v1/symbols/kline' => 6, // should be 5 but errors
                        'spot/v1/symbols/book' => 5,
                        'spot/v1/symbols/trades' => 5,
                        // contract markets
                        'contract/v1/tickers' => 15,
                        'contract/public/details' => 5,
                        'contract/public/depth' => 5,
                        'contract/public/open-interest' => 30,
                        'contract/public/funding-rate' => 30,
                        'contract/public/funding-rate-history' => 30,
                        'contract/public/kline' => 6, // should be 5 but errors
                        'account/v1/currencies' => 30,
                        'contract/public/markprice-kline' => 5, // 6 times per 1 second
                    ),
                ),
                'private' => array(
                    'get' => array(
                        // sub-account
                        'account/sub-account/v1/transfer-list' => 7.5,
                        'account/sub-account/v1/transfer-history' => 7.5,
                        'account/sub-account/main/v1/wallet' => 5,
                        'account/sub-account/main/v1/subaccount-list' => 7.5,
                        'account/contract/sub-account/main/v1/wallet' => 5,
                        'account/contract/sub-account/main/v1/transfer-list' => 7.5,
                        'account/contract/sub-account/v1/transfer-history' => 7.5,
                        // account
                        'account/v1/wallet' => 5,
                        'account/v1/currencies' => 30,
                        'spot/v1/wallet' => 5,
                        'account/v1/deposit/address' => 30,
                        'account/v1/withdraw/charge' => 32, // should be 30 but errors
                        'account/v2/deposit-withdraw/history' => 7.5,
                        'account/v1/deposit-withdraw/detail' => 7.5,
                        'account/v1/withdraw/address/list' => 30, // 2 times per 2 seconds
                        // order
                        'spot/v1/order_detail' => 1,
                        'spot/v2/orders' => 5,
                        'spot/v1/trades' => 5,
                        // newer order endpoint
                        'spot/v2/trades' => 4,
                        'spot/v3/orders' => 5,
                        'spot/v2/order_detail' => 1,
                        // margin
                        'spot/v1/margin/isolated/borrow_record' => 1,
                        'spot/v1/margin/isolated/repay_record' => 1,
                        'spot/v1/margin/isolated/pairs' => 30,
                        'spot/v1/margin/isolated/account' => 5,
                        'spot/v1/trade_fee' => 30,
                        'spot/v1/user_fee' => 30,
                        // broker
                        'spot/v1/broker/rebate' => 1,
                        // contract
                        'contract/private/assets-detail' => 5,
                        'contract/private/order' => 1.2,
                        'contract/private/order-history' => 10,
                        'contract/private/position' => 10,
                        'contract/private/position-v2' => 10,
                        'contract/private/get-open-orders' => 1.2,
                        'contract/private/current-plan-order' => 1.2,
                        'contract/private/trades' => 10,
                        'contract/private/position-risk' => 10,
                        'contract/private/affilate/rebate-list' => 10,
                        'contract/private/affilate/trade-list' => 10,
                        'contract/private/transaction-history' => 10,
                        'contract/private/get-position-mode' => 1,
                    ),
                    'post' => array(
                        // sub-account endpoints
                        'account/sub-account/main/v1/sub-to-main' => 30,
                        'account/sub-account/sub/v1/sub-to-main' => 30,
                        'account/sub-account/main/v1/main-to-sub' => 30,
                        'account/sub-account/sub/v1/sub-to-sub' => 30,
                        'account/sub-account/main/v1/sub-to-sub' => 30,
                        'account/contract/sub-account/main/v1/sub-to-main' => 7.5,
                        'account/contract/sub-account/main/v1/main-to-sub' => 7.5,
                        'account/contract/sub-account/sub/v1/sub-to-main' => 7.5,
                        // account
                        'account/v1/withdraw/apply' => 7.5,
                        // transaction and trading
                        'spot/v1/submit_order' => 1,
                        'spot/v1/batch_orders' => 1,
                        'spot/v2/cancel_order' => 1,
                        'spot/v1/cancel_orders' => 15,
                        'spot/v4/query/order' => 1, // 60 times/2 sec = 30/s => 30/30 = 1
                        'spot/v4/query/client-order' => 1, // 60 times/2 sec = 30/s => 30/30 = 1
                        'spot/v4/query/open-orders' => 5, // 12 times/2 sec = 6/s => 30/6 = 5
                        'spot/v4/query/history-orders' => 5, // 12 times/2 sec = 6/s => 30/6 = 5
                        'spot/v4/query/trades' => 5, // 12 times/2 sec = 6/s => 30/6 = 5
                        'spot/v4/query/order-trades' => 5, // 12 times/2 sec = 6/s => 30/6 = 5
                        'spot/v4/cancel_orders' => 3,
                        'spot/v4/cancel_all' => 90,
                        'spot/v4/batch_orders' => 3,
                        // newer endpoint
                        'spot/v3/cancel_order' => 1,
                        'spot/v2/batch_orders' => 1,
                        'spot/v2/submit_order' => 1,
                        // margin
                        'spot/v1/margin/submit_order' => 1.5, // 20 times per second
                        'spot/v1/margin/isolated/borrow' => 30,
                        'spot/v1/margin/isolated/repay' => 30,
                        'spot/v1/margin/isolated/transfer' => 30,
                        // contract
                        'account/v1/transfer-contract-list' => 60,
                        'account/v1/transfer-contract' => 60,
                        'contract/private/submit-order' => 2.5,
                        'contract/private/cancel-order' => 1.5,
                        'contract/private/cancel-orders' => 30,
                        'contract/private/submit-plan-order' => 2.5,
                        'contract/private/cancel-plan-order' => 1.5,
                        'contract/private/submit-leverage' => 2.5,
                        'contract/private/submit-tp-sl-order' => 2.5,
                        'contract/private/modify-plan-order' => 2.5,
                        'contract/private/modify-preset-plan-order' => 2.5,
                        'contract/private/modify-limit-order' => 2.5,
                        'contract/private/modify-tp-sl-order' => 2.5,
                        'contract/private/submit-trail-order' => 2.5, // weight is not provided by the exchange, is set order
                        'contract/private/cancel-trail-order' => 1.5, // weight is not provided by the exchange, is set order
                        'contract/private/set-position-mode' => 1,
                    ),
                ),
            ),
            'timeframes' => array(
                '1m' => 1,
                '3m' => 3,
                '5m' => 5,
                '15m' => 15,
                '30m' => 30,
                '45m' => 45,
                '1h' => 60,
                '2h' => 120,
                '3h' => 180,
                '4h' => 240,
                '1d' => 1440,
                '1w' => 10080,
                '1M' => 43200,
            ),
            'fees' => array(
                'trading' => array(
                    'tierBased' => true,
                    'percentage' => true,
                    'taker' => $this->parse_number('0.0040'),
                    'maker' => $this->parse_number('0.0035'),
                    'tiers' => array(
                        'taker' => array(
                            array( $this->parse_number('0'), $this->parse_number('0.0020') ),
                            array( $this->parse_number('10'), $this->parse_number('0.18') ),
                            array( $this->parse_number('50'), $this->parse_number('0.0016') ),
                            array( $this->parse_number('250'), $this->parse_number('0.0014') ),
                            array( $this->parse_number('1000'), $this->parse_number('0.0012') ),
                            array( $this->parse_number('5000'), $this->parse_number('0.0010') ),
                            array( $this->parse_number('25000'), $this->parse_number('0.0008') ),
                            array( $this->parse_number('50000'), $this->parse_number('0.0006') ),
                        ),
                        'maker' => array(
                            array( $this->parse_number('0'), $this->parse_number('0.001') ),
                            array( $this->parse_number('10'), $this->parse_number('0.0009') ),
                            array( $this->parse_number('50'), $this->parse_number('0.0008') ),
                            array( $this->parse_number('250'), $this->parse_number('0.0007') ),
                            array( $this->parse_number('1000'), $this->parse_number('0.0006') ),
                            array( $this->parse_number('5000'), $this->parse_number('0.0005') ),
                            array( $this->parse_number('25000'), $this->parse_number('0.0004') ),
                            array( $this->parse_number('50000'), $this->parse_number('0.0003') ),
                        ),
                    ),
                ),
            ),
            'precisionMode' => TICK_SIZE,
            'exceptions' => array(
                'exact' => array(
                    // general errors
                    '30000' => '\\ccxt\\ExchangeError', // 404, Not found
                    '30001' => '\\ccxt\\AuthenticationError', // 401, Header X-BM-KEY is empty
                    '30002' => '\\ccxt\\AuthenticationError', // 401, Header X-BM-KEY not found
                    '30003' => '\\ccxt\\AccountSuspended', // 401, Header X-BM-KEY has frozen
                    '30004' => '\\ccxt\\AuthenticationError', // 401, Header X-BM-SIGN is empty
                    '30005' => '\\ccxt\\AuthenticationError', // 401, Header X-BM-SIGN is wrong
                    '30006' => '\\ccxt\\AuthenticationError', // 401, Header X-BM-TIMESTAMP is empty
                    '30007' => '\\ccxt\\AuthenticationError', // 401, Header X-BM-TIMESTAMP range. Within a minute
                    '30008' => '\\ccxt\\AuthenticationError', // 401, Header X-BM-TIMESTAMP invalid format
                    '30010' => '\\ccxt\\PermissionDenied', // 403, IP is forbidden. We recommend enabling IP whitelist for API trading. After that reauth your account
                    '30011' => '\\ccxt\\AuthenticationError', // 403, Header X-BM-KEY over expire time
                    '30012' => '\\ccxt\\AuthenticationError', // 403, Header X-BM-KEY is forbidden to request it
                    '30013' => '\\ccxt\\RateLimitExceeded', // 429, Request too many requests
                    '30014' => '\\ccxt\\ExchangeNotAvailable', // 503, Service unavailable
                    '30016' => '\\ccxt\\OnMaintenance', // 200, Service maintenance, the function is temporarily unavailable
                    '30017' => '\\ccxt\\RateLimitExceeded', // 418, Your account request is temporarily rejected due to violation of current limiting rules
                    '30018' => '\\ccxt\\BadRequest', // 503, Request Body requires JSON format
                    '30019' => '\\ccxt\\PermissionDenied', // 200, You do not have the permissions to perform this operation
                    // funding account & sub account errors
                    '60000' => '\\ccxt\\BadRequest', // 400, Invalid request (maybe the body is empty, or the int parameter passes string data)
                    '60001' => '\\ccxt\\BadRequest', // 400, Asset account type does not exist
                    '60002' => '\\ccxt\\BadRequest', // 400, currency does not exist
                    '60003' => '\\ccxt\\ExchangeError', // 400, Currency has been closed recharge channel, if there is any problem, please consult customer service
                    '60004' => '\\ccxt\\ExchangeError', // 400, Currency has been closed withdraw channel, if there is any problem, please consult customer service
                    '60005' => '\\ccxt\\ExchangeError', // 400, Minimum amount is %s
                    '60006' => '\\ccxt\\ExchangeError', // 400, Maximum withdraw precision is %d
                    '60007' => '\\ccxt\\InvalidAddress', // 400, Only withdrawals from added addresses are allowed
                    '60008' => '\\ccxt\\InsufficientFunds', // 400, Balance not enough
                    '60009' => '\\ccxt\\ExchangeError', // 400, Beyond the limit
                    '60010' => '\\ccxt\\ExchangeError', // 400, Withdraw id or deposit id not found
                    '60011' => '\\ccxt\\InvalidAddress', // 400, Address is not valid
                    '60012' => '\\ccxt\\ExchangeError', // 400, This action is not property_exists($this, supported) currency(If IOTA, HLX recharge and withdraw calls are prohibited)
                    '60020' => '\\ccxt\\PermissionDenied', // 403, Your account is not allowed to recharge
                    '60021' => '\\ccxt\\PermissionDenied', // 403, Your account is not allowed to withdraw
                    '60022' => '\\ccxt\\PermissionDenied', // 403, No withdrawals for 24 hours
                    '60026' => '\\ccxt\\PermissionDenied', // 403, Sub-account does not have permission to operate
                    '60027' => '\\ccxt\\PermissionDenied', // 403, Only supports sub-account calls
                    '60028' => '\\ccxt\\AccountSuspended', // 403, Account is disabled for security reasons, please contact customer service
                    '60029' => '\\ccxt\\AccountSuspended', // 403, The account is frozen by the master account, please contact the master account to unfreeze the account
                    '60030' => '\\ccxt\\BadRequest', // 405, Method Not Allowed
                    '60031' => '\\ccxt\\BadRequest', // 415, Unsupported Media Type
                    '60050' => '\\ccxt\\ExchangeError', // 500, User account not found
                    '60051' => '\\ccxt\\ExchangeError', // 500, Internal Server Error
                    '61001' => '\\ccxt\\InsufficientFunds', // array("message":"Balance not enough","code":61001,"trace":"b85ea1f8-b9af-4001-ac5f-9e061fe93d78","data":array())
                    '61003' => '\\ccxt\\BadRequest', // 400, array("message":"sub-account not found","code":61003,"trace":"b35ec2fd-0bc9-4ef2-a3c0-6f78d4f335a4","data":array())
                    '61004' => '\\ccxt\\BadRequest', // 400, Duplicate requests (such an existing requestNo)
                    '61005' => '\\ccxt\\BadRequest', // 403, Asset transfer between accounts is not available
                    '61006' => '\\ccxt\\NotSupported', // 403, The sub-account api only supports organization accounts
                    '61007' => '\\ccxt\\ExchangeError', // 403, Please complete your institution verification to enable withdrawal function.
                    '61008' => '\\ccxt\\ExchangeError', // 403, Suspend transfer out
                    // spot public errors
                    '70000' => '\\ccxt\\ExchangeError', // 200, no data
                    '70001' => '\\ccxt\\BadRequest', // 200, request param can not be null
                    '70002' => '\\ccxt\\BadSymbol', // 200, symbol is invalid
                    '70003' => '\\ccxt\\NetworkError', // array("code":70003,"trace":"81a9d57b63be4819b65d3065e6a4682b.105.17105295323593915","message":"net error, please try later","data":null)
                    '71001' => '\\ccxt\\BadRequest', // 200, after is invalid
                    '71002' => '\\ccxt\\BadRequest', // 200, before is invalid
                    '71003' => '\\ccxt\\BadRequest', // 200, request after or before is invalid
                    '71004' => '\\ccxt\\BadRequest', // 200, request kline count limit
                    '71005' => '\\ccxt\\BadRequest', // 200, request step error
                    // spot & margin errors
                    '50000' => '\\ccxt\\BadRequest', // 400, Bad Request
                    '50001' => '\\ccxt\\BadSymbol', // 400, Symbol not found
                    '50002' => '\\ccxt\\BadRequest', // 400, From Or To format error
                    '50003' => '\\ccxt\\BadRequest', // 400, Step format error
                    '50004' => '\\ccxt\\BadRequest', // 400, Kline size over 500
                    '50005' => '\\ccxt\\OrderNotFound', // 400, Order Id not found
                    '50006' => '\\ccxt\\InvalidOrder', // 400, Minimum size is %s
                    '50007' => '\\ccxt\\InvalidOrder', // 400, Maximum size is %s
                    '50008' => '\\ccxt\\InvalidOrder', // 400, Minimum price is %s
                    '50009' => '\\ccxt\\InvalidOrder', // 400, Minimum count*price is %s
                    '50010' => '\\ccxt\\InvalidOrder', // 400, RequestParam size is required
                    '50011' => '\\ccxt\\InvalidOrder', // 400, RequestParam price is required
                    '50012' => '\\ccxt\\InvalidOrder', // 400, RequestParam notional is required
                    '50013' => '\\ccxt\\InvalidOrder', // 400, Maximum limit*offset is %d
                    '50014' => '\\ccxt\\BadRequest', // 400, RequestParam limit is required
                    '50015' => '\\ccxt\\BadRequest', // 400, Minimum limit is 1
                    '50016' => '\\ccxt\\BadRequest', // 400, Maximum limit is %d
                    '50017' => '\\ccxt\\BadRequest', // 400, RequestParam offset is required
                    '50018' => '\\ccxt\\BadRequest', // 400, Minimum offset is 1
                    '50019' => '\\ccxt\\ExchangeError', // 400, Invalid status. validate status is [1=Failed, 2=Success, 3=Frozen Failed, 4=Frozen Success, 5=Partially Filled, 6=Fully Fulled, 7=Canceling, 8=Canceled]                    '50020' => '\\ccxt\\InsufficientFunds', // 400, Balance not enough
                    '50020' => '\\ccxt\\InsufficientFunds', // 400, Balance not enough
                    '50021' => '\\ccxt\\BadRequest', // 400, Invalid %s
                    '50022' => '\\ccxt\\ExchangeNotAvailable', // 400, Service unavailable
                    '50023' => '\\ccxt\\BadSymbol', // 400, This Symbol can't place order by api
                    '50024' => '\\ccxt\\BadRequest', // 400, Order book size over 200
                    '50025' => '\\ccxt\\BadRequest', // 400, Maximum price is %s
                    '50026' => '\\ccxt\\BadRequest', // 400, The buy order price cannot be higher than the open price
                    '50027' => '\\ccxt\\BadRequest', // 400, The sell order price cannot be lower than the open price
                    '50028' => '\\ccxt\\BadRequest', // 400, Missing parameters
                    '50029' => '\\ccxt\\InvalidOrder', // 400, array("message":"param not match : size * price >=1000","code":50029,"trace":"f931f030-b692-401b-a0c5-65edbeadc598","data":array())
                    '50030' => '\\ccxt\\OrderNotFound', // 400, array("message":"Order is already canceled","code":50030,"trace":"8d6f64ee-ad26-45a4-9efd-1080f9fca1fa","data":array())
                    '50031' => '\\ccxt\\OrderNotFound', // 400, Order is already completed
                    '50032' => '\\ccxt\\OrderNotFound', // 400, array("message":"Order does not exist","code":50032,"trace":"8d6b482d-4bf2-4e6c-aab2-9dcd22bf2481","data":array())
                    '50033' => '\\ccxt\\InvalidOrder', // 400, The order quantity should be greater than 0 and less than or equal to 10
                    // below Error codes used interchangeably for both failed postOnly and IOC orders depending on market price and order side
                    '50034' => '\\ccxt\\InvalidOrder', // 400, array("message":"The price is high and there is no matching depth","code":50034,"trace":"ebfae59a-ba69-4735-86b2-0ed7b9ca14ea","data":array())
                    '50035' => '\\ccxt\\InvalidOrder', // 400, array("message":"The price is low and there is no matching depth","code":50035,"trace":"677f01c7-8b88-4346-b097-b4226c75c90e","data":array())
                    '50036' => '\\ccxt\\ExchangeError', // 400, Cancel failed, order is not revocable status
                    '50037' => '\\ccxt\\BadRequest', // 400, The maximum length of clientOrderId cannot exceed 32
                    '50038' => '\\ccxt\\BadRequest', // 400, ClientOrderId only allows a combination of numbers and letters
                    '50039' => '\\ccxt\\BadRequest', // 400, Order_id and clientOrderId cannot be empty at the same time
                    '50040' => '\\ccxt\\BadSymbol', // 400, Symbol Not Available
                    '50041' => '\\ccxt\\ExchangeError', // 400, Out of query time range
                    '50042' => '\\ccxt\\BadRequest', // 400, clientOrderId is duplicate
                    '51000' => '\\ccxt\\BadSymbol', // 400, Currency not found
                    '51001' => '\\ccxt\\ExchangeError', // 400, Margin Account not Opened
                    '51002' => '\\ccxt\\ExchangeError', // 400, Margin Account Not Available
                    '51003' => '\\ccxt\\ExchangeError', // 400, Account Limit
                    '51004' => '\\ccxt\\InsufficientFunds', // 400, array("message":"Exceed the maximum number of borrows available.","code":51004,"trace":"4030b753-9beb-44e6-8352-1633c5edcd47","data":array())
                    '51005' => '\\ccxt\\InvalidOrder', // 400, Less than the minimum borrowable amount
                    '51006' => '\\ccxt\\InvalidOrder', // 400, Exceeds the amount to be repaid
                    '51007' => '\\ccxt\\BadRequest', // 400, order_mode not found
                    '51008' => '\\ccxt\\ExchangeError', // 400, Operation is limited, please try again later
                    '51009' => '\\ccxt\\InvalidOrder', // 400, Parameter mismatch => limit order/market order quantity should be greater than the minimum number of should buy/sell
                    '51010' => '\\ccxt\\InvalidOrder', // 400, Parameter mismatch => limit order price should be greater than the minimum buy price
                    '51011' => '\\ccxt\\InvalidOrder', // 400, array("message":"param not match : size * price >=5","code":51011,"trace":"525e1d27bfd34d60b2d90ba13a7c0aa9.74.16696421352220797","data":array())
                    '51012' => '\\ccxt\\InvalidOrder', // 400, Parameter mismatch => limit order price should be greater than the minimum buy price
                    '51013' => '\\ccxt\\InvalidOrder', // 400, Parameter mismatch => Limit order quantity * price should be greater than the minimum transaction amount
                    '51014' => '\\ccxt\\InvalidOrder', // 400, Participation mismatch => the number of market order buy orders should be greater than the minimum buyable amount
                    '51015' => '\\ccxt\\InvalidOrder', // 400, Parameter mismatch => the price of market order buy order placed is too small
                    '52000' => '\\ccxt\\BadRequest', // 400, Unsupported OrderMode Type
                    '52001' => '\\ccxt\\BadRequest', // 400, Unsupported Trade Type
                    '52002' => '\\ccxt\\BadRequest', // 400, Unsupported Side Type
                    '52003' => '\\ccxt\\BadRequest', // 400, Unsupported Query State Type
                    '52004' => '\\ccxt\\BadRequest', // 400, End time must be greater than or equal to Start time
                    '53000' => '\\ccxt\\AccountSuspended', // 403, Your account is frozen due to security policies. Please contact customer service
                    '53001' => '\\ccxt\\AccountSuspended', // 403, array("message":"Your kyc country is restricted. Please contact customer service.","code":53001,"trace":"8b445940-c123-4de9-86d7-73c5be2e7a24","data":array())
                    '53002' => '\\ccxt\\PermissionDenied', // 403, Your account has not yet completed the kyc advanced certification, please complete first
                    '53003' => '\\ccxt\\PermissionDenied', // 403 No permission, please contact the main account
                    '53005' => '\\ccxt\\PermissionDenied', // 403 Don't have permission to access the interface
                    '53006' => '\\ccxt\\PermissionDenied', // 403 Please complete your personal verification(Starter)
                    '53007' => '\\ccxt\\PermissionDenied', // 403 Please complete your personal verification(Advanced)
                    '53008' => '\\ccxt\\PermissionDenied', // 403 Services is not available in your countries and areas
                    '53009' => '\\ccxt\\PermissionDenied', // 403 Your account has not yet completed the qr code certification, please complete first
                    '53010' => '\\ccxt\\PermissionDenied', // 403 This account is restricted from borrowing
                    '57001' => '\\ccxt\\BadRequest', // 405, Method Not Allowed
                    '58001' => '\\ccxt\\BadRequest', // 415, Unsupported Media Type
                    '59001' => '\\ccxt\\ExchangeError', // 500, User account not found
                    '59002' => '\\ccxt\\ExchangeError', // 500, Internal Server Error
                    '59003' => '\\ccxt\\ExchangeError', // 500, Spot wallet call fail
                    '59004' => '\\ccxt\\ExchangeError', // 500, Margin wallet service call exception
                    '59005' => '\\ccxt\\PermissionDenied', // 500, Margin wallet service restricted
                    '59006' => '\\ccxt\\ExchangeError', // 500, Transfer fail
                    '59007' => '\\ccxt\\ExchangeError', // 500, Get symbol risk data fail
                    '59008' => '\\ccxt\\ExchangeError', // 500, Trading order failure
                    '59009' => '\\ccxt\\ExchangeError', // 500, Loan success,but trading order failure
                    '59010' => '\\ccxt\\InsufficientFunds', // 500, Insufficient loan amount.
                    '59011' => '\\ccxt\\ExchangeError', // 500, The Get Wallet Balance service call fail, please try again later
                    // contract errors
                    '40001' => '\\ccxt\\ExchangeError', // 400, Cloud account not found
                    '40002' => '\\ccxt\\ExchangeError', // 400, out_trade_no not found
                    '40003' => '\\ccxt\\ExchangeError', // 400, out_trade_no already existed
                    '40004' => '\\ccxt\\ExchangeError', // 400, Cloud account count limit
                    '40005' => '\\ccxt\\ExchangeError', // 400, Transfer vol precision error
                    '40006' => '\\ccxt\\PermissionDenied', // 400, Invalid ip error
                    '40007' => '\\ccxt\\BadRequest', // 400, Parse parameter error
                    '40008' => '\\ccxt\\InvalidNonce', // 400, Check nonce error
                    '40009' => '\\ccxt\\BadRequest', // 400, Check ver error
                    '40010' => '\\ccxt\\BadRequest', // 400, Not found func error
                    '40011' => '\\ccxt\\BadRequest', // 400, Invalid request
                    '40012' => '\\ccxt\\ExchangeError', // 500, System error
                    '40013' => '\\ccxt\\ExchangeError', // 400, Access too often" CLIENT_TIME_INVALID, "Please check your system time.
                    '40014' => '\\ccxt\\BadSymbol', // 400, This contract is offline
                    '40015' => '\\ccxt\\BadSymbol', // 400, This contract's exchange has been paused
                    '40016' => '\\ccxt\\InvalidOrder', // 400, This order would trigger user position liquidate
                    '40017' => '\\ccxt\\InvalidOrder', // 400, It is not possible to open and close simultaneously in the same position
                    '40018' => '\\ccxt\\InvalidOrder', // 400, Your position is closed
                    '40019' => '\\ccxt\\ExchangeError', // 400, Your position is in liquidation delegating
                    '40020' => '\\ccxt\\InvalidOrder', // 400, Your position volume is not enough
                    '40021' => '\\ccxt\\ExchangeError', // 400, The position is not exsit
                    '40022' => '\\ccxt\\ExchangeError', // 400, The position is not isolated
                    '40023' => '\\ccxt\\ExchangeError', // 400, The position would liquidate when sub margin
                    '40024' => '\\ccxt\\ExchangeError', // 400, The position would be warnning of liquidation when sub margin
                    '40025' => '\\ccxt\\ExchangeError', // 400, The position’s margin shouldn’t be lower than the base limit
                    '40026' => '\\ccxt\\ExchangeError', // 400, You cross margin position is in liquidation delegating
                    '40027' => '\\ccxt\\InsufficientFunds', // 400, You contract account available balance not enough
                    '40028' => '\\ccxt\\PermissionDenied', // 400, Your plan order's count is more than system maximum limit.
                    '40029' => '\\ccxt\\InvalidOrder', // 400, The order's leverage is too large.
                    '40030' => '\\ccxt\\InvalidOrder', // 400, The order's leverage is too small.
                    '40031' => '\\ccxt\\InvalidOrder', // 400, The deviation between current price and trigger price is too large.
                    '40032' => '\\ccxt\\InvalidOrder', // 400, The plan order's life cycle is too long.
                    '40033' => '\\ccxt\\InvalidOrder', // 400, The plan order's life cycle is too short.
                    '40034' => '\\ccxt\\BadSymbol', // 400, This contract is not found
                    '40035' => '\\ccxt\\OrderNotFound', // 400, The order is not exist
                    '40036' => '\\ccxt\\InvalidOrder', // 400, The order status is invalid
                    '40037' => '\\ccxt\\OrderNotFound', // 400, The order id is not exist
                    '40038' => '\\ccxt\\BadRequest', // 400, The k-line step is invalid
                    '40039' => '\\ccxt\\BadRequest', // 400, The timestamp is invalid
                    '40040' => '\\ccxt\\InvalidOrder', // 400, The order leverage is invalid
                    '40041' => '\\ccxt\\InvalidOrder', // 400, The order side is invalid
                    '40042' => '\\ccxt\\InvalidOrder', // 400, The order type is invalid
                    '40043' => '\\ccxt\\InvalidOrder', // 400, The order precision is invalid
                    '40044' => '\\ccxt\\InvalidOrder', // 400, The order range is invalid
                    '40045' => '\\ccxt\\InvalidOrder', // 400, The order open type is invalid
                    '40046' => '\\ccxt\\PermissionDenied', // 403, The account is not opened futures
                    '40047' => '\\ccxt\\PermissionDenied', // 403, Services is not available in you countries and areas
                    '40048' => '\\ccxt\\InvalidOrder', // 403, ClientOrderId only allows a combination of numbers and letters
                    '40049' => '\\ccxt\\InvalidOrder', // 403, The maximum length of clientOrderId cannot exceed 32
                    '40050' => '\\ccxt\\InvalidOrder', // 403, Client OrderId duplicated with existing orders
                ),
                'broad' => array(
                    'You contract account available balance not enough' => '\\ccxt\\InsufficientFunds',
                    'you contract account available balance not enough' => '\\ccxt\\InsufficientFunds',
                ),
            ),
            'commonCurrencies' => array(
                '$GM' => 'GOLDMINER',
                '$HERO' => 'Step Hero',
                '$PAC' => 'PAC',
                'BP' => 'BEYOND',
                'GDT' => 'Gorilla Diamond',
                'GLD' => 'Goldario',
                'MVP' => 'MVP Coin',
                'TRU' => 'Truebit', // conflict with TrueFi
            ),
            'options' => array(
                'defaultNetworks' => array(
                    'USDT' => 'TRC20',
                    'BTC' => 'BTC',
                    'ETH' => 'ERC20',
                ),
                'timeDifference' => 0, // the difference between system clock and exchange clock
                'adjustForTimeDifference' => false, // controls the adjustment logic upon instantiation
                'networks' => array(
                    'ERC20' => 'ERC20',
                    'SOL' => 'SOL',
                    'BTC' => 'BTC',
                    'TRC20' => 'TRC20',
                    // todo => should be TRX after unification
                    // 'TRC20' => array( 'TRC20', 'trc20', 'TRON' ), // todo => after unification i.e. TRON is returned from fetchDepositAddress
                    // 'ERC20' => array( 'ERC20', 'ERC-20', 'ERC20 ' ), // todo => after unification
                    'OMNI' => 'OMNI',
                    'XLM' => 'XLM',
                    'EOS' => 'EOS',
                    'NEO' => 'NEO',
                    'BTM' => 'BTM',
                    'BCH' => 'BCH',
                    'LTC' => 'LTC',
                    'BSV' => 'BSV',
                    'XRP' => 'XRP',
                    // 'VECHAIN' => array( 'VET', 'Vechain' ), // todo => after unification
                    'PLEX' => 'PLEX',
                    'XCH' => 'XCH',
                    // 'AVALANCHE_C' => array( 'AVAX', 'AVAX-C' ), // todo => after unification
                    'NEAR' => 'NEAR',
                    'FIO' => 'FIO',
                    'SCRT' => 'SCRT',
                    'IOTX' => 'IOTX',
                    'ALGO' => 'ALGO',
                    'ATOM' => 'ATOM',
                    'DOT' => 'DOT',
                    'ADA' => 'ADA',
                    'DOGE' => 'DOGE',
                    'XYM' => 'XYM',
                    'GLMR' => 'GLMR',
                    'MOVR' => 'MOVR',
                    'ZIL' => 'ZIL',
                    'INJ' => 'INJ',
                    'KSM' => 'KSM',
                    'ZEC' => 'ZEC',
                    'NAS' => 'NAS',
                    'POLYGON' => 'MATIC',
                    'HRC20' => 'HECO',
                    'XDC' => 'XDC',
                    'ONE' => 'ONE',
                    'LAT' => 'LAT',
                    'CSPR' => 'Casper',
                    'ICP' => 'Computer',
                    'XTZ' => 'XTZ',
                    'MINA' => 'MINA',
                    'BEP20' => 'BSC_BNB',
                    'THETA' => 'THETA',
                    'AKT' => 'AKT',
                    'AR' => 'AR',
                    'CELO' => 'CELO',
                    'FIL' => 'FIL',
                    'NULS' => 'NULS',
                    'ETC' => 'ETC',
                    'DASH' => 'DASH',
                    'DGB' => 'DGB',
                    'BEP2' => 'BEP2',
                    'GRIN' => 'GRIN',
                    'WAVES' => 'WAVES',
                    'ABBC' => 'ABBC',
                    'ACA' => 'ACA',
                    'QTUM' => 'QTUM',
                    'PAC' => 'PAC',
                    // 'TERRACLASSIC' => 'LUNC', // TBD
                    // 'TERRA' => 'Terra', // TBD
                    // 'HEDERA' => array( 'HBAR', 'Hedera', 'Hedera Mainnet' ), // todo => after unification
                    'TLOS' => 'TLOS',
                    'KARDIA' => 'KardiaChain',
                    'FUSE' => 'FUSE',
                    'TRC10' => 'TRC10',
                    'FIRO' => 'FIRO',
                    'FTM' => 'Fantom',
                    // 'KLAYTN' => array( 'klaytn', 'KLAY', 'Klaytn' ), // todo => after unification
                    // 'ELROND' => array( 'EGLD', 'Elrond eGold', 'MultiversX' ), // todo => after unification
                    'EVER' => 'EVER',
                    'KAVA' => 'KAVA',
                    'HYDRA' => 'HYDRA',
                    'PLCU' => 'PLCU',
                    'BRISE' => 'BRISE',
                    // 'CRC20' => array( 'CRO', 'CRO_Chain' ), // todo => after unification
                    // 'CONFLUX' => array( 'CFX eSpace', 'CFX' ), // todo => after unification
                    'OPTIMISM' => 'OPTIMISM',
                    'REEF' => 'REEF',
                    'SYS' => 'SYS', // NEVM is different
                    'VITE' => 'VITE',
                    'STX' => 'STX',
                    'SXP' => 'SXP',
                    'BITCI' => 'BITCI',
                    // 'ARBITRUM' => array( 'ARBI', 'Arbitrum' ), // todo => after unification
                    'XRD' => 'XRD',
                    'ASTR' => 'ASTAR',
                    'ZEN' => 'HORIZEN',
                    'LTO' => 'LTO',
                    'ETHW' => 'ETHW',
                    'ETHF' => 'ETHF',
                    'IOST' => 'IOST',
                    // 'CHILIZ' => array( 'CHZ', 'CHILIZ' ), // todo => after unification
                    'APT' => 'APT',
                    // 'FLOW' => array( 'FLOW', 'Flow' ), // todo => after unification
                    'ONT' => 'ONT',
                    'EVMOS' => 'EVMOS',
                    'XMR' => 'XMR',
                    'OASYS' => 'OAS',
                    'OSMO' => 'OSMO',
                    'OMAX' => 'OMAX Chain',
                    'DESO' => 'DESO',
                    'BFIC' => 'BFIC',
                    'OHO' => 'OHO',
                    'CS' => 'CS',
                    'CHEQ' => 'CHEQ',
                    'NODL' => 'NODL',
                    'NEM' => 'XEM',
                    'FRA' => 'FRA',
                    'ERGO' => 'ERG',
                    // todo => below will be uncommented after unification
                    // 'BITCOINHD' => 'BHD',
                    // 'CRUST' => 'CRU',
                    // 'MINTME' => 'MINTME',
                    // 'ZENITH' => 'ZENITH',
                    // 'ZENIQ' => 'ZENIQ', // "ZEN-20" is different
                    // 'BITCOINVAULT' => 'BTCV',
                    // 'MOBILECOIN' => 'MBX',
                    // 'PINETWORK' => 'PI',
                    // 'PI' => 'PI',
                    // 'REBUS' => 'REBUS',
                    // 'XODEX' => 'XODEX',
                    // 'ULTRONGLOW' => 'UTG'
                    // 'QIBLOCKCHAIN' => 'QIE',
                    // 'XIDEN' => 'XDEN',
                    // 'PHAETON' => 'PHAE',
                    // 'REDLIGHT' => 'REDLC',
                    // 'VERITISE' => 'VTS',
                    // 'VERIBLOCK' => 'VBK',
                    // 'RAMESTTA' => 'RAMA',
                    // 'BITICA' => 'BDCC',
                    // 'CROWNSOVEREIGN' => 'CSOV',
                    // 'DRAC' => 'DRC20',
                    // 'QCHAIN' => 'QDT',
                    // 'KINGARU' => 'KRU',
                    // 'PROOFOFMEMES' => 'POM',
                    // 'CUBE' => 'CUBE',
                    // 'CADUCEUS' => 'CMP',
                    // 'VEIL' => 'VEIL',
                    // 'ENERGYWEB' => 'EWT',
                    // 'CYPHERIUM' => 'CPH',
                    // 'LBRY' => 'LBC',
                    // 'ETHERCOIN' => 'ETE',
                    // undetermined chains:
                    // LEX (for LexThum), TAYCAN (for TRICE), SFL (probably TAYCAN), OMNIA (for APEX), NAC (for NAC), KAG (Kinesis), CEM (crypto emergency), XVM (for Venidium), NEVM (for NEVM), IGT20 (for IGNITE), FILM (FILMCredits), CC (CloudCoin), MERGE (MERGE), LTNM (Bitcoin latinum), PLUGCN ( PlugChain), DINGO (dingo), LED (LEDGIS), AVAT (AVAT), VSOL (Vsolidus), EPIC (EPIC cash), NFC (netflowcoin), mrx (Metrix Coin), Idena (idena network), PKT (PKT Cash), BondDex (BondDex), XBN (XBN), KALAM (Kalamint), REV (RChain), KRC20 (MyDeFiPet), ARC20 (Hurricane Token), GMD (Coop network), BERS (Berith), ZEBI (Zebi), BRC (Baer Chain), DAPS (DAPS Coin), APL (Gold Secured Currency), NDAU (NDAU), WICC (WICC), UPG (Unipay God), TSL (TreasureSL), MXW (Maxonrow), CLC (Cifculation), SMH (SMH Coin), XIN (CPCoin), RDD (ReddCoin), OK (Okcash), KAR (KAR), CCX (ConcealNetwork),
                ),
                'networksById' => array(
                    'ETH' => 'ERC20',
                    'Ethereum' => 'ERC20',
                    'USDT' => 'OMNI', // the default USDT network for bitmart is OMNI
                    'Bitcoin' => 'BTC',
                ),
                'defaultType' => 'spot', // 'spot', 'swap'
                'fetchBalance' => array(
                    'type' => 'spot', // 'spot', 'swap', 'account'
                ),
                'accountsByType' => array(
                    'spot' => 'spot',
                    'swap' => 'swap',
                ),
                'createMarketBuyOrderRequiresPrice' => true,
                'brokerId' => 'CCXTxBitmart000',
            ),
            'features' => array(
                'default' => array(
                    'sandbox' => false,
                    'createOrder' => array(
                        'marginMode' => true,
                        'triggerPrice' => false,
                        'triggerPriceType' => null,
                        'triggerDirection' => false,
                        'stopLossPrice' => false,
                        'takeProfitPrice' => false,
                        'attachedStopLossTakeProfit' => null,
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => false,
                            'PO' => true,
                            'GTD' => false,
                        ),
                        'hedged' => false,
                        'trailing' => false,
                        'marketBuyRequiresPrice' => false, // todo => https://developer-pro.bitmart.com/en/spot/#new-order-v2-signed
                        'marketBuyByCost' => true,
                        'leverage' => true, // todo => implement
                        'selfTradePrevention' => false,
                        'iceberg' => false,
                    ),
                    'createOrders' => array(
                        'max' => 10,
                    ),
                    'fetchMyTrades' => array(
                        'marginMode' => true,
                        'limit' => 200,
                        'daysBack' => null,
                        'untilDays' => 99999,
                        'symbolRequired' => false,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => true,
                        'limit' => 200,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOrders' => null,
                    'fetchClosedOrders' => array(
                        'marginMode' => true,
                        'limit' => 200,
                        'daysBack' => null,
                        'daysBackCanceled' => null,
                        'untilDays' => null,
                        'trigger' => false,
                        'trailing' => false,
                        'symbolRequired' => false,
                    ),
                    'fetchOHLCV' => array(
                        'limit' => 1000, // variable timespans for recent endpoint, 200 for historical
                    ),
                ),
                'forDerivatives' => array(
                    'extends' => 'default',
                    'createOrder' => array(
                        'marginMode' => true,
                        'triggerPrice' => true,
                        'triggerPriceType' => array(
                            'last' => true,
                            'mark' => true,
                            'index' => false,
                        ),
                        'triggerDirection' => true, // todo => implementation broken
                        'stopLossPrice' => true,
                        'takeProfitPrice' => true,
                        'attachedStopLossTakeProfit' => array(
                            'triggerPriceType' => array(
                                'last' => true,
                                'mark' => true,
                                'index' => false,
                            ),
                            'price' => false,
                        ),
                        'timeInForce' => array(
                            'IOC' => true,
                            'FOK' => true,
                            'PO' => true,
                            'GTD' => false,
                        ),
                        'hedged' => false,
                        'trailing' => true,
                        'marketBuyRequiresPrice' => true,
                        'marketBuyByCost' => true,
                        // exchange-supported features
                        // 'selfTradePrevention' => true,
                        // 'twap' => false,
                        // 'iceberg' => false,
                        // 'oco' => false,
                    ),
                    'fetchMyTrades' => array(
                        'marginMode' => true,
                        'limit' => null,
                        'daysBack' => null,
                        'untilDays' => 99999,
                    ),
                    'fetchOrder' => array(
                        'marginMode' => false,
                        'trigger' => false,
                        'trailing' => true,
                    ),
                    'fetchOpenOrders' => array(
                        'marginMode' => false,
                        'limit' => 100,
                        'trigger' => true,
                        'trailing' => false,
                    ),
                    'fetchClosedOrders' => array(
                        'marginMode' => true,
                        'limit' => 200,
                        'daysBack' => null,
                        'daysBackCanceled' => null,
                        'untilDays' => null,
                        'trigger' => false,
                        'trailing' => false,
                    ),
                    'fetchOHLCV' => array(
                        'limit' => 500,
                    ),
                ),
                'spot' => array(
                    'extends' => 'default',
                ),
                'swap' => array(
                    'linear' => array(
                        'extends' => 'forDerivatives',
                    ),
                    'inverse' => array(
                        'extends' => 'forDerivatives',
                    ),
                ),
                'future' => array(
                    'linear' => null,
                    'inverse' => null,
                ),
            ),
        ));
    }

    public function fetch_time($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetches the current integer timestamp in milliseconds from the exchange server
             *
             * @see https://developer-pro.bitmart.com/en/spot/#get-system-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 = Async\await($this->publicGetSystemTime ($params));
            //
            //     {
            //         "message":"OK",
            //         "code":1000,
            //         "trace":"c4e5e5b7-fe9f-4191-89f7-53f6c5bf9030",
            //         "data":{
            //             "server_time":1599843709578
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            return $this->safe_integer($data, 'server_time');
        }) ();
    }

    public function fetch_status($params = array ()) {
        return Async\async(function () use ($params) {
            /**
             * the latest known information on the availability of the exchange API
             *
             * @see https://developer-pro.bitmart.com/en/spot/#get-system-$service-$status
             *
             * @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~
             */
            $options = $this->safe_dict($this->options, 'fetchStatus', array());
            $defaultType = $this->safe_string($this->options, 'defaultType');
            $type = $this->safe_string($options, 'type', $defaultType);
            $type = $this->safe_string($params, 'type', $type);
            $params = $this->omit($params, 'type');
            $response = Async\await($this->publicGetSystemService ($params));
            //
            //     {
            //         "message" => "OK",
            //         "code" => 1000,
            //         "trace" => "1d3f28b0-763e-4f78-90c4-5e3ad19dc595",
            //         "data" => {
            //           "service" => array(
            //             array(
            //               "title" => "Spot API Stop",
            //               "service_type" => "spot",
            //               "status" => 2,
            //               "start_time" => 1648639069125,
            //               "end_time" => 1648639069125
            //             ),
            //             {
            //               "title" => "Contract API Stop",
            //               "service_type" => "contract",
            //               "status" => 2,
            //               "start_time" => 1648639069125,
            //               "end_time" => 1648639069125
            //             }
            //           )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $services = $this->safe_list($data, 'service', array());
            $servicesByType = $this->index_by($services, 'service_type');
            if ($type === 'swap') {
                $type = 'contract';
            }
            $service = $this->safe_string($servicesByType, $type);
            $status = null;
            $eta = null;
            if ($service !== null) {
                $statusCode = $this->safe_integer($service, 'status');
                if ($statusCode === 2) {
                    $status = 'ok';
                } else {
                    $status = 'maintenance';
                    $eta = $this->safe_integer($service, 'end_time');
                }
            }
            return array(
                'status' => $status,
                'updated' => null,
                'eta' => $eta,
                'url' => null,
                'info' => $response,
            );
        }) ();
    }

    public function fetch_spot_markets($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            $response = Async\await($this->publicGetSpotV1SymbolsDetails ($params));
            //
            //     {
            //         "message":"OK",
            //         "code":1000,
            //         "trace":"a67c9146-086d-4d3f-9897-5636a9bb26e1",
            //         "data":{
            //             "symbols":array(
            //               array(
            //                  "symbol" => "BTC_USDT",
            //                  "symbol_id" => 53,
            //                  "base_currency" => "BTC",
            //                  "quote_currency" => "USDT",
            //                  "base_min_size" => "0.000010000000000000000000000000",
            //                  "base_max_size" => "100000000.000000000000000000000000000000",
            //                  "price_min_precision" => -1,
            //                  "price_max_precision" => 2,
            //                  "quote_increment" => "0.00001", // Api docs says "The minimum order quantity is also the minimum order quantity increment", however I think they mistakenly use the term 'order quantity'
            //                  "expiration" => "NA",
            //                  "min_buy_amount" => "5.000000000000000000000000000000",
            //                  "min_sell_amount" => "5.000000000000000000000000000000",
            //                  "trade_status" => "trading"
            //               ),
            //             )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $symbols = $this->safe_list($data, 'symbols', array());
            $result = array();
            $fees = $this->fees['trading'];
            for ($i = 0; $i < count($symbols); $i++) {
                $market = $symbols[$i];
                $id = $this->safe_string($market, 'symbol');
                $numericId = $this->safe_integer($market, 'symbol_id');
                $baseId = $this->safe_string($market, 'base_currency');
                $quoteId = $this->safe_string($market, 'quote_currency');
                $base = $this->safe_currency_code($baseId);
                $quote = $this->safe_currency_code($quoteId);
                $symbol = $base . '/' . $quote;
                $minBuyCost = $this->safe_string($market, 'min_buy_amount');
                $minSellCost = $this->safe_string($market, 'min_sell_amount');
                $minCost = Precise::string_max($minBuyCost, $minSellCost);
                $baseMinSize = $this->safe_number($market, 'base_min_size');
                $result[] = $this->safe_market_structure(array(
                    'id' => $id,
                    'numericId' => $numericId,
                    'symbol' => $symbol,
                    'base' => $base,
                    'quote' => $quote,
                    'settle' => null,
                    'baseId' => $baseId,
                    'quoteId' => $quoteId,
                    'settleId' => null,
                    'type' => 'spot',
                    'spot' => true,
                    'margin' => false,
                    'swap' => false,
                    'future' => false,
                    'option' => false,
                    'active' => true,
                    'contract' => false,
                    'linear' => null,
                    'inverse' => null,
                    'contractSize' => null,
                    'expiry' => null,
                    'expiryDatetime' => null,
                    'strike' => null,
                    'optionType' => null,
                    'maker' => $fees['maker'],
                    'taker' => $fees['taker'],
                    'precision' => array(
                        'amount' => $baseMinSize,
                        'price' => $this->parse_number($this->parse_precision($this->safe_string($market, 'price_max_precision'))),
                    ),
                    'limits' => array(
                        'leverage' => array(
                            'min' => null,
                            'max' => null,
                        ),
                        'amount' => array(
                            'min' => $baseMinSize,
                            'max' => $this->safe_number($market, 'base_max_size'),
                        ),
                        'price' => array(
                            'min' => null,
                            'max' => null,
                        ),
                        'cost' => array(
                            'min' => $this->parse_number($minCost),
                            'max' => null,
                        ),
                    ),
                    'created' => null,
                    'info' => $market,
                ));
            }
            return $result;
        }) ();
    }

    public function fetch_contract_markets($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            $response = Async\await($this->publicGetContractPublicDetails ($params));
            //
            //     {
            //         "code" => 1000,
            //         "message" => "Ok",
            //         "data" => {
            //             "symbols" => array(
            //                 array(
            //                     "symbol" => "BTCUSDT",
            //                     "product_type" => 1,
            //                     "open_timestamp" => 1645977600000,
            //                     "expire_timestamp" => 0,
            //                     "settle_timestamp" => 0,
            //                     "base_currency" => "BTC",
            //                     "quote_currency" => "USDT",
            //                     "last_price" => "63547.4",
            //                     "volume_24h" => "110938430",
            //                     "turnover_24h" => "7004836342.6944",
            //                     "index_price" => "63587.85404255",
            //                     "index_name" => "BTCUSDT",
            //                     "contract_size" => "0.001",
            //                     "min_leverage" => "1",
            //                     "max_leverage" => "100",
            //                     "price_precision" => "0.1",
            //                     "vol_precision" => "1",
            //                     "max_volume" => "1000000",
            //                     "min_volume" => "1",
            //                     "funding_rate" => "0.0000801",
            //                     "expected_funding_rate" => "-0.0000035",
            //                     "open_interest" => "278214",
            //                     "open_interest_value" => "17555316.9355496",
            //                     "high_24h" => "64109.4",
            //                     "low_24h" => "61857.6",
            //                     "change_24h" => "0.0239264900886327",
            //                     "funding_time" => 1726819200000
            //                 ),
            //             )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $symbols = $this->safe_list($data, 'symbols', array());
            $result = array();
            $fees = $this->fees['trading'];
            for ($i = 0; $i < count($symbols); $i++) {
                $market = $symbols[$i];
                $id = $this->safe_string($market, 'symbol');
                $baseId = $this->safe_string($market, 'base_currency');
                $quoteId = $this->safe_string($market, 'quote_currency');
                $base = $this->safe_currency_code($baseId);
                $quote = $this->safe_currency_code($quoteId);
                $settleId = 'USDT'; // this is bitmart's ID for usdt
                $settle = $this->safe_currency_code($settleId);
                $symbol = $base . '/' . $quote . ':' . $settle;
                $productType = $this->safe_integer($market, 'product_type');
                $isSwap = ($productType === 1);
                $isFutures = ($productType === 2);
                $expiry = $this->safe_integer($market, 'expire_timestamp');
                if (!$isFutures && ($expiry === 0)) {
                    $expiry = null;
                }
                $result[] = $this->safe_market_structure(array(
                    'id' => $id,
                    'numericId' => null,
                    'symbol' => $symbol,
                    'base' => $base,
                    'quote' => $quote,
                    'settle' => $settle,
                    'baseId' => $baseId,
                    'quoteId' => $quoteId,
                    'settleId' => $settleId,
                    'type' => $isSwap ? 'swap' : 'future',
                    'spot' => false,
                    'margin' => false,
                    'swap' => $isSwap,
                    'future' => $isFutures,
                    'option' => false,
                    'active' => true,
                    'contract' => true,
                    'linear' => true,
                    'inverse' => false,
                    'contractSize' => $this->safe_number($market, 'contract_size'),
                    'expiry' => $expiry,
                    'expiryDatetime' => $this->iso8601($expiry),
                    'strike' => null,
                    'optionType' => null,
                    'maker' => $fees['maker'],
                    'taker' => $fees['taker'],
                    'precision' => array(
                        'amount' => $this->safe_number($market, 'vol_precision'),
                        'price' => $this->safe_number($market, 'price_precision'),
                    ),
                    'limits' => array(
                        'leverage' => array(
                            'min' => $this->safe_number($market, 'min_leverage'),
                            'max' => $this->safe_number($market, 'max_leverage'),
                        ),
                        'amount' => array(
                            'min' => $this->safe_number($market, 'min_volume'),
                            'max' => $this->safe_number($market, 'max_volume'),
                        ),
                        'price' => array(
                            'min' => null,
                            'max' => null,
                        ),
                        'cost' => array(
                            'min' => null,
                            'max' => null,
                        ),
                    ),
                    'created' => $this->safe_integer($market, 'open_timestamp'),
                    'info' => $market,
                ));
            }
            return $result;
        }) ();
    }

    public function fetch_markets($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * retrieves data on all markets for bitmart
             *
             * @see https://developer-pro.bitmart.com/en/spot/#get-trading-pair-details-v1
             * @see https://developer-pro.bitmart.com/en/futuresv2/#get-$contract-details
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} an array of objects representing market data
             */
            if ($this->options['adjustForTimeDifference']) {
                Async\await($this->load_time_difference());
            }
            $spot = Async\await($this->fetch_spot_markets($params));
            $contract = Async\await($this->fetch_contract_markets($params));
            return $this->array_concat($spot, $contract);
        }) ();
    }

    public function fetch_currencies($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetches all available $currencies on an exchange
             *
             * @see https://developer-pro.bitmart.com/en/spot/#get-$currency-list-v1
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an associative dictionary of $currencies
             */
            $response = Async\await($this->publicGetAccountV1Currencies ($params));
            //
            //     {
            //         "message" => "OK",
            //         "code" => 1000,
            //         "trace" => "619294ecef584282b26a3be322b1e01f.66.17403093228242229",
            //         "data" => {
            //             "currencies" => array(
            //                 {
            //                     "currency" => "BTC",
            //                     "name" => "Bitcoin",
            //                     "recharge_minsize" => '0.00000001',
            //                     "contract_address" => null,
            //                     "network" => "BTC",
            //                     "withdraw_enabled" => true,
            //                     "deposit_enabled" => true,
            //                     "withdraw_minsize" => "0.0003",
            //                     "withdraw_minfee" => "9.61",
            //                     "withdraw_fee_estimate" => "9.61",
            //                     "withdraw_fee" => "0.0001"
            //                 }
            //             )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $currencies = $this->safe_list($data, 'currencies', array());
            $result = array();
            for ($i = 0; $i < count($currencies); $i++) {
                $currency = $currencies[$i];
                $fullId = $this->safe_string($currency, 'currency');
                $currencyId = $fullId;
                $networkId = $this->safe_string($currency, 'network');
                $isNtf = (mb_strpos($fullId, 'NFT') !== false);
                if (!$isNtf) {
                    $parts = explode('-', $fullId);
                    $currencyId = $this->safe_string($parts, 0);
                    $second = $this->safe_string($parts, 1);
                    if ($second !== null) {
                        $networkId = strtoupper($second);
                    }
                }
                $currencyCode = $this->safe_currency_code($currencyId);
                $entry = $this->safe_dict($result, $currencyCode);
                if ($entry === null) {
                    $entry = array(
                        'info' => $currency,
                        'id' => $currencyId,
                        'code' => $currencyCode,
                        'precision' => null,
                        'name' => $this->safe_string($currency, 'name'),
                        'deposit' => null,
                        'withdraw' => null,
                        'active' => null,
                        'networks' => array(),
                        'type' => $isNtf ? 'other' : 'crypto',
                    );
                }
                $networkCode = $this->network_id_to_code($networkId);
                $withdraw = $this->safe_bool($currency, 'withdraw_enabled');
                $deposit = $this->safe_bool($currency, 'deposit_enabled');
                $entry['networks'][$networkCode] = array(
                    'info' => $currency,
                    'id' => $networkId,
                    'code' => $networkCode,
                    'withdraw' => $withdraw,
                    'deposit' => $deposit,
                    'active' => $withdraw && $deposit,
                    'fee' => $this->safe_number($currency, 'withdraw_fee'),
                    'limits' => array(
                        'withdraw' => array(
                            'min' => $this->safe_number($currency, 'withdraw_minsize'),
                            'max' => null,
                        ),
                        'deposit' => array(
                            'min' => null,
                            'max' => null,
                        ),
                    ),
                );
                $result[$currencyCode] = $entry;
            }
            $keys = is_array($result) ? array_keys($result) : array();
            for ($i = 0; $i < count($keys); $i++) {
                $key = $keys[$i];
                $currency = $result[$key];
                $result[$key] = $this->safe_currency_structure($currency);
            }
            return $result;
        }) ();
    }

    public function get_currency_id_from_code_and_network(?string $currencyCode, ?string $networkCode): ?string {
        if ($networkCode === null) {
            $networkCode = $this->default_network_code($currencyCode); // use default $network code if not provided
        }
        $currency = $this->currency($currencyCode);
        $id = $currency['id'];
        $idFromNetwork = null;
        $networks = $this->safe_dict($currency, 'networks', array());
        $networkInfo = array();
        if ($networkCode === null) {
            // $network code is not provided and there is no default $network code
            $network = $this->safe_dict($networks, $currencyCode); // trying to find $network that has the same code
            if ($network === null) {
                // use the first $network in the $networks list if there is no $network code with the same code
                $keys = is_array($networks) ? array_keys($networks) : array();
                $length = count($keys);
                if ($length > 0) {
                    $network = $this->safe_value($networks, $keys[0]);
                }
            }
            $networkInfo = $this->safe_dict($network, 'info', array());
            $idFromNetwork = $this->safe_string($networkInfo, 'currency'); // use $currency name from $network
        } else {
            $providedOrDefaultNetwork = $this->safe_dict($networks, $networkCode);
            if ($providedOrDefaultNetwork !== null) {
                $networkInfo = $this->safe_dict($providedOrDefaultNetwork, 'info', array());
                $idFromNetwork = $this->safe_string($networkInfo, 'currency'); // use $currency name from $network
            } else {
                $id .= '-' . $this->network_code_to_id($networkCode, $currencyCode); // use concatenated $currency $id and $network code if $network is not found
            }
        }
        return ($idFromNetwork !== null) ? $idFromNetwork : $id;
    }

    public function fetch_transaction_fee(string $code, $params = array ()) {
        return Async\async(function () use ($code, $params) {
            /**
             * @deprecated
             * please use fetchDepositWithdrawFee instead
             * @param {string} $code unified $currency $code
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->network] the $network $code of the $currency
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=fee-structure fee structure~
             */
            Async\await($this->load_markets());
            $currency = $this->currency($code);
            $network = null;
            list($network, $params) = $this->handle_network_code_and_params($params);
            $request = array(
                'currency' => $this->get_currency_id_from_code_and_network($currency['code'], $network),
            );
            $response = Async\await($this->privateGetAccountV1WithdrawCharge ($this->extend($request, $params)));
            //
            //     {
            //         "message" => "OK",
            //         "code" => "1000",
            //         "trace" => "3ecc0adf-91bd-4de7-aca1-886c1122f54f",
            //         "data" => {
            //             "today_available_withdraw_BTC" => "100.0000",
            //             "min_withdraw" => "0.005",
            //             "withdraw_precision" => "8",
            //             "withdraw_fee" => "0.000500000000000000000000000000"
            //         }
            //     }
            //
            $data = $response['data'];
            $withdrawFees = array();
            $withdrawFees[$code] = $this->safe_number($data, 'withdraw_fee');
            return array(
                'info' => $response,
                'withdraw' => $withdrawFees,
                'deposit' => array(),
            );
        }) ();
    }

    public function parse_deposit_withdraw_fee($fee, ?array $currency = null) {
        //
        //    {
        //        "today_available_withdraw_BTC" => "100.0000",
        //        "min_withdraw" => "0.005",
        //        "withdraw_precision" => "8",
        //        "withdraw_fee" => "0.000500000000000000000000000000"
        //    }
        //
        return array(
            'info' => $fee,
            'withdraw' => array(
                'fee' => $this->safe_number($fee, 'withdraw_fee'),
                'percentage' => null,
            ),
            'deposit' => array(
                'fee' => null,
                'percentage' => null,
            ),
            'networks' => array(),
        );
    }

    public function fetch_deposit_withdraw_fee(string $code, $params = array ()) {
        return Async\async(function () use ($code, $params) {
            /**
             * fetch the fee for deposits and withdrawals
             *
             * @see https://developer-pro.bitmart.com/en/spot/#withdraw-quota-keyed
             *
             * @param {string} $code unified currency $code
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->network] the $network $code of the currency
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=fee-structure fee structure~
             */
            Async\await($this->load_markets());
            $network = null;
            list($network, $params) = $this->handle_network_code_and_params($params);
            $request = array(
                'currency' => $this->get_currency_id_from_code_and_network($code, $network),
            );
            $response = Async\await($this->privateGetAccountV1WithdrawCharge ($this->extend($request, $params)));
            //
            //     {
            //         "message" => "OK",
            //         "code" => "1000",
            //         "trace" => "3ecc0adf-91bd-4de7-aca1-886c1122f54f",
            //         "data" => {
            //             "today_available_withdraw_BTC" => "100.0000",
            //             "min_withdraw" => "0.005",
            //             "withdraw_precision" => "8",
            //             "withdraw_fee" => "0.000500000000000000000000000000"
            //         }
            //     }
            //
            $data = $response['data'];
            return $this->parse_deposit_withdraw_fee($data);
        }) ();
    }

    public function parse_ticker(array $ticker, ?array $market = null): array {
        //
        // spot (REST) fetchTickers
        //
        //     {
        //         'result' => array(
        //             "AFIN_USDT",     // $symbol
        //             "0.001047",      // $last
        //             "11110",         // v_24h
        //             "11.632170",     // qv_24h
        //             "0.001048",      // open_24h
        //             "0.001048",      // high_24h
        //             "0.001047",      // low_24h
        //             "-0.00095",      // price_change_24h
        //             "0.001029",      // bid_px
        //             "5555",          // bid_sz
        //             "0.001041",      // ask_px
        //             "5297",          // ask_sz
        //             "1717122550482"  // $timestamp
        //         )
        //     }
        //
        // spot (REST) fetchTicker
        //
        //     {
        //         "symbol" => "BTC_USDT",
        //         "last" => "68500.00",
        //         "v_24h" => "10491.65490",
        //         "qv_24h" => "717178990.42",
        //         "open_24h" => "68149.75",
        //         "high_24h" => "69499.99",
        //         "low_24h" => "67132.40",
        //         "fluctuation" => "0.00514",
        //         "bid_px" => "68500",
        //         "bid_sz" => "0.00162",
        //         "ask_px" => "68500.01",
        //         "ask_sz" => "0.01722",
        //         "ts" => "1717131391671"
        //     }
        //
        // spot (WS)
        //
        //      {
        //          "symbol":"BTC_USDT",
        //          "last_price":"146.24",
        //          "open_24h":"147.17",
        //          "high_24h":"147.48",
        //          "low_24h":"143.88",
        //          "base_volume_24h":"117387.58", // NOT base, but quote currency!!!
        //          "s_t" => 1610936002
        //      }
        //
        // swap
        //
        //     {
        //         "symbol" => "BTCUSDT",
        //         "product_type" => 1,
        //         "open_timestamp" => 1645977600000,
        //         "expire_timestamp" => 0,
        //         "settle_timestamp" => 0,
        //         "base_currency" => "BTC",
        //         "quote_currency" => "USDT",
        //         "last_price" => "63547.4",
        //         "volume_24h" => "110938430",
        //         "turnover_24h" => "7004836342.6944",
        //         "index_price" => "63587.85404255",
        //         "index_name" => "BTCUSDT",
        //         "contract_size" => "0.001",
        //         "min_leverage" => "1",
        //         "max_leverage" => "100",
        //         "price_precision" => "0.1",
        //         "vol_precision" => "1",
        //         "max_volume" => "1000000",
        //         "min_volume" => "1",
        //         "funding_rate" => "0.0000801",
        //         "expected_funding_rate" => "-0.0000035",
        //         "open_interest" => "278214",
        //         "open_interest_value" => "17555316.9355496",
        //         "high_24h" => "64109.4",
        //         "low_24h" => "61857.6",
        //         "change_24h" => "0.0239264900886327",
        //         "funding_time" => 1726819200000
        //     }
        //
        $result = $this->safe_list($ticker, 'result', array());
        $average = $this->safe_string_2($ticker, 'avg_price', 'index_price');
        $marketId = $this->safe_string_2($ticker, 'symbol', 'contract_symbol');
        $timestamp = $this->safe_integer_2($ticker, 'timestamp', 'ts');
        $last = $this->safe_string_2($ticker, 'last_price', 'last');
        $percentage = $this->safe_string_2($ticker, 'price_change_percent_24h', 'change_24h');
        $change = $this->safe_string($ticker, 'fluctuation');
        $high = $this->safe_string_2($ticker, 'high_24h', 'high_price');
        $low = $this->safe_string_2($ticker, 'low_24h', 'low_price');
        $bid = $this->safe_string_2($ticker, 'best_bid', 'bid_px');
        $bidVolume = $this->safe_string_2($ticker, 'best_bid_size', 'bid_sz');
        $ask = $this->safe_string_2($ticker, 'best_ask', 'ask_px');
        $askVolume = $this->safe_string_2($ticker, 'best_ask_size', 'ask_sz');
        $open = $this->safe_string($ticker, 'open_24h');
        $baseVolume = $this->safe_string_n($ticker, array( 'base_volume_24h', 'v_24h', 'volume_24h' ));
        $quoteVolume = $this->safe_string_lower_n($ticker, array( 'quote_volume_24h', 'qv_24h', 'turnover_24h' ));
        $listMarketId = $this->safe_string($result, 0);
        if ($listMarketId !== null) {
            $marketId = $listMarketId;
            $timestamp = $this->safe_integer($result, 12);
            $high = $this->safe_string($result, 5);
            $low = $this->safe_string($result, 6);
            $bid = $this->safe_string($result, 8);
            $bidVolume = $this->safe_string($result, 9);
            $ask = $this->safe_string($result, 10);
            $askVolume = $this->safe_string($result, 11);
            $open = $this->safe_string($result, 4);
            $last = $this->safe_string($result, 1);
            $change = $this->safe_string($result, 7);
            $baseVolume = $this->safe_string($result, 2);
            $quoteVolume = $this->safe_string_lower($result, 3);
        }
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        if ($timestamp === null) {
            // $ticker from WS has a different field (in seconds)
            $timestamp = $this->safe_integer_product($ticker, 's_t', 1000);
        }
        if ($percentage === null) {
            $percentage = Precise::string_mul($change, '100');
        }
        if ($quoteVolume === null) {
            if ($baseVolume === null) {
                // this is swap
                $quoteVolume = $this->safe_string($ticker, 'volume_24h', $quoteVolume);
            } else {
                // this is a $ticker from websockets
                // contrary to name and documentation, base_volume_24h is actually the quote volume
                $quoteVolume = $baseVolume;
                $baseVolume = null;
            }
        }
        return $this->safe_ticker(array(
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'high' => $high,
            'low' => $low,
            'bid' => $bid,
            'bidVolume' => $bidVolume,
            'ask' => $ask,
            'askVolume' => $askVolume,
            'vwap' => null,
            'open' => $open,
            'close' => $last,
            'last' => $last,
            'previousClose' => null,
            'change' => null,
            'percentage' => $percentage,
            'average' => $average,
            'baseVolume' => $baseVolume,
            'quoteVolume' => $quoteVolume,
            'indexPrice' => $this->safe_string($ticker, 'index_price'),
            'info' => $ticker,
        ), $market);
    }

    public function fetch_ticker(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetches a price $ticker, a statistical calculation with the information calculated over the past 24 hours for a specific $market
             *
             * @see https://developer-pro.bitmart.com/en/spot/#get-$ticker-of-a-trading-pair-v3
             * @see https://developer-pro.bitmart.com/en/futuresv2/#get-contract-details
             *
             * @param {string} $symbol unified $symbol of the $market to fetch the $ticker for
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=$ticker-structure $ticker structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array();
            $response = null;
            if ($market['swap']) {
                $request['symbol'] = $market['id'];
                $response = Async\await($this->publicGetContractPublicDetails ($this->extend($request, $params)));
                //
                //     {
                //         "code" => 1000,
                //         "message" => "Ok",
                //         "data" => {
                //             "symbols" => array(
                //                 array(
                //                     "symbol" => "BTCUSDT",
                //                     "product_type" => 1,
                //                     "open_timestamp" => 1645977600000,
                //                     "expire_timestamp" => 0,
                //                     "settle_timestamp" => 0,
                //                     "base_currency" => "BTC",
                //                     "quote_currency" => "USDT",
                //                     "last_price" => "63547.4",
                //                     "volume_24h" => "110938430",
                //                     "turnover_24h" => "7004836342.6944",
                //                     "index_price" => "63587.85404255",
                //                     "index_name" => "BTCUSDT",
                //                     "contract_size" => "0.001",
                //                     "min_leverage" => "1",
                //                     "max_leverage" => "100",
                //                     "price_precision" => "0.1",
                //                     "vol_precision" => "1",
                //                     "max_volume" => "1000000",
                //                     "min_volume" => "1",
                //                     "funding_rate" => "0.0000801",
                //                     "expected_funding_rate" => "-0.0000035",
                //                     "open_interest" => "278214",
                //                     "open_interest_value" => "17555316.9355496",
                //                     "high_24h" => "64109.4",
                //                     "low_24h" => "61857.6",
                //                     "change_24h" => "0.0239264900886327",
                //                     "funding_time" => 1726819200000
                //                 ),
                //             )
                //         }
                //     }
                //
            } elseif ($market['spot']) {
                $request['symbol'] = $market['id'];
                $response = Async\await($this->publicGetSpotQuotationV3Ticker ($this->extend($request, $params)));
                //
                //     {
                //         "code" => 1000,
                //         "trace" => "f2194c2c202d2.99.1717535",
                //         "message" => "success",
                //         "data" => {
                //             "symbol" => "BTC_USDT",
                //             "last" => "68500.00",
                //             "v_24h" => "10491.65490",
                //             "qv_24h" => "717178990.42",
                //             "open_24h" => "68149.75",
                //             "high_24h" => "69499.99",
                //             "low_24h" => "67132.40",
                //             "fluctuation" => "0.00514",
                //             "bid_px" => "68500",
                //             "bid_sz" => "0.00162",
                //             "ask_px" => "68500.01",
                //             "ask_sz" => "0.01722",
                //             "ts" => "1717131391671"
                //         }
                //     }
                //
            } else {
                throw new NotSupported($this->id . ' fetchTicker() does not support ' . $market['type'] . ' markets, only spot and swap markets are accepted');
            }
            // fails in naming for contract $tickers 'contract_symbol'
            $tickers = array();
            $ticker = array();
            if ($market['spot']) {
                $ticker = $this->safe_dict($response, 'data', array());
            } else {
                $data = $this->safe_dict($response, 'data', array());
                $tickers = $this->safe_list($data, 'symbols', array());
                $ticker = $this->safe_dict($tickers, 0, array());
            }
            return $this->parse_ticker($ticker, $market);
        }) ();
    }

    public function fetch_tickers(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetches price $tickers for multiple markets, statistical information calculated over the past 24 hours for each $market
             *
             * @see https://developer-pro.bitmart.com/en/spot/#get-$ticker-of-all-pairs-v3
             * @see https://developer-pro.bitmart.com/en/futuresv2/#get-contract-details
             *
             * @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~
             */
            Async\await($this->load_markets());
            $symbols = $this->market_symbols($symbols);
            $type = null;
            $market = null;
            if ($symbols !== null) {
                $symbol = $this->safe_string($symbols, 0);
                $market = $this->market($symbol);
            }
            list($type, $params) = $this->handle_market_type_and_params('fetchTickers', $market, $params);
            $response = null;
            if ($type === 'spot') {
                $response = Async\await($this->publicGetSpotQuotationV3Tickers ($params));
                //
                //     {
                //         "code" => 1000,
                //         "trace" => "17c5e5d9ac49f9b71efca2bed55f1a.105.171225637482393",
                //         "message" => "success",
                //         "data" => array(
                //             array(
                //                 "AFIN_USDT",
                //                 "0.001047",
                //                 "11110",
                //                 "11.632170",
                //                 "0.001048",
                //                 "0.001048",
                //                 "0.001047",
                //                 "-0.00095",
                //                 "0.001029",
                //                 "5555",
                //                 "0.001041",
                //                 "5297",
                //                 "1717122550482"
                //             ),
                //         )
                //     }
                //
            } elseif ($type === 'swap') {
                $response = Async\await($this->publicGetContractPublicDetails ($params));
                //
                //     {
                //         "code" => 1000,
                //         "message" => "Ok",
                //         "data" => {
                //             "symbols" => array(
                //                 array(
                //                     "symbol" => "BTCUSDT",
                //                     "product_type" => 1,
                //                     "open_timestamp" => 1645977600000,
                //                     "expire_timestamp" => 0,
                //                     "settle_timestamp" => 0,
                //                     "base_currency" => "BTC",
                //                     "quote_currency" => "USDT",
                //                     "last_price" => "63547.4",
                //                     "volume_24h" => "110938430",
                //                     "turnover_24h" => "7004836342.6944",
                //                     "index_price" => "63587.85404255",
                //                     "index_name" => "BTCUSDT",
                //                     "contract_size" => "0.001",
                //                     "min_leverage" => "1",
                //                     "max_leverage" => "100",
                //                     "price_precision" => "0.1",
                //                     "vol_precision" => "1",
                //                     "max_volume" => "1000000",
                //                     "min_volume" => "1",
                //                     "funding_rate" => "0.0000801",
                //                     "expected_funding_rate" => "-0.0000035",
                //                     "open_interest" => "278214",
                //                     "open_interest_value" => "17555316.9355496",
                //                     "high_24h" => "64109.4",
                //                     "low_24h" => "61857.6",
                //                     "change_24h" => "0.0239264900886327",
                //                     "funding_time" => 1726819200000
                //                 ),
                //             )
                //         }
                //     }
                //
            } else {
                throw new NotSupported($this->id . ' fetchTickers() does not support ' . $type . ' markets, only spot and swap markets are accepted');
            }
            $tickers = array();
            if ($type === 'spot') {
                $tickers = $this->safe_list($response, 'data', array());
            } else {
                $data = $this->safe_dict($response, 'data', array());
                $tickers = $this->safe_list($data, 'symbols', array());
            }
            $result = array();
            for ($i = 0; $i < count($tickers); $i++) {
                $ticker = array();
                if ($type === 'spot') {
                    $ticker = $this->parse_ticker(array( 'result' => $tickers[$i] ));
                } else {
                    $ticker = $this->parse_ticker($tickers[$i]);
                }
                $symbol = $ticker['symbol'];
                $result[$symbol] = $ticker;
            }
            return $this->filter_by_array_tickers($result, 'symbol', $symbols);
        }) ();
    }

    public function fetch_order_book(string $symbol, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $limit, $params) {
            /**
             * fetches information on open orders with bid (buy) and ask (sell) prices, volumes and other $data
             *
             * @see https://developer-pro.bitmart.com/en/spot/#get-depth-v3
             * @see https://developer-pro.bitmart.com/en/futuresv2/#get-$market-depth
             *
             * @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
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'symbol' => $market['id'],
            );
            $response = null;
            if ($market['spot']) {
                if ($limit !== null) {
                    $request['limit'] = $limit; // default 35, max 50
                }
                $response = Async\await($this->publicGetSpotQuotationV3Books ($this->extend($request, $params)));
            } elseif ($market['swap']) {
                $response = Async\await($this->publicGetContractPublicDepth ($this->extend($request, $params)));
            } else {
                throw new NotSupported($this->id . ' fetchOrderBook() does not support ' . $market['type'] . ' markets, only spot and swap markets are accepted');
            }
            //
            // spot
            //
            //     {
            //         "code" => 1000,
            //         "message" => "success",
            //         "data" => array(
            //             "ts" => "1695264191808",
            //             "symbol" => "BTC_USDT",
            //             "asks" => [
            //                 ["26942.57","0.06492"],
            //                 ["26942.73","0.05447"],
            //                 ["26943.00","0.07154"]
            //             ],
            //             "bids" => [
            //                 ["26942.45","0.00074"],
            //                 ["26941.53","0.00371"],
            //                 ["26940.94","0.08992"]
            //             ]
            //         ),
            //         "trace" => "430a7f69581d4258a8e4b424dfb10782.73.16952341919017619"
            //     }
            //
            // swap
            //
            //     {
            //         "code" => 1000,
            //         "message" => "Ok",
            //         "data" => array(
            //             "asks" => [
            //                 ["26938.3","3499","3499"],
            //                 ["26938.5","14702","18201"],
            //                 ["26938.6","20457","38658"]
            //             ],
            //             "bids" => [
            //                 ["26938.2","20","20"],
            //                 ["26937.9","1913","1933"],
            //                 ["26937.8","2588","4521"]
            //             ],
            //             "timestamp" => 1695264383999,
            //             "symbol" => "BTCUSDT"
            //         ),
            //         "trace" => "4cad855074664097ac6ba5258c47305d.72.16952643834721135"
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $timestamp = $this->safe_integer_2($data, 'ts', 'timestamp');
            return $this->parse_order_book($data, $market['symbol'], $timestamp);
        }) ();
    }

    public function parse_trade(array $trade, ?array $market = null): array {
        //
        // public fetchTrades spot ( $amount = count * price )
        //
        //     array(
        //         "BTC_USDT",      // symbol
        //         "1717212457302", // $timestamp
        //         "67643.11",      // price
        //         "0.00106",       // size
        //         "sell"           // $side
        //     )
        //
        // spot => fetchMyTrades
        //
        //    {
        //        "tradeId":"182342999769370687",
        //        "orderId":"183270218784142990",
        //        "clientOrderId":"183270218784142990",
        //        "symbol":"ADA_USDT",
        //        "side":"buy",
        //        "orderMode":"spot",
        //        "type":"market",
        //        "price":"0.245948",
        //        "size":"20.71",
        //        "notional":"5.09358308",
        //        "fee":"0.00509358",
        //        "feeCoinName":"USDT",
        //        "tradeRole":"taker",
        //        "createTime":1695658457836,
        //    }
        //
        // swap => fetchMyTrades
        //
        //    {
        //        "order_id" => "230930336848609",
        //        "trade_id" => "6212604014",
        //        "symbol" => "BTCUSDT",
        //        "side" => 3,
        //        "price" => "26910.4",
        //        "vol" => "1",
        //        "exec_type" => "Taker",
        //        "profit" => false,
        //        "create_time" => 1695961596692,
        //        "realised_profit" => "-0.0003",
        //        "paid_fees" => "0.01614624"
        //    }
        //
        // ws swap
        //
        //    {
        //        'fee' => '-0.000044502',
        //        'feeCcy' => 'USDT',
        //        'fillPrice' => '74.17',
        //        'fillQty' => '1',
        //        'lastTradeID' => 6802340762
        //    }
        //
        $timestamp = $this->safe_integer_n($trade, array( 'createTime', 'create_time', 1 ));
        $isPublic = $this->safe_string($trade, 0);
        $isPublicTrade = ($isPublic !== null);
        $amount = null;
        $cost = null;
        $type = null;
        $side = null;
        if ($isPublicTrade) {
            $amount = $this->safe_string_2($trade, 'count', 3);
            $cost = $this->safe_string($trade, 'amount');
            $side = $this->safe_string_2($trade, 'type', 4);
        } else {
            $amount = $this->safe_string_n($trade, array( 'size', 'vol', 'fillQty' ));
            $cost = $this->safe_string($trade, 'notional');
            $type = $this->safe_string($trade, 'type');
            $side = $this->parse_order_side($this->safe_string($trade, 'side'));
        }
        $marketId = $this->safe_string_2($trade, 'symbol', 0);
        $market = $this->safe_market($marketId, $market);
        $feeCostString = $this->safe_string_2($trade, 'fee', 'paid_fees');
        $fee = null;
        if ($feeCostString !== null) {
            $feeCurrencyId = $this->safe_string($trade, 'feeCoinName');
            $feeCurrencyCode = $this->safe_currency_code($feeCurrencyId);
            if ($feeCurrencyCode === null) {
                $feeCurrencyCode = ($side === 'buy') ? $market['base'] : $market['quote'];
            }
            $fee = array(
                'cost' => $feeCostString,
                'currency' => $feeCurrencyCode,
            );
        }
        return $this->safe_trade(array(
            'info' => $trade,
            'id' => $this->safe_string_n($trade, array( 'tradeId', 'trade_id', 'lastTradeID' )),
            'order' => $this->safe_string_2($trade, 'orderId', 'order_id'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'symbol' => $market['symbol'],
            'type' => $type,
            'side' => $side,
            'price' => $this->safe_string_n($trade, array( 'price', 'fillPrice', 2 )),
            'amount' => $amount,
            'cost' => $cost,
            'takerOrMaker' => $this->safe_string_lower_2($trade, 'tradeRole', 'exec_type'),
            'fee' => $fee,
        ), $market);
    }

    public function fetch_trades(string $symbol, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * get a list of the most recent trades for a particular $symbol
             *
             * @see https://developer-pro.bitmart.com/en/spot/#get-recent-trades-v3
             *
             * @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~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            if (!$market['spot']) {
                throw new NotSupported($this->id . ' fetchTrades() does not support ' . $market['type'] . ' orders, only spot orders are accepted');
            }
            $request = array(
                'symbol' => $market['id'],
            );
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $response = Async\await($this->publicGetSpotQuotationV3Trades ($this->extend($request, $params)));
            //
            //     {
            //         "code" => 1000,
            //         "trace" => "58031f9a5bd.111.17117",
            //         "message" => "success",
            //         "data" => array(
            //             array(
            //                 "BTC_USDT",
            //                 "1717212457302",
            //                 "67643.11",
            //                 "0.00106",
            //                 "sell"
            //             ),
            //         )
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_trades($data, $market, $since, $limit);
        }) ();
    }

    public function parse_ohlcv($ohlcv, ?array $market = null): array {
        //
        // spot
        //    array(
        //        "1699512060", // timestamp
        //        "36746.49", // open
        //        "36758.71", // high
        //        "36736.13", // low
        //        "36755.99", // close
        //        "2.83965", // base volume
        //        "104353.57" // quote volume
        //    )
        //
        // swap
        //    {
        //        "low_price" => "20090.3",
        //        "high_price" => "20095.5",
        //        "open_price" => "20092.6",
        //        "close_price" => "20091.4",
        //        "volume" => "8748",
        //        "timestamp" => 1665002281
        //    }
        //
        // ws
        //    array(
        //        1631056350, // timestamp
        //        "46532.83", // open
        //        "46555.71", // high
        //        "46511.41", // low
        //        "46555.71", // close
        //        "0.25", // volume
        //    )
        //
        // ws swap
        //    {
        //        "symbol":"BTCUSDT",
        //        "o":"146.24",
        //        "h":"146.24",
        //        "l":"146.24",
        //        "c":"146.24",
        //        "v":"146"
        //    }
        //
        if (gettype($ohlcv) === 'array' && array_keys($ohlcv) === array_keys(array_keys($ohlcv))) {
            return array(
                $this->safe_timestamp($ohlcv, 0),
                $this->safe_number($ohlcv, 1),
                $this->safe_number($ohlcv, 2),
                $this->safe_number($ohlcv, 3),
                $this->safe_number($ohlcv, 4),
                $this->safe_number($ohlcv, 5),
            );
        } else {
            return array(
                $this->safe_timestamp_2($ohlcv, 'timestamp', 'ts'),
                $this->safe_number_2($ohlcv, 'open_price', 'o'),
                $this->safe_number_2($ohlcv, 'high_price', 'h'),
                $this->safe_number_2($ohlcv, 'low_price', 'l'),
                $this->safe_number_2($ohlcv, 'close_price', 'c'),
                $this->safe_number_2($ohlcv, 'volume', 'v'),
            );
        }
    }

    public function fetch_ohlcv(string $symbol, $timeframe = '1m', ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $timeframe, $since, $limit, $params) {
            /**
             * fetches historical candlestick data containing the open, high, low, and close $price, and the volume of a $market
             *
             * @see https://developer-pro.bitmart.com/en/spot/#get-history-k-line-v3
             * @see https://developer-pro.bitmart.com/en/futuresv2/#get-k-line
             *
             * @param {string} $symbol unified $symbol of the $market to fetch OHLCV data for
             * @param {string} $timeframe the length of time each candle represents
             * @param {int} [$since] timestamp in ms of the earliest candle to fetch
             * @param {int} [$limit] the maximum amount of candles to fetch
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] timestamp of the latest candle in ms
             * @param {boolean} [$params->paginate] *spot only* 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 {int[][]} A list of candles ordered, open, high, low, close, volume
             */
            Async\await($this->load_markets());
            $paginate = false;
            list($paginate, $params) = $this->handle_option_and_params($params, 'fetchOHLCV', 'paginate', false);
            if ($paginate) {
                return Async\await($this->fetch_paginated_call_deterministic('fetchOHLCV', $symbol, $since, $limit, $timeframe, $params, 200));
            }
            $market = $this->market($symbol);
            $duration = $this->parse_timeframe($timeframe);
            $parsedTimeframe = $this->safe_integer($this->timeframes, $timeframe);
            $request = array(
                'symbol' => $market['id'],
            );
            if ($parsedTimeframe !== null) {
                $request['step'] = $parsedTimeframe;
            } else {
                $request['step'] = $timeframe;
            }
            if ($market['spot']) {
                list($request, $params) = $this->handle_until_option('before', $request, $params, 0.001);
                if ($limit !== null) {
                    $request['limit'] = $limit;
                }
                if ($since !== null) {
                    $request['after'] = $this->parse_to_int(($since / 1000)) - 1;
                }
            } else {
                $maxLimit = 500;
                if ($limit === null) {
                    $limit = $maxLimit;
                }
                $limit = min ($maxLimit, $limit);
                $now = $this->parse_to_int($this->milliseconds() / 1000);
                if ($since === null) {
                    $start = $now - $limit * $duration;
                    $request['start_time'] = $start;
                    $request['end_time'] = $now;
                } else {
                    $start = $this->parse_to_int(($since / 1000)) - 1;
                    $end = $this->sum($start, $limit * $duration);
                    $request['start_time'] = $start;
                    $request['end_time'] = min ($end, $now);
                }
                list($request, $params) = $this->handle_until_option('end_time', $request, $params, 0.001);
            }
            $response = null;
            if ($market['swap']) {
                $price = $this->safe_string($params, 'price');
                if ($price === 'mark') {
                    $params = $this->omit($params, 'price');
                    $response = Async\await($this->publicGetContractPublicMarkpriceKline ($this->extend($request, $params)));
                } else {
                    $response = Async\await($this->publicGetContractPublicKline ($this->extend($request, $params)));
                }
            } else {
                $response = Async\await($this->publicGetSpotQuotationV3Klines ($this->extend($request, $params)));
            }
            //
            // spot
            //
            //     {
            //         "code" => 1000,
            //         "message" => "success",
            //         "data" => [
            //             ["1699512060","36746.49","36758.71","36736.13","36755.99","2.83965","104353.57"],
            //             ["1699512120","36756.00","36758.70","36737.14","36737.63","1.96070","72047.10"],
            //             ["1699512180","36737.63","36740.45","36737.62","36740.44","0.63194","23217.62"]
            //         ],
            //         "trace" => "6591fc7b508845359d5fa442e3b3a4fb.72.16995122398750695"
            //     }
            //
            // swap
            //
            //     {
            //         "code" => 1000,
            //         "message" => "Ok",
            //         "data" => array(
            //             array(
            //                 "low_price" => "20090.3",
            //                 "high_price" => "20095.5",
            //                 "open_price" => "20092.6",
            //                 "close_price" => "20091.4",
            //                 "volume" => "8748",
            //                 "timestamp" => 1665002281
            //             ),
            //             ...
            //         ),
            //         "trace" => "96c989db-e0f5-46f5-bba6-60cfcbde699b"
            //     }
            //
            $ohlcv = $this->safe_list($response, 'data', array());
            return $this->parse_ohlcvs($ohlcv, $market, $timeframe, $since, $limit);
        }) ();
    }

    public function fetch_my_trades(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             *
             * @see https://developer-pro.bitmart.com/en/spot/#account-trade-list-v4-signed
             * @see https://developer-pro.bitmart.com/en/futuresv2/#get-order-trade-keyed
             *
             * fetch all trades made by the user
             * @param {string} $symbol unified $market $symbol
             * @param {int} [$since] the earliest time in ms to fetch trades for
             * @param {int} [$limit] the maximum number of trades structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] the latest time in ms to fetch trades for
             * @param {boolean} [$params->marginMode] *spot* whether to fetch trades for margin orders or spot orders, defaults to spot orders (only isolated margin orders are supported)
             * @param {string} [$params->stpMode] self-trade prevention only for spot, defaults to none, ['none', 'cancel_maker', 'cancel_taker', 'cancel_both']
             * @return {Trade[]} a list of ~@link https://docs.ccxt.com/#/?id=trade-structure trade structures~
             */
            Async\await($this->load_markets());
            $market = null;
            $request = array();
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['symbol'] = $market['id'];
            }
            $type = null;
            $response = null;
            list($type, $params) = $this->handle_market_type_and_params('fetchMyTrades', $market, $params);
            $until = $this->safe_integer_n($params, array( 'until', 'endTime', 'end_time' ));
            $params = $this->omit($params, array( 'until' ));
            if ($type === 'spot') {
                $marginMode = null;
                list($marginMode, $params) = $this->handle_margin_mode_and_params('fetchMyTrades', $params);
                if ($marginMode === 'isolated') {
                    $request['orderMode'] = 'iso_margin';
                }
                $options = $this->safe_dict($this->options, 'fetchMyTrades', array());
                $maxLimit = 200;
                $defaultLimit = $this->safe_integer($options, 'limit', $maxLimit);
                if ($limit === null) {
                    $limit = $defaultLimit;
                }
                $request['limit'] = min ($limit, $maxLimit);
                if ($since !== null) {
                    $request['startTime'] = $since;
                }
                if ($until !== null) {
                    $request['endTime'] = $until;
                }
                $response = Async\await($this->privatePostSpotV4QueryTrades ($this->extend($request, $params)));
            } elseif ($type === 'swap') {
                if ($symbol === null) {
                    throw new ArgumentsRequired($this->id . ' fetchMyTrades() requires a $symbol argument');
                }
                if ($since !== null) {
                    $request['start_time'] = $since;
                }
                if ($until !== null) {
                    $request['end_time'] = $until;
                }
                $response = Async\await($this->privateGetContractPrivateTrades ($this->extend($request, $params)));
            } else {
                throw new NotSupported($this->id . ' fetchMyTrades() does not support ' . $type . ' orders, only spot and swap orders are accepted');
            }
            //
            // spot
            //
            //    {
            //        "code":1000,
            //        "message":"success",
            //        "data":array(
            //           {
            //              "tradeId":"182342999769370687",
            //              "orderId":"183270218784142990",
            //              "clientOrderId":"183270218784142990",
            //              "symbol":"ADA_USDT",
            //              "side":"buy",
            //              "orderMode":"spot",
            //              "type":"market",
            //              "price":"0.245948",
            //              "size":"20.71",
            //              "notional":"5.09358308",
            //              "fee":"0.00509358",
            //              "feeCoinName":"USDT",
            //              "tradeRole":"taker",
            //              "createTime":1695658457836,
            //              "updateTime":1695658457836
            //           }
            //        ),
            //        "trace":"fbaee9e0e2f5442fba5b3262fc86b0ac.65.16956593456523085"
            //    }
            //
            // swap
            //
            //     {
            //         "code" => 1000,
            //         "message" => "Ok",
            //         "data" => array(
            //             array(
            //                 "order_id" => "230930336848609",
            //                 "trade_id" => "6212604014",
            //                 "symbol" => "BTCUSDT",
            //                 "side" => 3,
            //                 "price" => "26910.4",
            //                 "vol" => "1",
            //                 "exec_type" => "Taker",
            //                 "profit" => false,
            //                 "create_time" => 1695961596692,
            //                 "realised_profit" => "-0.0003",
            //                 "paid_fees" => "0.01614624"
            //             ),
            //         ),
            //         "trace" => "4cad855074634097ac6ba5257c47305d.62.16959616054873723"
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_trades($data, $market, $since, $limit);
        }) ();
    }

    public function fetch_order_trades(string $id, ?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $since, $limit, $params) {
            /**
             *
             * @see https://developer-pro.bitmart.com/en/spot/#order-trade-list-v4-signed
             *
             * fetch all the trades made from a single order
             * @param {string} $id order $id
             * @param {string} $symbol unified market $symbol
             * @param {int} [$since] the earliest time in ms to fetch trades for
             * @param {int} [$limit] the maximum number of trades to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->stpMode] self-trade prevention only for spot, defaults to none, ['none', 'cancel_maker', 'cancel_taker', 'cancel_both']
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?$id=trade-structure trade structures~
             */
            Async\await($this->load_markets());
            $request = array(
                'orderId' => $id,
            );
            $response = Async\await($this->privatePostSpotV4QueryOrderTrades ($this->extend($request, $params)));
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_trades($data, null, $since, $limit);
        }) ();
    }

    public function custom_parse_balance($response, $marketType): array {
        $data = $this->safe_dict($response, 'data', array());
        $wallet = null;
        if ($marketType === 'swap') {
            $wallet = $this->safe_list($response, 'data', array());
        } elseif ($marketType === 'margin') {
            $wallet = $this->safe_list($data, 'symbols', array());
        } else {
            $wallet = $this->safe_list($data, 'wallet', array());
        }
        $result = array( 'info' => $response );
        if ($marketType === 'margin') {
            for ($i = 0; $i < count($wallet); $i++) {
                $entry = $wallet[$i];
                $marketId = $this->safe_string($entry, 'symbol');
                $symbol = $this->safe_symbol($marketId, null, '_');
                $base = $this->safe_dict($entry, 'base', array());
                $quote = $this->safe_dict($entry, 'quote', array());
                $baseCode = $this->safe_currency_code($this->safe_string($base, 'currency'));
                $quoteCode = $this->safe_currency_code($this->safe_string($quote, 'currency'));
                $subResult = array();
                $subResult[$baseCode] = $this->parse_balance_helper($base);
                $subResult[$quoteCode] = $this->parse_balance_helper($quote);
                $result[$symbol] = $this->safe_balance($subResult);
            }
            return $result;
        } else {
            for ($i = 0; $i < count($wallet); $i++) {
                $balance = $wallet[$i];
                $currencyId = $this->safe_string_2($balance, 'id', 'currency');
                $currencyId = $this->safe_string($balance, 'coin_code', $currencyId);
                $code = $this->safe_currency_code($currencyId);
                $account = $this->account();
                $account['free'] = $this->safe_string_2($balance, 'available', 'available_balance');
                $account['used'] = $this->safe_string_n($balance, array( 'unAvailable', 'frozen', 'frozen_balance' ));
                $result[$code] = $account;
            }
            return $this->safe_balance($result);
        }
    }

    public function parse_balance_helper($entry) {
        $account = $this->account();
        $account['used'] = $this->safe_string($entry, 'frozen');
        $account['free'] = $this->safe_string($entry, 'available');
        $account['total'] = $this->safe_string($entry, 'total_asset');
        $debt = $this->safe_string($entry, 'borrow_unpaid');
        $interest = $this->safe_string($entry, 'interest_unpaid');
        $account['debt'] = Precise::string_add($debt, $interest);
        return $account;
    }

    public function fetch_balance($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * query for balance and get the amount of funds available for trading or funds locked in orders
             *
             * @see https://developer-pro.bitmart.com/en/spot/#get-spot-wallet-balance-keyed
             * @see https://developer-pro.bitmart.com/en/futuresv2/#get-contract-assets-keyed
             * @see https://developer-pro.bitmart.com/en/spot/#get-account-balance-keyed
             * @see https://developer-pro.bitmart.com/en/spot/#get-margin-account-details-isolated-keyed
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=balance-structure balance structure~
             */
            Async\await($this->load_markets());
            $marketType = null;
            list($marketType, $params) = $this->handle_market_type_and_params('fetchBalance', null, $params);
            $marginMode = $this->safe_string($params, 'marginMode');
            $isMargin = $this->safe_bool($params, 'margin', false);
            $params = $this->omit($params, array( 'margin', 'marginMode' ));
            if ($marginMode !== null || $isMargin) {
                $marketType = 'margin';
            }
            $response = null;
            if ($marketType === 'spot') {
                $response = Async\await($this->privateGetSpotV1Wallet ($params));
            } elseif ($marketType === 'swap') {
                $response = Async\await($this->privateGetContractPrivateAssetsDetail ($params));
            } elseif ($marketType === 'account') {
                $response = Async\await($this->privateGetAccountV1Wallet ($params));
            } elseif ($marketType === 'margin') {
                $response = Async\await($this->privateGetSpotV1MarginIsolatedAccount ($params));
            } else {
                throw new NotSupported($this->id . ' fetchBalance() does not support ' . $marketType . ' markets, only spot, swap and account and margin markets are accepted');
            }
            //
            // spot
            //
            //     {
            //         "message":"OK",
            //         "code":1000,
            //         "trace":"39069916-72f9-44c7-acde-2ad5afd21cad",
            //         "data":{
            //             "wallet":array(
            //                 array("id":"BTC","name":"Bitcoin","available":"0.00000062","frozen":"0.00000000"),
            //                 array("id":"ETH","name":"Ethereum","available":"0.00002277","frozen":"0.00000000"),
            //                 array("id":"BMX","name":"BitMart Token","available":"0.00000000","frozen":"0.00000000")
            //             )
            //         }
            //     }
            //
            // account
            //
            //     {
            //         "message":"OK",
            //         "code":1000,
            //         "trace":"5c3b7fc7-93b2-49ef-bb59-7fdc56915b59",
            //         "data":{
            //             "wallet":array(
            //                 array("currency":"BTC","name":"Bitcoin","available":"0.00000062","frozen":"0.00000000","available_usd_valuation":null),
            //                 array("currency":"ETH","name":"Ethereum","available":"0.00002277","frozen":"0.00000000","available_usd_valuation":null)
            //             )
            //         }
            //     }
            //
            // swap
            //
            //     {
            //         "code" => 1000,
            //         "message" => "Ok",
            //         "data" => array(
            //             array(
            //                 "currency" => "USDT",
            //                 "available_balance" => "0",
            //                 "frozen_balance" => "0",
            //                 "unrealized" => "0",
            //                 "equity" => "0",
            //                 "position_deposit" => "0"
            //             ),
            //             ...
            //         ),
            //         "trace" => "f9da3a39-cf45-42e7-914d-294f565dfc33"
            //     }
            //
            // margin
            //
            //     {
            //         "message" => "OK",
            //         "code" => 1000,
            //         "trace" => "61dd6ab265c04064b72d8bc9b205f741.71.16701055600915302",
            //         "data" => {
            //             "symbols" => array(
            //                 {
            //                     "symbol" => "BTC_USDT",
            //                     "risk_rate" => "999.00",
            //                     "risk_level" => "1",
            //                     "buy_enabled" => false,
            //                     "sell_enabled" => false,
            //                     "liquidate_price" => null,
            //                     "liquidate_rate" => "1.15",
            //                     "base" => array(
            //                         "currency" => "BTC",
            //                         "borrow_enabled" => true,
            //                         "borrowed" => "0.00000000",
            //                         "available" => "0.00000000",
            //                         "frozen" => "0.00000000",
            //                         "net_asset" => "0.00000000",
            //                         "net_assetBTC" => "0.00000000",
            //                         "total_asset" => "0.00000000",
            //                         "borrow_unpaid" => "0.00000000",
            //                         "interest_unpaid" => "0.00000000"
            //                     ),
            //                     "quote" => {
            //                         "currency" => "USDT",
            //                         "borrow_enabled" => true,
            //                         "borrowed" => "0.00000000",
            //                         "available" => "20.00000000",
            //                         "frozen" => "0.00000000",
            //                         "net_asset" => "20.00000000",
            //                         "net_assetBTC" => "0.00118008",
            //                         "total_asset" => "20.00000000",
            //                         "borrow_unpaid" => "0.00000000",
            //                         "interest_unpaid" => "0.00000000"
            //                     }
            //                 }
            //             )
            //         }
            //     }
            //
            return $this->custom_parse_balance($response, $marketType);
        }) ();
    }

    public function parse_trading_fee(array $fee, ?array $market = null): array {
        //
        //     {
        //         "symbol" => "ETH_USDT",
        //         "taker_fee_rate" => "0.0025",
        //         "maker_fee_rate" => "0.0025"
        //     }
        //
        $marketId = $this->safe_string($fee, 'symbol');
        $symbol = $this->safe_symbol($marketId);
        return array(
            'info' => $fee,
            'symbol' => $symbol,
            'maker' => $this->safe_number($fee, 'maker_fee_rate'),
            'taker' => $this->safe_number($fee, 'taker_fee_rate'),
            'percentage' => null,
            'tierBased' => null,
        );
    }

    public function fetch_trading_fee(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetch the trading fees for a $market
             *
             * @see https://developer-pro.bitmart.com/en/spot/#get-actual-trade-fee-rate-keyed
             *
             * @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=fee-structure fee structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            if (!$market['spot']) {
                throw new NotSupported($this->id . ' fetchTradingFee() does not support ' . $market['type'] . ' orders, only spot orders are accepted');
            }
            $request = array(
                'symbol' => $market['id'],
            );
            $response = Async\await($this->privateGetSpotV1TradeFee ($this->extend($request, $params)));
            //
            //     {
            //         "message" => "OK",
            //         "code" => "1000",
            //         "trace" => "5a6f1e40-37fe-4849-a494-03279fadcc62",
            //         "data" => {
            //             "symbol" => "ETH_USDT",
            //             "taker_fee_rate" => "0.0025",
            //             "maker_fee_rate" => "0.0025"
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_trading_fee($data);
        }) ();
    }

    public function parse_order(array $order, ?array $market = null): array {
        //
        // createOrder, editOrder
        //
        //     {
        //         "order_id" => 2707217580
        //     }
        //
        // swap
        //   "data" => array(
        //       "order_id" => 231116359426639,
        //       "price" => "market price"
        //    ),
        //
        // cancelOrder
        //
        //     "2707217580" // $order $id
        //
        // spot fetchOrder, fetchOrdersByStatus, fetchOpenOrders, fetchClosedOrders
        //
        //     {
        //         "order_id":1736871726781,
        //         "symbol":"BTC_USDT",
        //         "create_time":1591096004000,
        //         "side":"sell",
        //         "type":"market", // limit, $market, limit_maker, ioc
        //         "price":"0.00",
        //         "price_avg":"0.00",
        //         "size":"0.02000",
        //         "notional":"0.00000000",
        //         "filled_notional":"0.00000000",
        //         "filled_size":"0.00000",
        //         "status":"8"
        //     }
        //
        // spot v4
        //    {
        //        "orderId" : "118100034543076010",
        //        "clientOrderId" : "118100034543076010",
        //        "symbol" : "BTC_USDT",
        //        "side" : "buy",
        //        "orderMode" : "spot",
        //        "type" : "limit",
        //        "state" : "filled",
        //        "price" : "48800.00",
        //        "priceAvg" : "39999.00",
        //        "size" : "0.10000",
        //        "filledSize" : "0.10000",
        //        "notional" : "4880.00000000",
        //        "filledNotional" : "3999.90000000",
        //        "createTime" : 1681701557927,
        //        "updateTime" : 1681701559408
        //    }
        //
        // swap => fetchOrder, fetchOpenOrders, fetchClosedOrders
        //
        //     {
        //         "order_id" => "230935812485489",
        //         "client_order_id" => "",
        //         "price" => "24000",
        //         "size" => "1",
        //         "symbol" => "BTCUSDT",
        //         "state" => 2,
        //         "side" => 1,
        //         "type" => "limit",
        //         "leverage" => "10",
        //         "open_type" => "isolated",
        //         "deal_avg_price" => "0",
        //         "deal_size" => "0",
        //         "create_time" => 1695702258629,
        //         "update_time" => 1695702258642,
        //         "activation_price_type" => 0,
        //         "activation_price" => "",
        //         "callback_rate" => ""
        //     }
        //
        $id = null;
        if (gettype($order) === 'string') {
            $id = $order;
            $order = array();
        }
        $id = $this->safe_string_2($order, 'order_id', 'orderId', $id);
        $timestamp = $this->safe_integer_2($order, 'create_time', 'createTime');
        $marketId = $this->safe_string($order, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $market = $this->safe_market($symbol, $market);
        $orderType = $this->safe_string($market, 'type', 'spot');
        $type = $this->safe_string($order, 'type');
        $timeInForce = null;
        $postOnly = null;
        if ($type === 'limit_maker') {
            $type = 'limit';
            $postOnly = true;
            $timeInForce = 'PO';
        }
        if ($type === 'ioc') {
            $type = 'limit';
            $timeInForce = 'IOC';
        }
        $priceString = $this->safe_string($order, 'price');
        if ($priceString === 'market price') {
            $priceString = null;
        }
        $trailingActivationPrice = $this->safe_number($order, 'activation_price');
        return $this->safe_order(array(
            'id' => $id,
            'clientOrderId' => $this->safe_string_2($order, 'client_order_id', 'clientOrderId'),
            'info' => $order,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastTradeTimestamp' => $this->safe_integer($order, 'update_time'),
            'symbol' => $symbol,
            'type' => $type,
            'timeInForce' => $timeInForce,
            'postOnly' => $postOnly,
            'side' => $this->parse_order_side($this->safe_string($order, 'side')),
            'price' => $this->omit_zero($priceString),
            'triggerPrice' => $trailingActivationPrice,
            'amount' => $this->omit_zero($this->safe_string($order, 'size')),
            'cost' => $this->safe_string_2($order, 'filled_notional', 'filledNotional'),
            'average' => $this->safe_string_n($order, array( 'price_avg', 'priceAvg', 'deal_avg_price' )),
            'filled' => $this->safe_string_n($order, array( 'filled_size', 'filledSize', 'deal_size' )),
            'remaining' => null,
            'status' => $this->parse_order_status_by_type($orderType, $this->safe_string_2($order, 'status', 'state')),
            'fee' => null,
            'trades' => null,
        ), $market);
    }

    public function parse_order_side($side) {
        $sides = array(
            '1' => 'buy',
            '2' => 'buy',
            '3' => 'sell',
            '4' => 'sell',
        );
        return $this->safe_string($sides, $side, $side);
    }

    public function parse_order_status_by_type($type, $status) {
        $statusesByType = array(
            'spot' => array(
                '1' => 'rejected', // Order failure
                '2' => 'open', // Placing order
                '3' => 'rejected', // Order failure, Freeze failure
                '4' => 'open', // Order success, Pending for fulfilment
                '5' => 'open', // Partially filled
                '6' => 'closed', // Fully filled
                '7' => 'canceled', // Canceling
                '8' => 'canceled', // Canceled
                'new' => 'open',
                'partially_filled' => 'open',
                'filled' => 'closed',
                'partially_canceled' => 'canceled',
            ),
            'swap' => array(
                '1' => 'open', // Submitting
                '2' => 'open', // Commissioned
                '4' => 'closed', // Completed
            ),
        );
        $statuses = $this->safe_dict($statusesByType, $type, array());
        return $this->safe_string($statuses, $status, $status);
    }

    public function create_market_buy_order_with_cost(string $symbol, float $cost, $params = array ()) {
        return Async\async(function () use ($symbol, $cost, $params) {
            /**
             * create a $market buy order by providing the $symbol and $cost
             *
             * @see https://developer-pro.bitmart.com/en/spot/#new-order-v2-signed
             *
             * @param {string} $symbol unified $symbol of the $market to create an order in
             * @param {float} $cost how much you want to trade in units of the quote currency
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            if (!$market['spot']) {
                throw new NotSupported($this->id . ' createMarketBuyOrderWithCost() supports spot orders only');
            }
            $params['createMarketBuyOrderRequiresPrice'] = false;
            return Async\await($this->create_order($symbol, 'market', 'buy', $cost, null, $params));
        }) ();
    }

    public function create_order(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        return Async\async(function () use ($symbol, $type, $side, $amount, $price, $params) {
            /**
             * create a trade $order
             *
             * @see https://developer-pro.bitmart.com/en/spot/#new-$order-v2-signed
             * @see https://developer-pro.bitmart.com/en/spot/#new-margin-$order-v1-signed
             * @see https://developer-pro.bitmart.com/en/futuresv2/#submit-$order-signed
             * @see https://developer-pro.bitmart.com/en/futuresv2/#submit-plan-$order-signed
             * @see https://developer-pro.bitmart.com/en/futuresv2/#submit-tp-sl-$order-signed
             * @see https://developer-pro.bitmart.com/en/futuresv2/#submit-trail-$order-signed
             *
             * @param {string} $symbol unified $symbol of the $market to create an $order in
             * @param {string} $type 'market', 'limit' or 'trailing' for swap markets only
             * @param {string} $side 'buy' or 'sell'
             * @param {float} $amount how much of currency you want to trade in units of base currency
             * @param {float} [$price] the $price at which the $order is to be fulfilled, in units of the quote currency, ignored in $market orders
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->marginMode] 'cross' or 'isolated'
             * @param {string} [$params->leverage] *swap only* leverage level
             * @param {string} [$params->clientOrderId] client $order id of the $order
             * @param {boolean} [$params->reduceOnly] *swap only* reduce only
             * @param {boolean} [$params->postOnly] make sure the $order is posted to the $order book and not matched immediately
             * @param {string} [$params->triggerPrice] *swap only* the $price to trigger a stop $order
             * @param {int} [$params->price_type] *swap only* 1 => last $price, 2 => fair $price, default is 1
             * @param {int} [$params->price_way] *swap only* 1 => $price way long, 2 => $price way short
             * @param {int} [$params->activation_price_type] *swap trailing $order only* 1 => last $price, 2 => fair $price, default is 1
             * @param {string} [$params->trailingPercent] *swap only* the percent to trail away from the current $market $price, min 0.1 max 5
             * @param {string} [$params->trailingTriggerPrice] *swap only* the $price to trigger a trailing $order, default uses the $price argument
             * @param {string} [$params->stopLossPrice] *swap only* the $price to trigger a stop-loss $order
             * @param {string} [$params->takeProfitPrice] *swap only* the $price to trigger a take-profit $order
             * @param {int} [$params->plan_category] *swap tp/sl only* 1 => tp/sl, 2 => position tp/sl, default is 1
             * @param {string} [$params->stpMode] self-trade prevention only for spot, defaults to none, ['none', 'cancel_maker', 'cancel_taker', 'cancel_both']
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=$order-structure $order structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $result = $this->handle_margin_mode_and_params('createOrder', $params);
            $marginMode = $this->safe_string($result, 0);
            $triggerPrice = $this->safe_string_n($params, array( 'triggerPrice', 'stopPrice', 'trigger_price' ));
            $stopLossPrice = $this->safe_string($params, 'stopLossPrice');
            $takeProfitPrice = $this->safe_string($params, 'takeProfitPrice');
            $isStopLoss = $stopLossPrice !== null;
            $isTakeProfit = $takeProfitPrice !== null;
            $isTriggerOrder = $triggerPrice !== null;
            $response = null;
            if ($market['spot']) {
                $spotRequest = $this->create_spot_order_request($symbol, $type, $side, $amount, $price, $params);
                if ($marginMode === 'isolated') {
                    $response = Async\await($this->privatePostSpotV1MarginSubmitOrder ($spotRequest));
                } else {
                    $response = Async\await($this->privatePostSpotV2SubmitOrder ($spotRequest));
                }
            } else {
                $swapRequest = $this->create_swap_order_request($symbol, $type, $side, $amount, $price, $params);
                $activationPrice = $this->safe_string($swapRequest, 'activation_price');
                if ($activationPrice !== null) {
                    // if $type is trailing
                    $response = Async\await($this->privatePostContractPrivateSubmitTrailOrder ($swapRequest));
                } elseif ($isTriggerOrder) {
                    $response = Async\await($this->privatePostContractPrivateSubmitPlanOrder ($swapRequest));
                } elseif ($isStopLoss || $isTakeProfit) {
                    $response = Async\await($this->privatePostContractPrivateSubmitTpSlOrder ($swapRequest));
                } else {
                    $response = Async\await($this->privatePostContractPrivateSubmitOrder ($swapRequest));
                }
            }
            //
            // spot and margin
            //
            //     {
            //         "code" => 1000,
            //         "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1",
            //         "message" => "OK",
            //         "data" => {
            //             "order_id" => 2707217580
            //         }
            //     }
            //
            // swap
            // array("code":1000,"message":"Ok","data":array("order_id":231116359426639,"price":"market $price"),"trace":"7f9c94e10f9d4513bc08a7bfc2a5559a.62.16996369620521911")
            //
            $data = $this->safe_dict($response, 'data', array());
            $order = $this->parse_order($data, $market);
            $order['type'] = $type;
            $order['side'] = $side;
            $order['amount'] = $amount;
            $order['price'] = $price;
            return $order;
        }) ();
    }

    public function create_orders(array $orders, $params = array ()) {
        return Async\async(function () use ($orders, $params) {
            /**
             * create a list of trade $orders
             *
             * @see https://developer-pro.bitmart.com/en/spot/#new-batch-$order-v4-signed
             *
             * @param {Array} $orders list of $orders to create, each object should contain the parameters required by createOrder, namely $symbol, $type, $side, $amount, $price and $params
             * @param {array} [$params]  extra parameters specific to the exchange API endpoint
             * @param {string} [$params->stpMode] self-trade prevention only for spot, defaults to none, ['none', 'cancel_maker', 'cancel_taker', 'cancel_both']
             * @return {array} an ~@link https://docs.ccxt.com/#/?id=$order-structure $order structure~
             */
            Async\await($this->load_markets());
            $ordersRequests = array();
            $symbol = null;
            $market = null;
            for ($i = 0; $i < count($orders); $i++) {
                $rawOrder = $orders[$i];
                $marketId = $this->safe_string($rawOrder, 'symbol');
                $market = $this->market($marketId);
                if (!$market['spot']) {
                    throw new NotSupported($this->id . ' createOrders() supports spot $orders only');
                }
                if ($symbol === null) {
                    $symbol = $marketId;
                } else {
                    if ($symbol !== $marketId) {
                        throw new BadRequest($this->id . ' createOrders() requires all $orders to have the same symbol');
                    }
                }
                $type = $this->safe_string($rawOrder, 'type');
                $side = $this->safe_string($rawOrder, 'side');
                $amount = $this->safe_value($rawOrder, 'amount');
                $price = $this->safe_value($rawOrder, 'price');
                $orderParams = $this->safe_dict($rawOrder, 'params', array());
                $orderRequest = $this->create_spot_order_request($marketId, $type, $side, $amount, $price, $orderParams);
                $orderRequest = $this->omit($orderRequest, array( 'symbol' )); // not needed because it goes in the outter object
                $ordersRequests[] = $orderRequest;
            }
            $request = array(
                'symbol' => $market['id'],
                'orderParams' => $ordersRequests,
            );
            $response = Async\await($this->privatePostSpotV4BatchOrders ($request));
            //
            // {
            //     "message" => "OK",
            //     "code" => 1000,
            //     "trace" => "5fc697fb817a4b5396284786a9b2609a.263.17022620476480263",
            //     "data" => {
            //       "code" => 0,
            //       "msg" => "success",
            //       "data" => {
            //         "orderIds" => array(
            //           "212751308355553320"
            //         )
            //       }
            //     }
            // }
            //
            $data = $this->safe_dict($response, 'data', array());
            $innderData = $this->safe_dict($data, 'data', array());
            $orderIds = $this->safe_list($innderData, 'orderIds', array());
            $parsedOrders = array();
            for ($i = 0; $i < count($orderIds); $i++) {
                $orderId = $orderIds[$i];
                $order = $this->safe_order(array( 'id' => $orderId ), $market);
                $parsedOrders[] = $order;
            }
            return $parsedOrders;
        }) ();
    }

    public function create_swap_order_request(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        /**
         * @ignore
         * create a trade order
         * @see https://developer-pro.bitmart.com/en/futuresv2/#submit-order-signed
         * @see https://developer-pro.bitmart.com/en/futuresv2/#submit-plan-order-signed
         * @see https://developer-pro.bitmart.com/en/futuresv2/#submit-tp-sl-order-signed
         * @see https://developer-pro.bitmart.com/en/futuresv2/#submit-trail-order-signed
         * @param {string} $symbol unified $symbol of the $market to create an order in
         * @param {string} $type 'market', 'limit', 'trailing', 'stop_loss', or 'take_profit'
         * @param {string} $side 'buy' or 'sell'
         * @param {float} $amount how much of currency you want to trade in units of base currency
         * @param {float} [$price] the $price at which the order is to be fulfilled, in units of the quote currency, ignored in $market orders
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {int} [$params->leverage] $leverage level
         * @param {boolean} [$params->reduceOnly] *swap only* reduce only
         * @param {string} [$params->marginMode] 'cross' or 'isolated', default is 'cross'
         * @param {string} [$params->clientOrderId] client order id of the order
         * @param {string} [$params->triggerPrice] *swap only* the $price to trigger a stop order
         * @param {int} [$params->price_type] *swap only* 1 => last $price, 2 => fair $price, default is 1
         * @param {int} [$params->price_way] *swap only* 1 => $price way long, 2 => $price way short
         * @param {int} [$params->activation_price_type] *swap trailing order only* 1 => last $price, 2 => fair $price, default is 1
         * @param {string} [$params->trailingPercent] *swap only* the percent to trail away from the current $market $price, min 0.1 max 5
         * @param {string} [$params->trailingTriggerPrice] *swap only* the $price to trigger a trailing order, default uses the $price argument
         * @param {string} [$params->stopLossPrice] *swap only* the $price to trigger a stop-loss order
         * @param {string} [$params->takeProfitPrice] *swap only* the $price to trigger a take-profit order
         * @param {int} [$params->plan_category] *swap tp/sl only* 1 => tp/sl, 2 => position tp/sl, default is 1
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
         */
        $market = $this->market($symbol);
        $stopLossPrice = $this->safe_string($params, 'stopLossPrice');
        $takeProfitPrice = $this->safe_string($params, 'takeProfitPrice');
        $isStopLoss = $stopLossPrice !== null;
        $isTakeProfit = $takeProfitPrice !== null;
        if ($isStopLoss) {
            $type = 'stop_loss';
        } elseif ($isTakeProfit) {
            $type = 'take_profit';
        }
        $request = array(
            'symbol' => $market['id'],
            'size' => intval($this->amount_to_precision($symbol, $amount)),
        );
        $timeInForce = $this->safe_string($params, 'timeInForce');
        $mode = $this->safe_integer($params, 'mode'); // only for swap
        $isMarketOrder = $type === 'market';
        $postOnly = null;
        $reduceOnly = $this->safe_bool($params, 'reduceOnly');
        $isExchangeSpecificPo = ($mode === 4);
        list($postOnly, $params) = $this->handle_post_only($isMarketOrder, $isExchangeSpecificPo, $params);
        $ioc = (($timeInForce === 'IOC') || ($mode === 3));
        $isLimitOrder = ($type === 'limit') || $postOnly || $ioc;
        if ($timeInForce === 'GTC') {
            $request['mode'] = 1;
        } elseif ($timeInForce === 'FOK') {
            $request['mode'] = 2;
        } elseif ($timeInForce === 'IOC') {
            $request['mode'] = 3;
        }
        if ($postOnly) {
            $request['mode'] = 4;
        }
        $triggerPrice = $this->safe_string_n($params, array( 'triggerPrice', 'stopPrice', 'trigger_price' ));
        $isTriggerOrder = $triggerPrice !== null;
        $trailingTriggerPrice = $this->safe_string_2($params, 'trailingTriggerPrice', 'activation_price', $this->number_to_string($price));
        $trailingPercent = $this->safe_string_2($params, 'trailingPercent', 'callback_rate');
        $isTrailingPercentOrder = $trailingPercent !== null;
        if ($isLimitOrder) {
            $request['price'] = $this->price_to_precision($symbol, $price);
        } elseif ($type === 'trailing' || $isTrailingPercentOrder) {
            $type = 'trailing';
            $request['callback_rate'] = $trailingPercent;
            $request['activation_price'] = $this->price_to_precision($symbol, $trailingTriggerPrice);
            $request['activation_price_type'] = $this->safe_integer($params, 'activation_price_type', 1);
        }
        if ($isTriggerOrder) {
            if ($isLimitOrder || $price !== null) {
                $request['executive_price'] = $this->price_to_precision($symbol, $price);
            }
            $request['trigger_price'] = $this->price_to_precision($symbol, $triggerPrice);
            $request['price_type'] = $this->safe_integer($params, 'price_type', 1);
            if ($side === 'buy') {
                if ($reduceOnly) {
                    $request['price_way'] = 2;
                } else {
                    $request['price_way'] = 1;
                }
            } elseif ($side === 'sell') {
                if ($reduceOnly) {
                    $request['price_way'] = 1;
                } else {
                    $request['price_way'] = 2;
                }
            }
        }
        $marginMode = null;
        list($marginMode, $params) = $this->handle_margin_mode_and_params('createOrder', $params, 'cross');
        if ($isStopLoss || $isTakeProfit) {
            $reduceOnly = true;
            $request['price_type'] = $this->safe_integer($params, 'price_type', 1);
            $request['executive_price'] = $this->price_to_precision($symbol, $price);
            if ($isStopLoss) {
                $request['trigger_price'] = $this->price_to_precision($symbol, $stopLossPrice);
            } else {
                $request['trigger_price'] = $this->price_to_precision($symbol, $takeProfitPrice);
            }
        } else {
            $request['open_type'] = $marginMode;
        }
        if ($side === 'buy') {
            if ($reduceOnly) {
                $request['side'] = 2; // buy close short
            } else {
                $request['side'] = 1; // buy open long
            }
        } elseif ($side === 'sell') {
            if ($reduceOnly) {
                $request['side'] = 3; // sell close long
            } else {
                $request['side'] = 4; // sell open short
            }
        }
        $clientOrderId = $this->safe_string($params, 'clientOrderId');
        if ($clientOrderId !== null) {
            $params = $this->omit($params, 'clientOrderId');
            $request['client_order_id'] = $clientOrderId;
        }
        $leverage = $this->safe_integer($params, 'leverage');
        $params = $this->omit($params, array( 'timeInForce', 'postOnly', 'reduceOnly', 'leverage', 'trailingTriggerPrice', 'trailingPercent', 'triggerPrice', 'stopPrice', 'stopLossPrice', 'takeProfitPrice' ));
        if ($leverage !== null) {
            $request['leverage'] = $this->number_to_string($leverage);
        } elseif ($isTriggerOrder) {
            $request['leverage'] = '1'; // for plan orders $leverage is required, if not available default to 1
        }
        if ($type !== 'trailing') {
            $request['type'] = $type;
        }
        return $this->extend($request, $params);
    }

    public function create_spot_order_request(string $symbol, string $type, string $side, float $amount, ?float $price = null, $params = array ()) {
        /**
         * @ignore
         * create a spot order $request
         * @see https://developer-pro.bitmart.com/en/spot/#new-order-v2-signed
         * @see https://developer-pro.bitmart.com/en/spot/#new-margin-order-v1-signed
         * @param {string} $symbol unified $symbol of the $market to create an order in
         * @param {string} $type 'market' or 'limit'
         * @param {string} $side 'buy' or 'sell'
         * @param {float} $amount how much of currency you want to trade in units of base currency
         * @param {float} [$price] the $price at which the order is to be fulfilled, in units of the quote currency, ignored in $market orders
         * @param {array} [$params] extra parameters specific to the exchange API endpoint
         * @param {string} [$params->marginMode] 'cross' or 'isolated'
         * @return {array} an ~@link https://docs.ccxt.com/#/?id=order-structure order structure~
         */
        $market = $this->market($symbol);
        $request = array(
            'symbol' => $market['id'],
            'side' => $side,
            'type' => $type,
        );
        $timeInForce = $this->safe_string($params, 'timeInForce');
        if ($timeInForce === 'FOK') {
            throw new InvalidOrder($this->id . ' createOrder() only accepts $timeInForce parameter values of IOC or PO');
        }
        $mode = $this->safe_integer($params, 'mode'); // only for swap
        $isMarketOrder = $type === 'market';
        $postOnly = null;
        $isExchangeSpecificPo = ($type === 'limit_maker') || ($mode === 4);
        list($postOnly, $params) = $this->handle_post_only($isMarketOrder, $isExchangeSpecificPo, $params);
        $params = $this->omit($params, array( 'timeInForce', 'postOnly' ));
        $ioc = (($timeInForce === 'IOC') || ($type === 'ioc'));
        $isLimitOrder = ($type === 'limit') || $postOnly || $ioc;
        // method = 'privatePostSpotV2SubmitOrder';
        if ($isLimitOrder) {
            $request['size'] = $this->amount_to_precision($symbol, $amount);
            $request['price'] = $this->price_to_precision($symbol, $price);
        } elseif ($isMarketOrder) {
            // for $market buy it requires the $amount of quote currency to spend
            if ($side === 'buy') {
                $notional = $this->safe_string_2($params, 'cost', 'notional');
                $params = $this->omit($params, 'cost');
                $createMarketBuyOrderRequiresPrice = true;
                list($createMarketBuyOrderRequiresPrice, $params) = $this->handle_option_and_params($params, 'createOrder', 'createMarketBuyOrderRequiresPrice', true);
                if ($createMarketBuyOrderRequiresPrice) {
                    if (($price === null) && ($notional === null)) {
                        throw new InvalidOrder($this->id . ' createOrder() requires the $price argument for $market buy orders to calculate the total cost to spend ($amount * $price), alternatively set the $createMarketBuyOrderRequiresPrice option or param to false and pass the cost to spend in the $amount argument or in the "notional" extra parameter (the exchange-specific behaviour)');
                    } else {
                        $amountString = $this->number_to_string($amount);
                        $priceString = $this->number_to_string($price);
                        $notional = Precise::string_mul($amountString, $priceString);
                    }
                } else {
                    $notional = ($notional === null) ? $this->number_to_string($amount) : $notional;
                }
                $request['notional'] = $this->decimal_to_precision($notional, TRUNCATE, $market['precision']['price'], $this->precisionMode);
            } elseif ($side === 'sell') {
                $request['size'] = $this->amount_to_precision($symbol, $amount);
            }
        }
        if ($postOnly) {
            $request['type'] = 'limit_maker';
        }
        if ($ioc) {
            $request['type'] = 'ioc';
        }
        $clientOrderId = $this->safe_string($params, 'clientOrderId');
        if ($clientOrderId !== null) {
            $params = $this->omit($params, 'clientOrderId');
            $request['client_order_id'] = $clientOrderId;
        }
        return $this->extend($request, $params);
    }

    public function cancel_order(string $id, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $params) {
            /**
             * cancels an open $order
             *
             * @see https://developer-pro.bitmart.com/en/futuresv2/#cancel-$order-signed
             * @see https://developer-pro.bitmart.com/en/spot/#cancel-$order-v3-signed
             * @see https://developer-pro.bitmart.com/en/futuresv2/#cancel-plan-$order-signed
             * @see https://developer-pro.bitmart.com/en/futuresv2/#cancel-$order-signed
             * @see https://developer-pro.bitmart.com/en/futuresv2/#cancel-trail-$order-signed
             *
             * @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
             * @param {string} [$params->clientOrderId] *spot only* the client $order $id of the $order to cancel
             * @param {boolean} [$params->trigger] *swap only* whether the $order is a $trigger $order
             * @param {boolean} [$params->trailing] *swap only* whether the $order is a stop $order
             * @return {array} An ~@link https://docs.ccxt.com/#/?$id=$order-structure $order structure~
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' cancelOrder() requires a $symbol argument');
            }
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'symbol' => $market['id'],
            );
            $clientOrderId = $this->safe_string_2($params, 'clientOrderId', 'client_order_id');
            if ($clientOrderId !== null) {
                $request['client_order_id'] = $clientOrderId;
            } else {
                $request['order_id'] = (string) $id;
            }
            $params = $this->omit($params, array( 'clientOrderId' ));
            $response = null;
            if ($market['spot']) {
                $response = Async\await($this->privatePostSpotV3CancelOrder ($this->extend($request, $params)));
            } else {
                $trigger = $this->safe_bool_2($params, 'stop', 'trigger');
                $trailing = $this->safe_bool($params, 'trailing');
                $params = $this->omit($params, array( 'stop', 'trigger' ));
                if ($trigger) {
                    $response = Async\await($this->privatePostContractPrivateCancelPlanOrder ($this->extend($request, $params)));
                } elseif ($trailing) {
                    $response = Async\await($this->privatePostContractPrivateCancelTrailOrder ($this->extend($request, $params)));
                } else {
                    $response = Async\await($this->privatePostContractPrivateCancelOrder ($this->extend($request, $params)));
                }
            }
            // swap
            // array("code":1000,"message":"Ok","trace":"7f9c94e10f9d4513bc08a7bfc2a5559a.55.16959817848001851")
            //
            // spot
            //
            //     {
            //         "code" => 1000,
            //         "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1",
            //         "message" => "OK",
            //         "data" => {
            //             "result" => true
            //         }
            //     }
            //
            // spot alternative
            //
            //     {
            //         "code" => 1000,
            //         "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1",
            //         "message" => "OK",
            //         "data" => true
            //     }
            //
            if ($market['swap']) {
                return $this->safe_order(array( 'info' => $response ));
            }
            $data = $this->safe_value($response, 'data');
            if ($data === true) {
                return $this->safe_order(array( 'id' => $id ), $market);
            }
            $succeeded = $this->safe_value($data, 'succeed');
            if ($succeeded !== null) {
                $id = $this->safe_string($succeeded, 0);
                if ($id === null) {
                    throw new InvalidOrder($this->id . ' cancelOrder() failed to cancel ' . $symbol . ' $order $id ' . $id);
                }
            } else {
                $result = $this->safe_value($data, 'result');
                if (!$result) {
                    throw new InvalidOrder($this->id . ' cancelOrder() ' . $symbol . ' $order $id ' . $id . ' is filled or canceled');
                }
            }
            $order = $this->safe_order(array( 'id' => $id, 'symbol' => $market['symbol'], 'info' => array()), $market);
            return $order;
        }) ();
    }

    public function cancel_orders(array $ids, ?string $symbol = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($ids, $symbol, $params) {
            /**
             * cancel multiple orders
             *
             * @see https://developer-pro.bitmart.com/en/spot/#cancel-batch-order-v4-signed
             *
             * @param {string[]} $ids order $ids
             * @param {string} $symbol unified $symbol of the $market the order was made in
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string[]} [$params->clientOrderIds] client order $ids
             * @return {array} an list of ~@link https://docs.ccxt.com/#/?$id=order-structure order structures~
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' cancelOrders() requires a $symbol argument');
            }
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            if (!$market['spot']) {
                throw new NotSupported($this->id . ' cancelOrders() does not support ' . $market['type'] . ' orders, only spot orders are accepted');
            }
            $clientOrderIds = $this->safe_list($params, 'clientOrderIds');
            $params = $this->omit($params, array( 'clientOrderIds' ));
            $request = array(
                'symbol' => $market['id'],
            );
            if ($clientOrderIds !== null) {
                $request['clientOrderIds'] = $clientOrderIds;
            } else {
                $request['orderIds'] = $ids;
            }
            $response = Async\await($this->privatePostSpotV4CancelOrders ($this->extend($request, $params)));
            //
            //  {
            //      "message" => "OK",
            //      "code" => 1000,
            //      "trace" => "c4edbce860164203954f7c3c81d60fc6.309.17022669632770001",
            //      "data" => {
            //        "successIds" => array(
            //          "213055379155243012"
            //        ),
            //        "failIds" => array(),
            //        "totalCount" => 1,
            //        "successCount" => 1,
            //        "failedCount" => 0
            //      }
            //  }
            //
            $data = $this->safe_dict($response, 'data', array());
            $allOrders = array();
            $successIds = $this->safe_list($data, 'successIds', array());
            for ($i = 0; $i < count($successIds); $i++) {
                $id = $successIds[$i];
                $allOrders[] = $this->safe_order(array( 'id' => $id, 'status' => 'canceled' ), $market);
            }
            $failIds = $this->safe_list($data, 'failIds', array());
            for ($i = 0; $i < count($failIds); $i++) {
                $id = $failIds[$i];
                $allOrders[] = $this->safe_order(array( 'id' => $id, 'status' => 'failed' ), $market);
            }
            return $allOrders;
        }) ();
    }

    public function cancel_all_orders(?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($symbol, $params) {
            /**
             * cancel all open orders in a $market
             *
             * @see https://developer-pro.bitmart.com/en/spot/#cancel-all-order-v4-signed
             * @see https://developer-pro.bitmart.com/en/futuresv2/#cancel-all-orders-signed
             *
             * @param {string} $symbol unified $market $symbol of the $market to cancel orders in
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->side] *spot only* 'buy' or 'sell'
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $request = array();
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['symbol'] = $market['id'];
            }
            $response = null;
            $type = null;
            list($type, $params) = $this->handle_market_type_and_params('cancelAllOrders', $market, $params);
            if ($type === 'spot') {
                $response = Async\await($this->privatePostSpotV4CancelAll ($this->extend($request, $params)));
            } elseif ($type === 'swap') {
                if ($symbol === null) {
                    throw new ArgumentsRequired($this->id . ' cancelAllOrders() requires a $symbol argument');
                }
                $response = Async\await($this->privatePostContractPrivateCancelOrders ($this->extend($request, $params)));
            }
            //
            // spot
            //
            //     {
            //         "code" => 1000,
            //         "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1",
            //         "message" => "OK",
            //         "data" => array()
            //     }
            //
            // swap
            //
            //     {
            //         "code" => 1000,
            //         "message" => "Ok",
            //         "trace" => "7f9c94e10f9d4513bc08a7bfc2a5559a.70.16954131323145323"
            //     }
            //
            return array( $this->safe_order(array( 'info' => $response )) );
        }) ();
    }

    public function fetch_orders_by_status($status, ?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($status, $symbol, $since, $limit, $params) {
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' fetchOrdersByStatus() requires a $symbol argument');
            }
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            if (!$market['spot']) {
                throw new NotSupported($this->id . ' fetchOrdersByStatus() does not support ' . $market['type'] . ' $orders, only spot $orders are accepted');
            }
            $request = array(
                'symbol' => $market['id'],
                'offset' => 1, // max offset * $limit < 500
                'N' => 100, // max $limit is 100
            );
            if ($status === 'open') {
                $request['status'] = 9;
            } elseif ($status === 'closed') {
                $request['status'] = 6;
            } elseif ($status === 'canceled') {
                $request['status'] = 8;
            } else {
                $request['status'] = $status;
            }
            $response = Async\await($this->privateGetSpotV3Orders ($this->extend($request, $params)));
            //
            // spot
            //
            //     {
            //         "message":"OK",
            //         "code":1000,
            //         "trace":"70e7d427-7436-4fb8-8cdd-97e1f5eadbe9",
            //         "data":{
            //             "current_page":1,
            //             "orders":array(
            //                 {
            //                     "order_id":2147601241,
            //                     "symbol":"BTC_USDT",
            //                     "create_time":1591099963000,
            //                     "side":"sell",
            //                     "type":"limit",
            //                     "price":"9000.00",
            //                     "price_avg":"0.00",
            //                     "size":"1.00000",
            //                     "notional":"9000.00000000",
            //                     "filled_notional":"0.00000000",
            //                     "filled_size":"0.00000",
            //                     "status":"4"
            //                 }
            //             )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $orders = $this->safe_list($data, 'orders', array());
            return $this->parse_orders($orders, $market, $since, $limit);
        }) ();
    }

    public function fetch_open_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             *
             * @see https://developer-pro.bitmart.com/en/spot/#current-open-orders-v4-signed
             * @see https://developer-pro.bitmart.com/en/futuresv2/#get-all-open-orders-keyed
             * @see https://developer-pro.bitmart.com/en/futuresv2/#get-all-current-plan-orders-keyed
             *
             * fetch all unfilled currently open orders
             * @param {string} $symbol unified $market $symbol
             * @param {int} [$since] the earliest time in ms to fetch open orders for
             * @param {int} [$limit] the maximum number of open order structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {boolean} [$params->marginMode] *spot* whether to fetch trades for margin orders or spot orders, defaults to spot orders (only isolated margin orders are supported)
             * @param {int} [$params->until] *spot* the latest time in ms to fetch orders for
             * @param {string} [$params->type] *swap* order $type, 'limit' or 'market'
             * @param {string} [$params->order_state] *swap* the order state, 'all' or 'partially_filled', default is 'all'
             * @param {string} [$params->orderType] *swap only* 'limit', 'market', or 'trailing'
             * @param {boolean} [$params->trailing] *swap only* set to true if you want to fetch $trailing orders
             * @param {boolean} [$params->trigger] *swap only* set to true if you want to fetch trigger orders
             * @param {string} [$params->stpMode] self-trade prevention only for spot, defaults to none, ['none', 'cancel_maker', 'cancel_taker', 'cancel_both']
             * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $market = null;
            $request = array();
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['symbol'] = $market['id'];
            }
            $type = null;
            $response = null;
            list($type, $params) = $this->handle_market_type_and_params('fetchOpenOrders', $market, $params);
            if ($type === 'spot') {
                if ($limit !== null) {
                    $request['limit'] = min ($limit, 200);
                }
                $marginMode = null;
                list($marginMode, $params) = $this->handle_margin_mode_and_params('fetchOpenOrders', $params);
                if ($marginMode === 'isolated') {
                    $request['orderMode'] = 'iso_margin';
                }
                if ($since !== null) {
                    $request['startTime'] = $since;
                }
                $until = $this->safe_integer_2($params, 'until', 'endTime');
                if ($until !== null) {
                    $params = $this->omit($params, array( 'endTime' ));
                    $request['endTime'] = $until;
                }
                $response = Async\await($this->privatePostSpotV4QueryOpenOrders ($this->extend($request, $params)));
            } elseif ($type === 'swap') {
                if ($limit !== null) {
                    $request['limit'] = min ($limit, 100);
                }
                $isTrigger = $this->safe_bool_2($params, 'stop', 'trigger');
                $params = $this->omit($params, array( 'stop', 'trigger' ));
                if ($isTrigger) {
                    $response = Async\await($this->privateGetContractPrivateCurrentPlanOrder ($this->extend($request, $params)));
                } else {
                    $trailing = $this->safe_bool($params, 'trailing', false);
                    $orderType = $this->safe_string($params, 'orderType');
                    $params = $this->omit($params, array( 'orderType', 'trailing' ));
                    if ($trailing) {
                        $orderType = 'trailing';
                    }
                    if ($orderType !== null) {
                        $request['type'] = $orderType;
                    }
                    $response = Async\await($this->privateGetContractPrivateGetOpenOrders ($this->extend($request, $params)));
                }
            } else {
                throw new NotSupported($this->id . ' fetchOpenOrders() does not support ' . $type . ' orders, only spot and swap orders are accepted');
            }
            //
            // spot
            //
            //     {
            //         "code" => 1000,
            //         "message" => "success",
            //         "data" => array(
            //             {
            //                 "orderId" => "183299373022163211",
            //                 "clientOrderId" => "183299373022163211",
            //                 "symbol" => "BTC_USDT",
            //                 "side" => "buy",
            //                 "orderMode" => "spot",
            //                 "type" => "limit",
            //                 "state" => "new",
            //                 "price" => "25000.00",
            //                 "priceAvg" => "0.00",
            //                 "size" => "0.00020",
            //                 "filledSize" => "0.00000",
            //                 "notional" => "5.00000000",
            //                 "filledNotional" => "0.00000000",
            //                 "createTime" => 1695703703338,
            //                 "updateTime" => 1695703703359
            //             }
            //         ),
            //         "trace" => "15f11d48e3234c81a2e786cr2e7a38e6.71.16957022303515933"
            //     }
            //
            // swap
            //
            //     {
            //         "code" => 1000,
            //         "message" => "Ok",
            //         "data" => array(
            //             {
            //                 "order_id" => "230935812485489",
            //                 "client_order_id" => "",
            //                 "price" => "24000",
            //                 "size" => "1",
            //                 "symbol" => "BTCUSDT",
            //                 "state" => 2,
            //                 "side" => 1,
            //                 "type" => "limit",
            //                 "leverage" => "10",
            //                 "open_type" => "isolated",
            //                 "deal_avg_price" => "0",
            //                 "deal_size" => "0",
            //                 "create_time" => 1695702258629,
            //                 "update_time" => 1695702258642
            //             }
            //         ),
            //         "trace" => "7f9d94g10f9d4513bc08a7rfc3a5559a.71.16957022303515933"
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_orders($data, $market, $since, $limit);
        }) ();
    }

    public function fetch_closed_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             *
             * @see https://developer-pro.bitmart.com/en/spot/#account-orders-v4-signed
             * @see https://developer-pro.bitmart.com/en/futuresv2/#get-order-history-keyed
             *
             * fetches information on multiple closed orders made by the user
             * @param {string} $symbol unified $market $symbol of the $market orders were made in
             * @param {int} [$since] the earliest time in ms to fetch orders for
             * @param {int} [$limit] the maximum number of order structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] timestamp in ms of the latest entry
             * @param {string} [$params->marginMode] *spot only* 'cross' or 'isolated', for margin trading
             * @param {string} [$params->stpMode] self-trade prevention only for spot, defaults to none, ['none', 'cancel_maker', 'cancel_taker', 'cancel_both']
             * @return {Order[]} a list of ~@link https://docs.ccxt.com/#/?id=order-structure order structures~
             */
            Async\await($this->load_markets());
            $market = null;
            $request = array();
            if ($symbol !== null) {
                $market = $this->market($symbol);
                $request['symbol'] = $market['id'];
            }
            $type = null;
            list($type, $params) = $this->handle_market_type_and_params('fetchClosedOrders', $market, $params);
            if ($type !== 'spot') {
                if ($symbol === null) {
                    throw new ArgumentsRequired($this->id . ' fetchClosedOrders() requires a $symbol argument');
                }
            }
            if ($since !== null) {
                $startTimeKey = ($type === 'spot') ? 'startTime' : 'start_time';
                $request[$startTimeKey] = $since;
            }
            $endTimeKey = ($type === 'spot') ? 'endTime' : 'end_time';
            $until = $this->safe_integer_2($params, 'until', $endTimeKey);
            if ($until !== null) {
                $params = $this->omit($params, array( 'until' ));
                $request[$endTimeKey] = $until;
            }
            $response = null;
            if ($type === 'spot') {
                $marginMode = null;
                list($marginMode, $params) = $this->handle_margin_mode_and_params('fetchClosedOrders', $params);
                if ($marginMode === 'isolated') {
                    $request['orderMode'] = 'iso_margin';
                }
                $response = Async\await($this->privatePostSpotV4QueryHistoryOrders ($this->extend($request, $params)));
            } else {
                $response = Async\await($this->privateGetContractPrivateOrderHistory ($this->extend($request, $params)));
            }
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_orders($data, $market, $since, $limit);
        }) ();
    }

    public function fetch_canceled_orders(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetches information on multiple canceled orders made by the user
             * @param {string} $symbol unified market $symbol of the market orders were made in
             * @param {int} [$since] timestamp in ms of the earliest order, default is null
             * @param {int} [$limit] max number of orders to return, default is null
             * @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~
             */
            return Async\await($this->fetch_orders_by_status('canceled', $symbol, $since, $limit, $params));
        }) ();
    }

    public function fetch_order(string $id, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($id, $symbol, $params) {
            /**
             * fetches information on an order made by the user
             *
             * @see https://developer-pro.bitmart.com/en/spot/#query-order-by-$id-v4-signed
             * @see https://developer-pro.bitmart.com/en/spot/#query-order-by-clientorderid-v4-signed
             * @see https://developer-pro.bitmart.com/en/futuresv2/#get-order-detail-keyed
             *
             * @param {string} $id the $id of the order
             * @param {string} $symbol unified $symbol of the $market the order was made in
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->clientOrderId] *spot* fetch the order by client order $id instead of order $id
             * @param {string} [$params->orderType] *swap only* 'limit', 'market', 'liquidate', 'bankruptcy', 'adl' or 'trailing'
             * @param {boolean} [$params->trailing] *swap only* set to true if you want to fetch a $trailing order
             * @param {string} [$params->stpMode] self-trade prevention only for spot, defaults to none, ['none', 'cancel_maker', 'cancel_taker', 'cancel_both']
             * @return {array} An ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
             */
            Async\await($this->load_markets());
            $request = array();
            $type = null;
            $market = null;
            $response = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
            }
            list($type, $params) = $this->handle_market_type_and_params('fetchOrder', $market, $params);
            if ($type === 'spot') {
                $clientOrderId = $this->safe_string($params, 'clientOrderId');
                if (!$clientOrderId) {
                    $request['orderId'] = $id;
                }
                if ($clientOrderId !== null) {
                    $response = Async\await($this->privatePostSpotV4QueryClientOrder ($this->extend($request, $params)));
                } else {
                    $response = Async\await($this->privatePostSpotV4QueryOrder ($this->extend($request, $params)));
                }
            } elseif ($type === 'swap') {
                if ($symbol === null) {
                    throw new ArgumentsRequired($this->id . ' fetchOrder() requires a $symbol argument');
                }
                $trailing = $this->safe_bool($params, 'trailing', false);
                $orderType = $this->safe_string($params, 'orderType');
                $params = $this->omit($params, array( 'orderType', 'trailing' ));
                if ($trailing) {
                    $orderType = 'trailing';
                }
                if ($orderType !== null) {
                    $request['type'] = $orderType;
                }
                $request['symbol'] = $market['id'];
                $request['order_id'] = $id;
                $response = Async\await($this->privateGetContractPrivateOrder ($this->extend($request, $params)));
            }
            //
            // spot
            //
            //     {
            //         "code" => 1000,
            //         "message" => "success",
            //         "data" => array(
            //             "orderId" => "183347420821295423",
            //             "clientOrderId" => "183347420821295423",
            //             "symbol" => "BTC_USDT",
            //             "side" => "buy",
            //             "orderMode" => "spot",
            //             "type" => "limit",
            //             "state" => "new",
            //             "price" => "24000.00",
            //             "priceAvg" => "0.00",
            //             "size" => "0.00022",
            //             "filledSize" => "0.00000",
            //             "notional" => "5.28000000",
            //             "filledNotional" => "0.00000000",
            //             "createTime" => 1695783014734,
            //             "updateTime" => 1695783014762
            //         ),
            //         "trace" => "ce3e6422c8b44d5fag855348a68693ed.63.14957831547451715"
            //     }
            //
            // swap
            //
            //     {
            //         "code" => 1000,
            //         "message" => "Ok",
            //         "data" => array(
            //             "order_id" => "230927283405028",
            //             "client_order_id" => "",
            //             "price" => "23000",
            //             "size" => "1",
            //             "symbol" => "BTCUSDT",
            //             "state" => 2,
            //             "side" => 1,
            //             "type" => "limit",
            //             "leverage" => "10",
            //             "open_type" => "isolated",
            //             "deal_avg_price" => "0",
            //             "deal_size" => "0",
            //             "create_time" => 1695783433600,
            //             "update_time" => 1695783433613
            //         ),
            //         "trace" => "4cad855075664097af6ba5257c47605d.63.14957831547451715"
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_order($data, $market);
        }) ();
    }

    public function fetch_deposit_address(string $code, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $params) {
            /**
             * fetch the deposit address for a $currency associated with this account
             *
             * @see https://developer-pro.bitmart.com/en/spot/#deposit-address-keyed
             *
             * @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~
             */
            Async\await($this->load_markets());
            $currency = $this->currency($code);
            $network = null;
            list($network, $params) = $this->handle_network_code_and_params($params);
            $request = array(
                'currency' => $this->get_currency_id_from_code_and_network($code, $network),
            );
            $response = Async\await($this->privateGetAccountV1DepositAddress ($this->extend($request, $params)));
            //
            //    {
            //        "message" => "OK",
            //        "code" => 1000,
            //        "trace" => "0e6edd79-f77f-4251-abe5-83ba75d06c1a",
            //        "data" => {
            //            $currency => 'ETH',
            //            chain => 'Ethereum',
            //            address => '0x99B5EEc2C520f86F0F62F05820d28D05D36EccCf',
            //            address_memo => ''
            //        }
            //    }
            //
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_deposit_address($data, $currency);
        }) ();
    }

    public function parse_deposit_address($depositAddress, $currency = null): array {
        //
        // fetchDepositAddress
        //    {
        //        $currency => 'ETH',
        //        chain => 'Ethereum',
        //        $address => '0x99B5EEc2C520f86F0F62F05820d28D05D36EccCf',
        //        address_memo => ''
        //    }
        //
        // fetchWithdrawAddress
        //     {
        //         "currency" => "ETH",
        //         "network" => "ETH",
        //         "address" => "0x1121",
        //         "memo" => "12",
        //         "remark" => "12",
        //         "addressType" => 0,
        //         "verifyStatus" => 0
        //     }
        //
        $currencyId = $this->safe_string($depositAddress, 'currency');
        $network = $this->safe_string_2($depositAddress, 'chain', 'network');
        if (mb_strpos($currencyId, 'NFT') === false) {
            $parts = explode('-', $currencyId);
            $currencyId = $this->safe_string($parts, 0);
            $secondPart = $this->safe_string($parts, 1);
            if ($secondPart !== null) {
                $network = $secondPart;
            }
        }
        $address = $this->safe_string($depositAddress, 'address');
        $currency = $this->safe_currency($currencyId, $currency);
        $this->check_address($address);
        return array(
            'info' => $depositAddress,
            'currency' => $this->safe_string($currency, 'code'),
            'network' => $this->network_id_to_code($network),
            'address' => $address,
            'tag' => $this->safe_string_2($depositAddress, 'address_memo', 'memo'),
        );
    }

    public function withdraw(string $code, float $amount, string $address, ?string $tag = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $amount, $address, $tag, $params) {
            /**
             * make a withdrawal
             *
             * @see https://developer-pro.bitmart.com/en/spot/#withdraw-signed
             *
             * @param {string} $code unified $currency $code
             * @param {float} $amount the $amount to withdraw
             * @param {string} $address the $address to withdraw to
             * @param {string} $tag
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->network] the $network name for this withdrawal
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=$transaction-structure $transaction structure~
             */
            list($tag, $params) = $this->handle_withdraw_tag_and_params($tag, $params);
            $this->check_address($address);
            Async\await($this->load_markets());
            $currency = $this->currency($code);
            $network = null;
            list($network, $params) = $this->handle_network_code_and_params($params);
            $request = array(
                'currency' => $this->get_currency_id_from_code_and_network($code, $network),
                'amount' => $amount,
                'destination' => 'To Digital Address', // To Digital Address, To Binance, To OKEX
                'address' => $address,
            );
            if ($tag !== null) {
                $request['address_memo'] = $tag;
            }
            $response = Async\await($this->privatePostAccountV1WithdrawApply ($this->extend($request, $params)));
            //
            //     {
            //         "code" => 1000,
            //         "trace":"886fb6ae-456b-4654-b4e0-d681ac05cea1",
            //         "message" => "OK",
            //         "data" => {
            //             "withdraw_id" => "121212"
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $transaction = $this->parse_transaction($data, $currency);
            return $this->extend($transaction, array(
                'code' => $code,
                'address' => $address,
                'tag' => $tag,
            ));
        }) ();
    }

    public function fetch_transactions_by_type($type, ?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($type, $code, $since, $limit, $params) {
            Async\await($this->load_markets());
            if ($limit === null) {
                $limit = 1000; // max 1000
            }
            $request = array(
                'operation_type' => $type, // deposit or withdraw
                'N' => $limit,
            );
            $currency = null;
            if ($code !== null) {
                $currency = $this->currency($code);
                $request['currency'] = $currency['id'];
            }
            if ($since !== null) {
                $request['startTime'] = $since;
            }
            $until = $this->safe_integer($params, 'until');
            if ($until !== null) {
                $params = $this->omit($params, 'until');
                $request['endTime'] = $until;
            }
            $response = Async\await($this->privateGetAccountV2DepositWithdrawHistory ($this->extend($request, $params)));
            //
            //     {
            //         "message":"OK",
            //         "code":1000,
            //         "trace":"142bf92a-fc50-4689-92b6-590886f90b97",
            //         "data":{
            //             "records":array(
            //                 array(
            //                     "withdraw_id":"1679952",
            //                     "deposit_id":"",
            //                     "operation_type":"withdraw",
            //                     "currency":"BMX",
            //                     "apply_time":1588867374000,
            //                     "arrival_amount":"59.000000000000",
            //                     "fee":"1.000000000000",
            //                     "status":0,
            //                     "address":"0xe57b69a8776b37860407965B73cdFFBDFe668Bb5",
            //                     "address_memo":"",
            //                     "tx_id":""
            //                 ),
            //             )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $records = $this->safe_list($data, 'records', array());
            return $this->parse_transactions($records, $currency, $since, $limit);
        }) ();
    }

    public function fetch_deposit(string $id, ?string $code = null, $params = array ()) {
        return Async\async(function () use ($id, $code, $params) {
            /**
             * fetch information on a deposit
             *
             * @see https://developer-pro.bitmart.com/en/spot/#get-a-deposit-or-withdraw-detail-keyed
             *
             * @param {string} $id deposit $id
             * @param {string} $code not used by bitmart fetchDeposit ()
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?$id=transaction-structure transaction structure~
             */
            Async\await($this->load_markets());
            $request = array(
                'id' => $id,
            );
            $response = Async\await($this->privateGetAccountV1DepositWithdrawDetail ($this->extend($request, $params)));
            //
            //     {
            //         "message":"OK",
            //         "code":1000,
            //         "trace":"f7f74924-14da-42a6-b7f2-d3799dd9a612",
            //         "data":{
            //             "record":{
            //                 "withdraw_id":"",
            //                 "deposit_id":"1679952",
            //                 "operation_type":"deposit",
            //                 "currency":"BMX",
            //                 "apply_time":1588867374000,
            //                 "arrival_amount":"59.000000000000",
            //                 "fee":"1.000000000000",
            //                 "status":0,
            //                 "address":"0xe57b69a8776b37860407965B73cdFFBDFe668Bb5",
            //                 "address_memo":"",
            //                 "tx_id":""
            //             }
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $record = $this->safe_dict($data, 'record', array());
            return $this->parse_transaction($record);
        }) ();
    }

    public function fetch_deposits(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch all deposits made to an account
             *
             * @see https://developer-pro.bitmart.com/en/spot/#get-deposit-and-withdraw-history-keyed
             *
             * @param {string} $code unified currency $code
             * @param {int} [$since] the earliest time in ms to fetch deposits for
             * @param {int} [$limit] the maximum number of deposits structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
             */
            return Async\await($this->fetch_transactions_by_type('deposit', $code, $since, $limit, $params));
        }) ();
    }

    public function fetch_withdrawal(string $id, ?string $code = null, $params = array ()) {
        return Async\async(function () use ($id, $code, $params) {
            /**
             * fetch $data on a currency withdrawal via the withdrawal $id
             *
             * @see https://developer-pro.bitmart.com/en/spot/#get-a-deposit-or-withdraw-detail-keyed
             *
             * @param {string} $id withdrawal $id
             * @param {string} $code not used by bitmart.fetchWithdrawal
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?$id=transaction-structure transaction structure~
             */
            Async\await($this->load_markets());
            $request = array(
                'id' => $id,
            );
            $response = Async\await($this->privateGetAccountV1DepositWithdrawDetail ($this->extend($request, $params)));
            //
            //     {
            //         "message":"OK",
            //         "code":1000,
            //         "trace":"f7f74924-14da-42a6-b7f2-d3799dd9a612",
            //         "data":{
            //             "record":{
            //                 "withdraw_id":"1679952",
            //                 "deposit_id":"",
            //                 "operation_type":"withdraw",
            //                 "currency":"BMX",
            //                 "apply_time":1588867374000,
            //                 "arrival_amount":"59.000000000000",
            //                 "fee":"1.000000000000",
            //                 "status":0,
            //                 "address":"0xe57b69a8776b37860407965B73cdFFBDFe668Bb5",
            //                 "address_memo":"",
            //                 "tx_id":""
            //             }
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $record = $this->safe_dict($data, 'record', array());
            return $this->parse_transaction($record);
        }) ();
    }

    public function fetch_withdrawals(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch all withdrawals made from an account
             *
             * @see https://developer-pro.bitmart.com/en/spot/#get-deposit-and-withdraw-history-keyed
             *
             * @param {string} $code unified currency $code
             * @param {int} [$since] the earliest time in ms to fetch withdrawals for
             * @param {int} [$limit] the maximum number of withdrawals structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transaction-structure transaction structures~
             */
            return Async\await($this->fetch_transactions_by_type('withdraw', $code, $since, $limit, $params));
        }) ();
    }

    public function parse_transaction_status(?string $status) {
        $statuses = array(
            '0' => 'pending', // Create
            '1' => 'pending', // Submitted, waiting for withdrawal
            '2' => 'pending', // Processing
            '3' => 'ok', // Success
            '4' => 'canceled', // Cancel
            '5' => 'failed', // Fail
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_transaction(array $transaction, ?array $currency = null): array {
        //
        // withdraw
        //
        //     {
        //         "withdraw_id" => "121212"
        //     }
        //
        // fetchDeposits, fetchWithdrawals, fetchWithdrawal
        //
        //     {
        //         "withdraw_id":"1679952",
        //         "deposit_id":"",
        //         "operation_type":"withdraw",
        //         "currency":"BMX",
        //         "apply_time":1588867374000,
        //         "arrival_amount":"59.000000000000",
        //         "fee":"1.000000000000",
        //         "status":0,
        //         "address":"0xe57b69a8776b37860407965B73cdFFBDFe668Bb5",
        //         "address_memo":"",
        //         "tx_id":""
        //     }
        //
        $id = null;
        $withdrawId = $this->safe_string($transaction, 'withdraw_id');
        $depositId = $this->safe_string($transaction, 'deposit_id');
        $type = null;
        if (($withdrawId !== null) && ($withdrawId !== '')) {
            $type = 'withdraw';
            $id = $withdrawId;
        } elseif (($depositId !== null) && ($depositId !== '')) {
            $type = 'deposit';
            $id = $depositId;
        }
        $amount = $this->safe_number($transaction, 'arrival_amount');
        $timestamp = $this->safe_integer($transaction, 'apply_time');
        $currencyId = $this->safe_string($transaction, 'currency');
        $networkId = null;
        if ($currencyId !== null) {
            if (mb_strpos($currencyId, 'NFT') === false) {
                $parts = explode('-', $currencyId);
                $currencyId = $this->safe_string($parts, 0);
                $networkId = $this->safe_string($parts, 1);
            }
        }
        $code = $this->safe_currency_code($currencyId, $currency);
        $status = $this->parse_transaction_status($this->safe_string($transaction, 'status'));
        $feeCost = $this->safe_number($transaction, 'fee');
        $fee = null;
        if ($feeCost !== null) {
            $fee = array(
                'cost' => $feeCost,
                'currency' => $code,
            );
        }
        $txid = $this->safe_string($transaction, 'tx_id');
        $address = $this->safe_string($transaction, 'address');
        $tag = $this->safe_string($transaction, 'address_memo');
        return array(
            'info' => $transaction,
            'id' => $id,
            'currency' => $code,
            'amount' => $amount,
            'network' => $this->network_id_to_code($networkId),
            'address' => $address,
            'addressFrom' => null,
            'addressTo' => null,
            'tag' => $tag,
            'tagFrom' => null,
            'tagTo' => null,
            'status' => $status,
            'type' => $type,
            'updated' => null,
            'txid' => $txid,
            'internal' => null,
            'comment' => null,
            'timestamp' => ($timestamp !== 0) ? $timestamp : null,
            'datetime' => ($timestamp !== 0) ? $this->iso8601($timestamp) : null,
            'fee' => $fee,
        );
    }

    public function repay_isolated_margin(string $symbol, string $code, $amount, $params = array ()) {
        return Async\async(function () use ($symbol, $code, $amount, $params) {
            /**
             * repay borrowed margin and interest
             *
             * @see https://developer-pro.bitmart.com/en/spot/#margin-repay-isolated-signed
             *
             * @param {string} $symbol unified $market $symbol
             * @param {string} $code unified $currency $code of the $currency to repay
             * @param {string} $amount the $amount to repay
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=margin-loan-structure margin loan structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $currency = $this->currency($code);
            $request = array(
                'symbol' => $market['id'],
                'currency' => $currency['id'],
                'amount' => $this->currency_to_precision($code, $amount),
            );
            $response = Async\await($this->privatePostSpotV1MarginIsolatedRepay ($this->extend($request, $params)));
            //
            //     {
            //         "message" => "OK",
            //         "code" => 1000,
            //         "trace" => "b0a60b4c-e986-4b54-a190-8f7c05ddf685",
            //         "data" => {
            //             "repay_id" => "2afcc16d99bd4707818c5a355dc89bed"
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $transaction = $this->parse_margin_loan($data, $currency);
            return $this->extend($transaction, array(
                'amount' => $amount,
                'symbol' => $symbol,
            ));
        }) ();
    }

    public function borrow_isolated_margin(string $symbol, string $code, float $amount, $params = array ()) {
        return Async\async(function () use ($symbol, $code, $amount, $params) {
            /**
             * create a loan to borrow margin
             *
             * @see https://developer-pro.bitmart.com/en/spot/#margin-borrow-isolated-signed
             *
             * @param {string} $symbol unified $market $symbol
             * @param {string} $code unified $currency $code of the $currency to borrow
             * @param {string} $amount the $amount to borrow
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=margin-loan-structure margin loan structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $currency = $this->currency($code);
            $request = array(
                'symbol' => $market['id'],
                'currency' => $currency['id'],
                'amount' => $this->currency_to_precision($code, $amount),
            );
            $response = Async\await($this->privatePostSpotV1MarginIsolatedBorrow ($this->extend($request, $params)));
            //
            //     {
            //         "message" => "OK",
            //         "code" => 1000,
            //         "trace" => "e6fda683-181e-4e78-ac9c-b27c4c8ba035",
            //         "data" => {
            //             "borrow_id" => "629a7177a4ed4cf09869c6a4343b788c"
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $transaction = $this->parse_margin_loan($data, $currency);
            return $this->extend($transaction, array(
                'amount' => $amount,
                'symbol' => $symbol,
            ));
        }) ();
    }

    public function parse_margin_loan($info, ?array $currency = null) {
        //
        // borrowMargin
        //
        //     {
        //         "borrow_id" => "629a7177a4ed4cf09869c6a4343b788c",
        //     }
        //
        // repayMargin
        //
        //     {
        //         "repay_id" => "2afcc16d99bd4707818c5a355dc89bed",
        //     }
        //
        return array(
            'id' => $this->safe_string_2($info, 'borrow_id', 'repay_id'),
            'currency' => $this->safe_currency_code(null, $currency),
            'amount' => null,
            'symbol' => null,
            'timestamp' => null,
            'datetime' => null,
            'info' => $info,
        );
    }

    public function fetch_isolated_borrow_rate(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetch the rate of interest to borrow a currency for margin trading
             *
             * @see https://developer-pro.bitmart.com/en/spot/#get-trading-pair-borrowing-rate-and-amount-keyed
             *
             * @param {string} $symbol unified $symbol of the $market to fetch the borrow rate for
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an {@link https://github.com/ccxt/ccxt/wiki/Manual#isolated-borrow-rate-structure isolated borrow rate structure}
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'symbol' => $market['id'],
            );
            $response = Async\await($this->privateGetSpotV1MarginIsolatedPairs ($this->extend($request, $params)));
            //
            //     {
            //         "message" => "OK",
            //         "code" => 1000,
            //         "trace" => "0985a130-a5ae-4fc1-863f-4704e214f585",
            //         "data" => {
            //             "symbols" => array(
            //                 {
            //                     "symbol" => "BTC_USDT",
            //                     "max_leverage" => "5",
            //                     "symbol_enabled" => true,
            //                     "base" => array(
            //                         "currency" => "BTC",
            //                         "daily_interest" => "0.00055000",
            //                         "hourly_interest" => "0.00002291",
            //                         "max_borrow_amount" => "2.00000000",
            //                         "min_borrow_amount" => "0.00000001",
            //                         "borrowable_amount" => "0.00670810"
            //                     ),
            //                     "quote" => {
            //                         "currency" => "USDT",
            //                         "daily_interest" => "0.00055000",
            //                         "hourly_interest" => "0.00002291",
            //                         "max_borrow_amount" => "50000.00000000",
            //                         "min_borrow_amount" => "0.00000001",
            //                         "borrowable_amount" => "135.12575038"
            //                     }
            //                 }
            //             )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $symbols = $this->safe_list($data, 'symbols', array());
            $borrowRate = $this->safe_dict($symbols, 0, array());
            return $this->parse_isolated_borrow_rate($borrowRate, $market);
        }) ();
    }

    public function parse_isolated_borrow_rate(array $info, ?array $market = null): array {
        //
        //     {
        //         "symbol" => "BTC_USDT",
        //         "max_leverage" => "5",
        //         "symbol_enabled" => true,
        //         "base" => array(
        //             "currency" => "BTC",
        //             "daily_interest" => "0.00055000",
        //             "hourly_interest" => "0.00002291",
        //             "max_borrow_amount" => "2.00000000",
        //             "min_borrow_amount" => "0.00000001",
        //             "borrowable_amount" => "0.00670810"
        //         ),
        //         "quote" => {
        //             "currency" => "USDT",
        //             "daily_interest" => "0.00055000",
        //             "hourly_interest" => "0.00002291",
        //             "max_borrow_amount" => "50000.00000000",
        //             "min_borrow_amount" => "0.00000001",
        //             "borrowable_amount" => "135.12575038"
        //         }
        //     }
        //
        $marketId = $this->safe_string($info, 'symbol');
        $symbol = $this->safe_symbol($marketId, $market);
        $baseData = $this->safe_dict($info, 'base', array());
        $quoteData = $this->safe_dict($info, 'quote', array());
        $baseId = $this->safe_string($baseData, 'currency');
        $quoteId = $this->safe_string($quoteData, 'currency');
        return array(
            'symbol' => $symbol,
            'base' => $this->safe_currency_code($baseId),
            'baseRate' => $this->safe_number($baseData, 'hourly_interest'),
            'quote' => $this->safe_currency_code($quoteId),
            'quoteRate' => $this->safe_number($quoteData, 'hourly_interest'),
            'period' => 3600000, // 1-Hour
            'timestamp' => null,
            'datetime' => null,
            'info' => $info,
        );
    }

    public function fetch_isolated_borrow_rates($params = array ()): PromiseInterface {
        return Async\async(function () use ($params) {
            /**
             * fetch the borrow interest rates of all currencies, currently only works for isolated margin
             *
             * @see https://developer-pro.bitmart.com/en/spot/#get-trading-pair-borrowing-rate-and-amount-keyed
             *
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a list of ~@link https://docs.ccxt.com/#/?id=isolated-borrow-rate-structure isolated borrow rate structures~
             */
            Async\await($this->load_markets());
            $response = Async\await($this->privateGetSpotV1MarginIsolatedPairs ($params));
            //
            //     {
            //         "message" => "OK",
            //         "code" => 1000,
            //         "trace" => "0985a130-a5ae-4fc1-863f-4704e214f585",
            //         "data" => {
            //             "symbols" => array(
            //                 {
            //                     "symbol" => "BTC_USDT",
            //                     "max_leverage" => "5",
            //                     "symbol_enabled" => true,
            //                     "base" => array(
            //                         "currency" => "BTC",
            //                         "daily_interest" => "0.00055000",
            //                         "hourly_interest" => "0.00002291",
            //                         "max_borrow_amount" => "2.00000000",
            //                         "min_borrow_amount" => "0.00000001",
            //                         "borrowable_amount" => "0.00670810"
            //                     ),
            //                     "quote" => {
            //                         "currency" => "USDT",
            //                         "daily_interest" => "0.00055000",
            //                         "hourly_interest" => "0.00002291",
            //                         "max_borrow_amount" => "50000.00000000",
            //                         "min_borrow_amount" => "0.00000001",
            //                         "borrowable_amount" => "135.12575038"
            //                     }
            //                 }
            //             )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $symbols = $this->safe_list($data, 'symbols', array());
            return $this->parse_isolated_borrow_rates($symbols);
        }) ();
    }

    public function transfer(string $code, float $amount, string $fromAccount, string $toAccount, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $amount, $fromAccount, $toAccount, $params) {
            /**
             * transfer $currency internally between wallets on the same account, currently only supports transfer between spot and margin
             *
             * @see https://developer-pro.bitmart.com/en/spot/#margin-asset-transfer-signed
             * @see https://developer-pro.bitmart.com/en/futuresv2/#transfer-signed
             *
             * @param {string} $code unified $currency $code
             * @param {float} $amount amount to transfer
             * @param {string} $fromAccount account to transfer from
             * @param {string} $toAccount account to transfer to
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=transfer-structure transfer structure~
             */
            Async\await($this->load_markets());
            $currency = $this->currency($code);
            $amountToPrecision = $this->currency_to_precision($code, $amount);
            $request = array(
                'amount' => $amountToPrecision,
                'currency' => $currency['id'],
            );
            $fromId = $this->convert_type_to_account($fromAccount);
            $toId = $this->convert_type_to_account($toAccount);
            if ($fromAccount === 'spot') {
                if ($toAccount === 'margin') {
                    $request['side'] = 'in';
                    $request['symbol'] = $toId;
                } elseif ($toAccount === 'swap') {
                    $request['type'] = 'spot_to_contract';
                }
            } elseif ($toAccount === 'spot') {
                if ($fromAccount === 'margin') {
                    $request['side'] = 'out';
                    $request['symbol'] = $fromId;
                } elseif ($fromAccount === 'swap') {
                    $request['type'] = 'contract_to_spot';
                }
            } else {
                throw new ArgumentsRequired($this->id . ' transfer() requires either $fromAccount or $toAccount to be spot');
            }
            $response = null;
            if (($fromAccount === 'margin') || ($toAccount === 'margin')) {
                $response = Async\await($this->privatePostSpotV1MarginIsolatedTransfer ($this->extend($request, $params)));
            } elseif (($fromAccount === 'swap') || ($toAccount === 'swap')) {
                $response = Async\await($this->privatePostAccountV1TransferContract ($this->extend($request, $params)));
            }
            //
            // margin
            //
            //     {
            //         "message" => "OK",
            //         "code" => 1000,
            //         "trace" => "b26cecec-ef5a-47d9-9531-2bd3911d3d55",
            //         "data" => {
            //             "transfer_id" => "ca90d97a621e47d49774f19af6b029f5"
            //         }
            //     }
            //
            // swap
            //
            //     {
            //         "message" => "OK",
            //         "code" => 1000,
            //         "trace" => "4cad858074667097ac6ba5257c57305d.68.16953302431189455",
            //         "data" => {
            //             "currency" => "USDT",
            //             "amount" => "5"
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            return $this->extend($this->parse_transfer($data, $currency), array(
                'status' => $this->parse_transfer_status($this->safe_string_2($response, 'code', 'message')),
            ));
        }) ();
    }

    public function parse_transfer_status(?string $status): ?string {
        $statuses = array(
            '1000' => 'ok',
            'OK' => 'ok',
            'FINISHED' => 'ok',
        );
        return $this->safe_string($statuses, $status, $status);
    }

    public function parse_transfer_to_account($type) {
        $types = array(
            'contract_to_spot' => 'spot',
            'spot_to_contract' => 'swap',
        );
        return $this->safe_string($types, $type, $type);
    }

    public function parse_transfer_from_account($type) {
        $types = array(
            'contract_to_spot' => 'swap',
            'spot_to_contract' => 'spot',
        );
        return $this->safe_string($types, $type, $type);
    }

    public function parse_transfer(array $transfer, ?array $currency = null): array {
        //
        // margin
        //
        //     {
        //         "transfer_id" => "ca90d97a621e47d49774f19af6b029f5"
        //     }
        //
        // swap
        //
        //     {
        //         "currency" => "USDT",
        //         "amount" => "5"
        //     }
        //
        // fetchTransfers
        //
        //     {
        //         "transfer_id" => "902463535961567232",
        //         "currency" => "USDT",
        //         "amount" => "5",
        //         "type" => "contract_to_spot",
        //         "state" => "FINISHED",
        //         "timestamp" => 1695330539565
        //     }
        //
        $currencyId = $this->safe_string($transfer, 'currency');
        $timestamp = $this->safe_integer($transfer, 'timestamp');
        return array(
            'id' => $this->safe_string($transfer, 'transfer_id'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'currency' => $this->safe_currency_code($currencyId, $currency),
            'amount' => $this->safe_number($transfer, 'amount'),
            'fromAccount' => $this->parse_transfer_from_account($this->safe_string($transfer, 'type')),
            'toAccount' => $this->parse_transfer_to_account($this->safe_string($transfer, 'type')),
            'status' => $this->parse_transfer_status($this->safe_string($transfer, 'state')),
        );
    }

    public function fetch_transfers(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch a history of internal transfers made on an account, only transfers between spot and swap are supported
             *
             * @see https://developer-pro.bitmart.com/en/futuresv2/#get-transfer-list-signed
             *
             * @param {string} $code unified $currency $code of the $currency transferred
             * @param {int} [$since] the earliest time in ms to fetch transfers for
             * @param {int} [$limit] the maximum number of transfer structures to retrieve
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->page] the required number of pages, default is 1, max is 1000
             * @param {int} [$params->until] the latest time in ms to fetch transfers for
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=transfer-structure transfer structures~
             */
            Async\await($this->load_markets());
            if ($limit === null) {
                $limit = 10;
            }
            $request = array(
                'page' => $this->safe_integer($params, 'page', 1), // default is 1, max is 1000
                'limit' => $limit, // default is 10, max is 100
            );
            $currency = null;
            if ($code !== null) {
                $currency = $this->currency($code);
                $request['currency'] = $currency['id'];
            }
            if ($since !== null) {
                $request['time_start'] = $since;
            }
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $until = $this->safe_integer($params, 'until'); // unified in milliseconds
            $endTime = $this->safe_integer($params, 'time_end', $until); // exchange-specific in milliseconds
            $params = $this->omit($params, array( 'until' ));
            if ($endTime !== null) {
                $request['time_end'] = $endTime;
            }
            $response = Async\await($this->privatePostAccountV1TransferContractList ($this->extend($request, $params)));
            //
            //     {
            //         "message" => "OK",
            //         "code" => 1000,
            //         "trace" => "7f9d93e10f9g4513bc08a7btc2a5559a.69.16953325693032193",
            //         "data" => {
            //             "records" => array(
            //                 array(
            //                     "transfer_id" => "902463535961567232",
            //                     "currency" => "USDT",
            //                     "amount" => "5",
            //                     "type" => "contract_to_spot",
            //                     "state" => "FINISHED",
            //                     "timestamp" => 1695330539565
            //                 ),
            //             )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $records = $this->safe_list($data, 'records', array());
            return $this->parse_transfers($records, $currency, $since, $limit);
        }) ();
    }

    public function fetch_borrow_interest(?string $code = null, ?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $symbol, $since, $limit, $params) {
            /**
             * fetch the $interest owed by the user for borrowing currency for margin trading
             *
             * @see https://developer-pro.bitmart.com/en/spot/#get-borrow-record-isolated-keyed
             *
             * @param {string} $code unified currency $code
             * @param {string} $symbol unified $market $symbol when fetch $interest in isolated markets
             * @param {int} [$since] the earliest time in ms to fetch borrrow $interest for
             * @param {int} [$limit] the maximum number of 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=borrow-$interest-structure borrow $interest structures~
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' fetchBorrowInterest() requires a $symbol argument');
            }
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'symbol' => $market['id'],
            );
            if ($limit !== null) {
                $request['N'] = $limit;
            }
            if ($since !== null) {
                $request['start_time'] = $since;
            }
            $response = Async\await($this->privateGetSpotV1MarginIsolatedBorrowRecord ($this->extend($request, $params)));
            //
            //     {
            //         "message" => "OK",
            //         "code" => 1000,
            //         "trace" => "8ea27a2a-4aba-49fa-961d-43a0137b0ef3",
            //         "data" => {
            //             "records" => array(
            //                 array(
            //                     "borrow_id" => "1659045283903rNvJnuRTJNL5J53n",
            //                     "symbol" => "BTC_USDT",
            //                     "currency" => "USDT",
            //                     "borrow_amount" => "100.00000000",
            //                     "daily_interest" => "0.00055000",
            //                     "hourly_interest" => "0.00002291",
            //                     "interest_amount" => "0.00229166",
            //                     "create_time" => 1659045284000
            //                 ),
            //             )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $rows = $this->safe_list($data, 'records', array());
            $interest = $this->parse_borrow_interests($rows, $market);
            return $this->filter_by_currency_since_limit($interest, $code, $since, $limit);
        }) ();
    }

    public function parse_borrow_interest(array $info, ?array $market = null): array {
        //
        //     {
        //         "borrow_id" => "1657664327844Lk5eJJugXmdHHZoe",
        //         "symbol" => "BTC_USDT",
        //         "currency" => "USDT",
        //         "borrow_amount" => "20.00000000",
        //         "daily_interest" => "0.00055000",
        //         "hourly_interest" => "0.00002291",
        //         "interest_amount" => "0.00045833",
        //         "create_time" => 1657664329000
        //     }
        //
        $marketId = $this->safe_string($info, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $timestamp = $this->safe_integer($info, 'create_time');
        return array(
            'info' => $info,
            'symbol' => $this->safe_string($market, 'symbol'),
            'currency' => $this->safe_currency_code($this->safe_string($info, 'currency')),
            'interest' => $this->safe_number($info, 'interest_amount'),
            'interestRate' => $this->safe_number($info, 'hourly_interest'),
            'amountBorrowed' => $this->safe_number($info, 'borrow_amount'),
            'marginMode' => 'isolated',
            'timestamp' => $timestamp,  // borrow creation time
            'datetime' => $this->iso8601($timestamp),
        );
    }

    public function fetch_open_interest(string $symbol, $params = array ()) {
        return Async\async(function () use ($symbol, $params) {
            /**
             * Retrieves the open interest of a currency
             *
             * @see https://developer-pro.bitmart.com/en/futuresv2/#get-futures-openinterest
             *
             * @param {string} $symbol Unified CCXT $market $symbol
             * @param {array} [$params] exchange specific parameters
             * @return {array} an open interest structurearray(@link https://docs.ccxt.com/#/?id=open-interest-structure)
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            if (!$market['contract']) {
                throw new BadRequest($this->id . ' fetchOpenInterest() supports contract markets only');
            }
            $request = array(
                'symbol' => $market['id'],
            );
            $response = Async\await($this->publicGetContractPublicOpenInterest ($this->extend($request, $params)));
            //
            //     {
            //         "code" => 1000,
            //         "message" => "Ok",
            //         "data" => array(
            //             "timestamp" => 1694657502415,
            //             "symbol" => "BTCUSDT",
            //             "open_interest" => "265231.721368593081729069",
            //             "open_interest_value" => "7006353.83988919"
            //         ),
            //         "trace" => "7f9c94e10f9d4513bc08a7bfc2a5559a.72.16946575108274991"
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_open_interest($data, $market);
        }) ();
    }

    public function parse_open_interest($interest, ?array $market = null) {
        //
        //     {
        //         "timestamp" => 1694657502415,
        //         "symbol" => "BTCUSDT",
        //         "open_interest" => "265231.721368593081729069",
        //         "open_interest_value" => "7006353.83988919"
        //     }
        //
        $timestamp = $this->safe_integer($interest, 'timestamp');
        $id = $this->safe_string($interest, 'symbol');
        return $this->safe_open_interest(array(
            'symbol' => $this->safe_symbol($id, $market),
            'openInterestAmount' => $this->safe_number($interest, 'open_interest'),
            'openInterestValue' => $this->safe_number($interest, 'open_interest_value'),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'info' => $interest,
        ), $market);
    }

    public function set_leverage(int $leverage, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($leverage, $symbol, $params) {
            /**
             * set the level of $leverage for a $market
             *
             * @see https://developer-pro.bitmart.com/en/futuresv2/#submit-$leverage-signed
             *
             * @param {float} $leverage the rate of $leverage
             * @param {string} $symbol unified $market $symbol
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->marginMode] 'isolated' or 'cross'
             * @return {array} response from the exchange
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' setLeverage() requires a $symbol argument');
            }
            $marginMode = null;
            list($marginMode, $params) = $this->handle_margin_mode_and_params('setLeverage', $params);
            $this->check_required_argument('setLeverage', $marginMode, 'marginMode', array( 'isolated', 'cross' ));
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            if (!$market['swap']) {
                throw new BadSymbol($this->id . ' setLeverage() supports swap contracts only');
            }
            $request = array(
                'symbol' => $market['id'],
                'leverage' => (string) $leverage,
                'open_type' => $marginMode,
            );
            return Async\await($this->privatePostContractPrivateSubmitLeverage ($this->extend($request, $params)));
        }) ();
    }

    public function fetch_funding_rate(string $symbol, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetch the current funding rate
             *
             * @see https://developer-pro.bitmart.com/en/futuresv2/#get-current-funding-rate
             *
             * @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=funding-rate-structure funding rate structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            if (!$market['swap']) {
                throw new BadSymbol($this->id . ' fetchFundingRate() supports swap contracts only');
            }
            $request = array(
                'symbol' => $market['id'],
            );
            $response = Async\await($this->publicGetContractPublicFundingRate ($this->extend($request, $params)));
            //
            //     {
            //         "code" => 1000,
            //         "message" => "Ok",
            //         "data" => array(
            //             "timestamp" => 1695184410697,
            //             "symbol" => "BTCUSDT",
            //             "rate_value" => "-0.00002614",
            //             "expected_rate" => "-0.00002"
            //         ),
            //         "trace" => "4cad855074654097ac7ba5257c47305d.54.16951844206655589"
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_funding_rate($data, $market);
        }) ();
    }

    public function fetch_funding_rate_history(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetches historical funding rate prices
             *
             * @see https://developer-pro.bitmart.com/en/futuresv2/#get-funding-rate-history
             *
             * @param {string} $symbol unified $symbol of the $market to fetch the funding rate history for
             * @param {int} [$since] not sent to exchange api, exchange api always returns the most recent $data, only used to filter exchange $response
             * @param {int} [$limit] the maximum amount of funding rate structures to fetch
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=funding-rate-history-structure funding rate structures~
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' fetchFundingRateHistory() requires a $symbol argument');
            }
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'symbol' => $market['id'],
            );
            if ($limit !== null) {
                $request['limit'] = $limit;
            }
            $response = Async\await($this->publicGetContractPublicFundingRateHistory ($this->extend($request, $params)));
            //
            //     {
            //         "code" => 1000,
            //         "message" => "Ok",
            //         "data" => array(
            //             "list" => array(
            //                 array(
            //                     "symbol" => "BTCUSDT",
            //                     "funding_rate" => "0.000091412174",
            //                     "funding_time" => "1734336000000"
            //                 ),
            //             )
            //         ),
            //         "trace" => "fg73d949fgfdf6a40c8fc7f5ae6738.54.345345345345"
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $result = $this->safe_list($data, 'list', array());
            $rates = array();
            for ($i = 0; $i < count($result); $i++) {
                $entry = $result[$i];
                $marketId = $this->safe_string($entry, 'symbol');
                $symbolInner = $this->safe_symbol($marketId, $market, '-', 'swap');
                $timestamp = $this->safe_integer($entry, 'funding_time');
                $rates[] = array(
                    'info' => $entry,
                    'symbol' => $symbolInner,
                    'fundingRate' => $this->safe_number($entry, 'funding_rate'),
                    'timestamp' => $timestamp,
                    'datetime' => $this->iso8601($timestamp),
                );
            }
            $sorted = $this->sort_by($rates, 'timestamp');
            return $this->filter_by_symbol_since_limit($sorted, $market['symbol'], $since, $limit);
        }) ();
    }

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

    public function fetch_position(string $symbol, $params = array ()) {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetch $data on a single open contract trade position
             *
             * @see https://developer-pro.bitmart.com/en/futuresv2/#get-current-position-keyed
             *
             * @param {string} $symbol unified $market $symbol of the $market the position is held in
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} a ~@link https://docs.ccxt.com/#/?id=position-structure position structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            $request = array(
                'symbol' => $market['id'],
            );
            $response = Async\await($this->privateGetContractPrivatePosition ($this->extend($request, $params)));
            //
            //     {
            //         "code" => 1000,
            //         "message" => "Ok",
            //         "data" => array(
            //             {
            //                 "symbol" => "BTCUSDT",
            //                 "leverage" => "10",
            //                 "timestamp" => 1696392515269,
            //                 "current_fee" => "0.0014250028",
            //                 "open_timestamp" => 1696392256998,
            //                 "current_value" => "27.4039",
            //                 "mark_price" => "27.4039",
            //                 "position_value" => "27.4079",
            //                 "position_cross" => "3.75723474",
            //                 "maintenance_margin" => "0.1370395",
            //                 "close_vol" => "0",
            //                 "close_avg_price" => "0",
            //                 "open_avg_price" => "27407.9",
            //                 "entry_price" => "27407.9",
            //                 "current_amount" => "1",
            //                 "unrealized_value" => "-0.004",
            //                 "realized_value" => "-0.01644474",
            //                 "position_type" => 1
            //             }
            //         ),
            //         "trace":"4cad855074664097ac5ba5257c47305d.67.16963925142065945"
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $first = $this->safe_dict($data, 0, array());
            return $this->parse_position($first, $market);
        }) ();
    }

    public function fetch_positions(?array $symbols = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbols, $params) {
            /**
             * fetch all open contract $positions
             *
             * @see https://developer-pro.bitmart.com/en/futuresv2/#get-current-position-keyed
             * @see https://developer-pro.bitmart.com/en/futuresv2/#get-current-position-v2-keyed
             *
             * @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~
             */
            Async\await($this->load_markets());
            $market = null;
            $symbolsLength = null;
            if ($symbols !== null) {
                $symbolsLength = count($symbols);
                $first = $this->safe_string($symbols, 0);
                $market = $this->market($first);
            }
            $request = array();
            if ($symbolsLength === 1) {
                // only supports $symbols or sending one symbol
                $request['symbol'] = $market['id'];
            }
            $response = Async\await($this->privateGetContractPrivatePositionV2 ($this->extend($request, $params)));
            //
            //     {
            //         "code" => 1000,
            //         "message" => "Ok",
            //         "data" => array(
            //             array(
            //                 "symbol" => "BTCUSDT",
            //                 "leverage" => "10",
            //                 "timestamp" => 1696392515269,
            //                 "current_fee" => "0.0014250028",
            //                 "open_timestamp" => 1696392256998,
            //                 "current_value" => "27.4039",
            //                 "mark_price" => "27.4039",
            //                 "position_value" => "27.4079",
            //                 "position_cross" => "3.75723474",
            //                 "maintenance_margin" => "0.1370395",
            //                 "close_vol" => "0",
            //                 "close_avg_price" => "0",
            //                 "open_avg_price" => "27407.9",
            //                 "entry_price" => "27407.9",
            //                 "current_amount" => "1",
            //                 "unrealized_value" => "-0.004",
            //                 "realized_value" => "-0.01644474",
            //                 "position_type" => 1
            //             ),
            //         ),
            //         "trace":"4cad855074664097ac5ba5257c47305d.67.16963925142065945"
            //     }
            //
            $positions = $this->safe_list($response, 'data', array());
            $result = array();
            for ($i = 0; $i < count($positions); $i++) {
                $result[] = $this->parse_position($positions[$i]);
            }
            $symbols = $this->market_symbols($symbols);
            return $this->filter_by_array_positions($result, 'symbol', $symbols, false);
        }) ();
    }

    public function parse_position(array $position, ?array $market = null) {
        //
        //     {
        //         "symbol" => "BTCUSDT",
        //         "leverage" => "10",
        //         "timestamp" => 1696392515269,
        //         "current_fee" => "0.0014250028",
        //         "open_timestamp" => 1696392256998,
        //         "current_value" => "27.4039",
        //         "mark_price" => "27.4039",
        //         "position_value" => "27.4079",
        //         "position_cross" => "3.75723474",
        //         "maintenance_margin" => "0.1370395",
        //         "close_vol" => "0",
        //         "close_avg_price" => "0",
        //         "open_avg_price" => "27407.9",
        //         "entry_price" => "27407.9",
        //         "current_amount" => "1",
        //         "unrealized_value" => "-0.004",
        //         "realized_value" => "-0.01644474",
        //         "position_type" => 1
        //     }
        //
        $marketId = $this->safe_string($position, 'symbol');
        $market = $this->safe_market($marketId, $market);
        $symbol = $market['symbol'];
        $timestamp = $this->safe_integer($position, 'timestamp');
        $side = $this->safe_integer($position, 'position_type');
        $maintenanceMargin = $this->safe_string($position, 'maintenance_margin');
        $notional = $this->safe_string($position, 'current_value');
        $collateral = $this->safe_string($position, 'position_cross');
        $maintenanceMarginPercentage = Precise::string_div($maintenanceMargin, $notional);
        $marginRatio = Precise::string_div($maintenanceMargin, $collateral);
        return $this->safe_position(array(
            'info' => $position,
            'id' => null,
            'symbol' => $symbol,
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'lastUpdateTimestamp' => null,
            'hedged' => null,
            'side' => ($side === 1) ? 'long' : 'short',
            'contracts' => $this->safe_number($position, 'current_amount'),
            'contractSize' => $this->safe_number($market, 'contractSize'),
            'entryPrice' => $this->safe_number($position, 'entry_price'),
            'markPrice' => $this->safe_number($position, 'mark_price'),
            'lastPrice' => null,
            'notional' => $this->parse_number($notional),
            'leverage' => $this->safe_number($position, 'leverage'),
            'collateral' => $this->parse_number($collateral),
            'initialMargin' => null,
            'initialMarginPercentage' => null,
            'maintenanceMargin' => $this->parse_number($maintenanceMargin),
            'maintenanceMarginPercentage' => $this->parse_number($maintenanceMarginPercentage),
            'unrealizedPnl' => $this->safe_number($position, 'unrealized_value'),
            'realizedPnl' => $this->safe_number($position, 'realized_value'),
            'liquidationPrice' => null,
            'marginMode' => null,
            'percentage' => null,
            'marginRatio' => $this->parse_number($marginRatio),
            'stopLossPrice' => null,
            'takeProfitPrice' => null,
        ));
    }

    public function fetch_my_liquidations(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * retrieves the users liquidated positions
             *
             * @see https://developer-pro.bitmart.com/en/futuresv2/#get-order-history-keyed
             *
             * @param {string} $symbol unified CCXT $market $symbol
             * @param {int} [$since] the earliest time in ms to fetch liquidations for
             * @param {int} [$limit] the maximum number of liquidation structures to retrieve
             * @param {array} [$params] exchange specific parameters for the bitmart api endpoint
             * @param {int} [$params->until] timestamp in ms of the latest liquidation
             * @return {array} an array of ~@link https://docs.ccxt.com/#/?id=liquidation-structure liquidation structures~
             */
            if ($symbol === null) {
                throw new ArgumentsRequired($this->id . ' fetchMyLiquidations() requires a $symbol argument');
            }
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            if (!$market['swap']) {
                throw new NotSupported($this->id . ' fetchMyLiquidations() supports swap markets only');
            }
            $request = array(
                'symbol' => $market['id'],
            );
            if ($since !== null) {
                $request['start_time'] = $since;
            }
            list($request, $params) = $this->handle_until_option('end_time', $request, $params);
            $response = Async\await($this->privateGetContractPrivateOrderHistory ($this->extend($request, $params)));
            //
            //     {
            //         "code" => 1000,
            //         "message" => "Ok",
            //         "data" => array(
            //             array(
            //                 "order_id" => "231007865458273",
            //                 "client_order_id" => "",
            //                 "price" => "27407.9",
            //                 "size" => "1",
            //                 "symbol" => "BTCUSDT",
            //                 "state" => 4,
            //                 "side" => 3,
            //                 "type" => "liquidate",
            //                 "leverage" => "10",
            //                 "open_type" => "isolated",
            //                 "deal_avg_price" => "27422.6",
            //                 "deal_size" => "1",
            //                 "create_time" => 1696405864011,
            //                 "update_time" => 1696405864045
            //             ),
            //         ),
            //         "trace" => "4cad855074664097ac6ba4257c47305d.71.16965658195443021"
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            $result = array();
            for ($i = 0; $i < count($data); $i++) {
                $entry = $data[$i];
                $checkLiquidation = $this->safe_string($entry, 'type');
                if ($checkLiquidation === 'liquidate') {
                    $result[] = $entry;
                }
            }
            return $this->parse_liquidations($result, $market, $since, $limit);
        }) ();
    }

    public function parse_liquidation($liquidation, ?array $market = null) {
        //
        //     {
        //         "order_id" => "231007865458273",
        //         "client_order_id" => "",
        //         "price" => "27407.9",
        //         "size" => "1",
        //         "symbol" => "BTCUSDT",
        //         "state" => 4,
        //         "side" => 3,
        //         "type" => "market",
        //         "leverage" => "10",
        //         "open_type" => "isolated",
        //         "deal_avg_price" => "27422.6",
        //         "deal_size" => "1",
        //         "create_time" => 1696405864011,
        //         "update_time" => 1696405864045
        //     }
        //
        $marketId = $this->safe_string($liquidation, 'symbol');
        $timestamp = $this->safe_integer($liquidation, 'update_time');
        $contractsString = $this->safe_string($liquidation, 'deal_size');
        $contractSizeString = $this->safe_string($market, 'contractSize');
        $priceString = $this->safe_string($liquidation, 'deal_avg_price');
        $baseValueString = Precise::string_mul($contractsString, $contractSizeString);
        $quoteValueString = Precise::string_mul($baseValueString, $priceString);
        return $this->safe_liquidation(array(
            'info' => $liquidation,
            'symbol' => $this->safe_symbol($marketId, $market),
            'contracts' => $this->parse_number($contractsString),
            'contractSize' => $this->parse_number($contractSizeString),
            'price' => $this->parse_number($priceString),
            'baseValue' => $this->parse_number($baseValueString),
            'quoteValue' => $this->parse_number($quoteValueString),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
        ));
    }

    public function edit_order(string $id, string $symbol, string $type, string $side, ?float $amount = null, ?float $price = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($id, $symbol, $type, $side, $amount, $price, $params) {
            /**
             * edits an open order
             *
             * @see https://developer-pro.bitmart.com/en/futuresv2/#modify-plan-order-signed
             * @see https://developer-pro.bitmart.com/en/futuresv2/#modify-tp-sl-order-signed
             * @see https://developer-pro.bitmart.com/en/futuresv2/#modify-preset-plan-order-signed
             * @see https://developer-pro.bitmart.com/en/futuresv2/#modify-limit-order-signed
             *
             * @param {string} $id order $id
             * @param {string} $symbol unified $symbol of the $market to edit an order in
             * @param {string} $type 'market' or 'limit'
             * @param {string} $side 'buy' or 'sell'
             * @param {float} [$amount] how much you want to trade in units of the base currency
             * @param {float} [$price] the $price to fulfill the order, in units of the quote currency, ignored in $market orders
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {string} [$params->triggerPrice] *swap only* the $price to trigger a stop order
             * @param {string} [$params->stopLossPrice] *swap only* the $price to trigger a stop-loss order
             * @param {string} [$params->takeProfitPrice] *swap only* the $price to trigger a take-profit order
             * @param {string} [$params->stopLoss.triggerPrice] *swap only* the $price to trigger a preset stop-loss order
             * @param {string} [$params->takeProfit.triggerPrice] *swap only* the $price to trigger a preset take-profit order
             * @param {string} [$params->clientOrderId] client order $id of the order
             * @param {int} [$params->price_type] *swap only* 1 => last $price, 2 => fair $price, default is 1
             * @param {int} [$params->plan_category] *swap tp/sl only* 1 => tp/sl, 2 => position tp/sl, default is 1
             * @return {array} an ~@link https://docs.ccxt.com/#/?$id=order-structure order structure~
             */
            Async\await($this->load_markets());
            $market = $this->market($symbol);
            if (!$market['swap']) {
                throw new NotSupported($this->id . ' editOrder() does not support ' . $market['type'] . ' markets, only swap markets are supported');
            }
            $stopLossPrice = $this->safe_string($params, 'stopLossPrice');
            $takeProfitPrice = $this->safe_string($params, 'takeProfitPrice');
            $triggerPrice = $this->safe_string_n($params, array( 'triggerPrice', 'stopPrice', 'trigger_price' ));
            $stopLoss = $this->safe_dict($params, 'stopLoss', array());
            $takeProfit = $this->safe_dict($params, 'takeProfit', array());
            $presetStopLoss = $this->safe_string($stopLoss, 'triggerPrice');
            $presetTakeProfit = $this->safe_string($takeProfit, 'triggerPrice');
            $isTriggerOrder = $triggerPrice !== null;
            $isStopLoss = $stopLossPrice !== null;
            $isTakeProfit = $takeProfitPrice !== null;
            $isPresetStopLoss = $presetStopLoss !== null;
            $isPresetTakeProfit = $presetTakeProfit !== null;
            $isLimitOrder = ($type === 'limit');
            $request = array(
                'symbol' => $market['id'],
            );
            $clientOrderId = $this->safe_string($params, 'clientOrderId');
            if ($clientOrderId !== null) {
                $params = $this->omit($params, 'clientOrderId');
                $request['client_order_id'] = $clientOrderId;
            }
            if ($id !== null) {
                $request['order_id'] = $id;
            }
            $params = $this->omit($params, array( 'triggerPrice', 'stopPrice', 'stopLossPrice', 'takeProfitPrice', 'stopLoss', 'takeProfit' ));
            $response = null;
            if ($isTriggerOrder || $isStopLoss || $isTakeProfit) {
                $request['price_type'] = $this->safe_integer($params, 'price_type', 1);
                if ($price !== null) {
                    $request['executive_price'] = $this->price_to_precision($symbol, $price);
                }
            }
            if ($isTriggerOrder) {
                $request['type'] = $type;
                $request['trigger_price'] = $this->price_to_precision($symbol, $triggerPrice);
                $response = Async\await($this->privatePostContractPrivateModifyPlanOrder ($this->extend($request, $params)));
                //
                //     {
                //         "code" => 1000,
                //         "message" => "Ok",
                //         "data" => array(
                //             "order_id" => "3000023150003503"
                //         ),
                //         "trace" => "324523453245.108.1734567125596324575"
                //     }
                //
            } elseif ($isStopLoss || $isTakeProfit) {
                $request['category'] = $type;
                if ($isStopLoss) {
                    $request['trigger_price'] = $this->price_to_precision($symbol, $stopLossPrice);
                } else {
                    $request['trigger_price'] = $this->price_to_precision($symbol, $takeProfitPrice);
                }
                $response = Async\await($this->privatePostContractPrivateModifyTpSlOrder ($this->extend($request, $params)));
                //
                //     {
                //         "code" => 1000,
                //         "message" => "Ok",
                //         "data" => array(
                //             "order_id" => "3000023150003480"
                //         ),
                //         "trace" => "23452345.104.1724536582682345459"
                //     }
                //
            } elseif ($isPresetStopLoss || $isPresetTakeProfit) {
                if ($isPresetStopLoss) {
                    $request['preset_stop_loss_price_type'] = $this->safe_integer($params, 'price_type', 1);
                    $request['preset_stop_loss_price'] = $this->price_to_precision($symbol, $presetStopLoss);
                } else {
                    $request['preset_take_profit_price_type'] = $this->safe_integer($params, 'price_type', 1);
                    $request['preset_take_profit_price'] = $this->price_to_precision($symbol, $presetTakeProfit);
                }
                $response = Async\await($this->privatePostContractPrivateModifyPresetPlanOrder ($this->extend($request, $params)));
                //
                //     {
                //         "code" => 1000,
                //         "message" => "Ok",
                //         "data" => array(
                //             "order_id" => "3000023150003496"
                //         ),
                //         "trace" => "a5c3234534534a836bc476a203.123452.172716624359200197"
                //     }
                //
            } elseif ($isLimitOrder) {
                $request['order_id'] = $this->parse_to_int($id); // reparse $id this endpoint is the only one requiring it
                if ($amount !== null) {
                    $request['size'] = $this->amount_to_precision($symbol, $amount);
                }
                if ($price !== null) {
                    $request['price'] = $this->price_to_precision($symbol, $price);
                }
                $response = Async\await($this->privatePostContractPrivateModifyLimitOrder ($this->extend($request, $params)));
            } else {
                throw new NotSupported($this->id . ' editOrder() only supports limit, trigger, stop loss and take profit orders');
            }
            $data = $this->safe_dict($response, 'data', array());
            return $this->parse_order($data, $market);
        }) ();
    }

    public function fetch_ledger(?string $code = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($code, $since, $limit, $params) {
            /**
             * fetch the history of changes, actions done by the user or operations that altered the balance of the user
             *
             * @see https://developer-pro.bitmart.com/en/futuresv2/#get-transaction-history-keyed
             *
             * @param {string} [$code] unified $currency $code
             * @param {int} [$since] timestamp in ms of the earliest ledger entry
             * @param {int} [$limit] max number of ledger entries to return
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] timestamp in ms of the latest ledger entry
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=ledger ledger structures~
             */
            Async\await($this->load_markets());
            $currency = null;
            if ($code !== null) {
                $currency = $this->currency($code);
            }
            $request = array();
            list($request, $params) = $this->handle_until_option('end_time', $request, $params);
            $transactionsRequest = $this->fetch_transactions_request(0, null, $since, $limit, $params);
            $response = Async\await($this->privateGetContractPrivateTransactionHistory ($transactionsRequest));
            //
            //     {
            //         "code" => 1000,
            //         "message" => "Ok",
            //         "data" => array(
            //             array(
            //                 "time" => "1734422402121",
            //                 "type" => "Funding Fee",
            //                 "amount" => "-0.00008253",
            //                 "asset" => "USDT",
            //                 "symbol" => "LTCUSDT",
            //                 "tran_id" => "1734422402121",
            //                 "flow_type" => 3
            //             ),
            //         ),
            //         "trace" => "4cd11f83c71egfhfgh842790f07241e.23.173442343427772866"
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_ledger($data, $currency, $since, $limit);
        }) ();
    }

    public function parse_ledger_entry(array $item, ?array $currency = null): array {
        //
        //     {
        //         "time" => "1734422402121",
        //         "type" => "Funding Fee",
        //         "amount" => "-0.00008253",
        //         "asset" => "USDT",
        //         "symbol" => "LTCUSDT",
        //         "tran_id" => "1734422402121",
        //         "flow_type" => 3
        //     }
        //
        $amount = $this->safe_string($item, 'amount');
        $direction = null;
        if (Precise::string_le($amount, '0')) {
            $direction = 'out';
            $amount = Precise::string_mul('-1', $amount);
        } else {
            $direction = 'in';
        }
        $currencyId = $this->safe_string($item, 'asset');
        $currency = $this->safe_currency($currencyId, $currency);
        $timestamp = $this->safe_integer($item, 'time');
        $type = $this->safe_string($item, 'type');
        return $this->safe_ledger_entry(array(
            'info' => $item,
            'id' => $this->safe_string($item, 'tran_id'),
            'direction' => $direction,
            'account' => null,
            'referenceAccount' => null,
            'referenceId' => $this->safe_string($item, 'tradeId'),
            'type' => $this->parse_ledger_entry_type($type),
            'currency' => $currency['code'],
            'amount' => $this->parse_number($amount),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'before' => null,
            'after' => null,
            'status' => null,
            'fee' => null,
        ), $currency);
    }

    public function parse_ledger_entry_type($type) {
        $ledgerType = array(
            'Commission Fee' => 'fee',
            'Funding Fee' => 'fee',
            'Realized PNL' => 'trade',
            'Transfer' => 'transfer',
            'Liquidation Clearance' => 'settlement',
        );
        return $this->safe_string($ledgerType, $type, $type);
    }

    public function fetch_transactions_request(?int $flowType = null, ?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()) {
        $request = array();
        if ($flowType !== null) {
            $request['flow_type'] = $flowType;
        }
        $market = null;
        if ($symbol !== null) {
            $market = $this->market($symbol);
            $request['symbol'] = $market['id'];
        }
        if ($since !== null) {
            $request['start_time'] = $since;
        }
        if ($limit !== null) {
            $request['page_size'] = $limit;
        }
        list($request, $params) = $this->handle_until_option('end_time', $request, $params);
        return $this->extend($request, $params);
    }

    public function fetch_funding_history(?string $symbol = null, ?int $since = null, ?int $limit = null, $params = array ()): PromiseInterface {
        return Async\async(function () use ($symbol, $since, $limit, $params) {
            /**
             * fetch the history of funding payments paid and received on this account
             *
             * @see https://developer-pro.bitmart.com/en/futuresv2/#get-transaction-history-keyed
             *
             * @param {string} [$symbol] unified $market $symbol
             * @param {int} [$since] the starting timestamp in milliseconds
             * @param {int} [$limit] the number of entries to return
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @param {int} [$params->until] the latest time in ms to fetch funding history for
             * @return {array[]} a list of ~@link https://docs.ccxt.com/#/?id=funding-history-structure funding history structures~
             */
            Async\await($this->load_markets());
            $market = null;
            if ($symbol !== null) {
                $market = $this->market($symbol);
            }
            $request = array();
            list($request, $params) = $this->handle_until_option('end_time', $request, $params);
            $transactionsRequest = $this->fetch_transactions_request(3, $symbol, $since, $limit, $params);
            $response = Async\await($this->privateGetContractPrivateTransactionHistory ($transactionsRequest));
            //
            //     {
            //         "code" => 1000,
            //         "message" => "Ok",
            //         "data" => array(
            //             array(
            //                 "time" => "1734422402121",
            //                 "type" => "Funding Fee",
            //                 "amount" => "-0.00008253",
            //                 "asset" => "USDT",
            //                 "symbol" => "LTCUSDT",
            //                 "tran_id" => "1734422402121",
            //                 "flow_type" => 3
            //             ),
            //         ),
            //         "trace" => "4cd11f83c71egfhfgh842790f07241e.23.173442343427772866"
            //     }
            //
            $data = $this->safe_list($response, 'data', array());
            return $this->parse_funding_histories($data, $market, $since, $limit);
        }) ();
    }

    public function parse_funding_history($contract, ?array $market = null) {
        //
        //     {
        //         "time" => "1734422402121",
        //         "type" => "Funding Fee",
        //         "amount" => "-0.00008253",
        //         "asset" => "USDT",
        //         "symbol" => "LTCUSDT",
        //         "tran_id" => "1734422402121",
        //         "flow_type" => 3
        //     }
        //
        $marketId = $this->safe_string($contract, 'symbol');
        $currencyId = $this->safe_string($contract, 'asset');
        $timestamp = $this->safe_integer($contract, 'time');
        return array(
            'info' => $contract,
            'symbol' => $this->safe_symbol($marketId, $market, null, 'swap'),
            'code' => $this->safe_currency_code($currencyId),
            'timestamp' => $timestamp,
            'datetime' => $this->iso8601($timestamp),
            'id' => $this->safe_string($contract, 'tran_id'),
            'amount' => $this->safe_number($contract, 'amount'),
        );
    }

    public function parse_funding_histories($contracts, $market = null, ?int $since = null, ?int $limit = null): array {
        $result = array();
        for ($i = 0; $i < count($contracts); $i++) {
            $contract = $contracts[$i];
            $result[] = $this->parse_funding_history($contract, $market);
        }
        $sorted = $this->sort_by($result, 'timestamp');
        return $this->filter_by_since_limit($sorted, $since, $limit);
    }

    public function fetch_withdraw_addresses(string $code, $note = null, $networkCode = null, $params = array ()) {
        return Async\async(function () use ($code, $note, $networkCode, $params) {
            Async\await($this->load_markets());
            $codes = null;
            if ($code !== null) {
                $currency = $this->currency($code);
                $code = $currency['code'];
                $codes = array( $code );
            }
            $response = Async\await($this->privateGetAccountV1WithdrawAddressList ($params));
            //
            //     {
            //         "message" => "OK",
            //         "code" => 1000,
            //         "trace" => "0e6edd79-f77f-4251-abe5-83ba75d06c1a",
            //         "data" => {
            //             "list" => array(
            //                 {
            //                     "currency" => "ETH",
            //                     "network" => "ETH",
            //                     "address" => "0x1121",
            //                     "memo" => "12",
            //                     "remark" => "12",
            //                     "addressType" => 0,
            //                     "verifyStatus" => 0
            //                 }
            //             )
            //         }
            //     }
            //
            $data = $this->safe_dict($response, 'data', array());
            $list = $this->safe_list($data, 'list', array());
            $allAddresses = $this->parse_deposit_addresses($list, $codes, false);
            $addresses = array();
            for ($i = 0; $i < count($allAddresses); $i++) {
                $address = $allAddresses[$i];
                $noteMatch = ($note === null) || ($address['note'] === $note);
                $networkMatch = ($networkCode === null) || ($address['network'] === $networkCode);
                if ($noteMatch && $networkMatch) {
                    $addresses[] = $address;
                }
            }
            return $addresses;
        }) ();
    }

    public function set_position_mode(bool $hedged, ?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($hedged, $symbol, $params) {
            /**
             * set $hedged to true or false for a market
             *
             * @see https://developer-pro.bitmart.com/en/futuresv2/#submit-leverage-signed
             *
             * @param {bool} $hedged set to true to use dualSidePosition
             * @param {string} $symbol not used by bingx setPositionMode ()
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} response from the exchange
             */
            Async\await($this->load_markets());
            $positionMode = null;
            if ($hedged) {
                $positionMode = 'hedge_mode';
            } else {
                $positionMode = 'one_way_mode';
            }
            $request = array(
                'position_mode' => $positionMode,
            );
            //
            // {
            //     "code" => 1000,
            //     "trace" => "0cc6f4c4-8b8c-4253-8e90-8d3195aa109c",
            //     "message" => "Ok",
            //     "data" => {
            //       "position_mode":"one_way_mode"
            //     }
            // }
            //
            return Async\await($this->privatePostContractPrivateSetPositionMode ($this->extend($request, $params)));
        }) ();
    }

    public function fetch_position_mode(?string $symbol = null, $params = array ()) {
        return Async\async(function () use ($symbol, $params) {
            /**
             * fetchs the position mode, hedged or one way, hedged for binance is set identically for all linear markets or all inverse markets
             *
             * @see https://developer-pro.bitmart.com/en/futuresv2/#get-position-mode-keyed
             *
             * @param {string} $symbol not used
             * @param {array} [$params] extra parameters specific to the exchange API endpoint
             * @return {array} an object detailing whether the market is in hedged or one-way mode
             */
            $response = Async\await($this->privateGetContractPrivateGetPositionMode ($params));
            //
            // {
            //     "code" => 1000,
            //     "trace" => "0cc6f4c4-8b8c-4253-8e90-8d3195aa109c",
            //     "message" => "Ok",
            //     "data" => {
            //       "position_mode":"one_way_mode"
            //     }
            // }
            //
            $data = $this->safe_dict($response, 'data');
            $positionMode = $this->safe_string($data, 'position_mode');
            return array(
                'info' => $response,
                'hedged' => ($positionMode === 'hedge_mode'),
            );
        }) ();
    }

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

    public function sign($path, $api = 'public', $method = 'GET', $params = array (), $headers = null, $body = null) {
        $parts = explode('/', $path);
        // to do => refactor $api endpoints with spot/swap sections
        $category = $this->safe_string($parts, 0, 'spot');
        $market = ($category === 'spot' || $category === 'account') ? 'spot' : 'swap';
        $baseUrl = $this->implode_hostname($this->urls['api'][$market]);
        $url = $baseUrl . '/' . $this->implode_params($path, $params);
        $query = $this->omit($params, $this->extract_params($path));
        $queryString = '';
        $getOrDelete = ($method === 'GET') || ($method === 'DELETE');
        if ($getOrDelete) {
            if ($query) {
                $queryString = $this->urlencode($query);
                $url .= '?' . $queryString;
            }
        }
        if ($api === 'private') {
            $this->check_required_credentials();
            $timestamp = (string) $this->nonce();
            $brokerId = $this->safe_string($this->options, 'brokerId', 'CCXTxBitmart000');
            $headers = array(
                'X-BM-KEY' => $this->apiKey,
                'X-BM-TIMESTAMP' => $timestamp,
                'X-BM-BROKER-ID' => $brokerId,
                'Content-Type' => 'application/json',
            );
            if (!$getOrDelete) {
                $body = $this->json($query);
                $queryString = $body;
            }
            $auth = $timestamp . '#' . $this->uid . '#' . $queryString;
            $signature = $this->hmac($this->encode($auth), $this->encode($this->secret), 'sha256');
            $headers['X-BM-SIGN'] = $signature;
        }
        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;
        }
        //
        // spot
        //
        //     array("message":"Bad Request [to is empty]","code":50000,"trace":"f9d46e1b-4edb-4d07-a06e-4895fb2fc8fc","data":array())
        //     array("message":"Bad Request [from is empty]","code":50000,"trace":"579986f7-c93a-4559-926b-06ba9fa79d76","data":array())
        //     array("message":"Kline size over 500","code":50004,"trace":"d625caa8-e8ca-4bd2-b77c-958776965819","data":array())
        //     array("message":"Balance not enough","code":50020,"trace":"7c709d6a-3292-462c-98c5-32362540aeef","data":array())
        //     array("code":40012,"message":"You contract account available balance not enough.","trace":"...")
        //
        // contract
        //
        //     array("errno":"OK","message":"INVALID_PARAMETER","code":49998,"trace":"eb5ebb54-23cd-4de2-9064-e090b6c3b2e3","data":null)
        //
        $message = $this->safe_string_lower($response, 'message');
        $isErrorMessage = ($message !== null) && ($message !== 'ok') && ($message !== 'success');
        $errorCode = $this->safe_string($response, 'code');
        $isErrorCode = ($errorCode !== null) && ($errorCode !== '1000');
        if ($isErrorCode || $isErrorMessage) {
            $feedback = $this->id . ' ' . $body;
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $message, $feedback);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $message, $feedback);
            $this->throw_exactly_matched_exception($this->exceptions['exact'], $errorCode, $feedback);
            $this->throw_broadly_matched_exception($this->exceptions['broad'], $errorCode, $feedback);
            throw new ExchangeError($feedback); // unknown $message
        }
        return null;
    }
}
